Skip to content

Commit fbdd2ab

Browse files
committed
Add support for subexpressions fix jknack#282
{{outer-helper (inner-helper 'abc') 'def'} Inner-helper will get invoked with the string argument 'abc', and whatever the inner-helper function returns will get passed in as the first argument to outer-helper (and 'def' will get passed passed in as the second argument to outer-helper
1 parent 03b9802 commit fbdd2ab

File tree

10 files changed

+246
-23
lines changed

10 files changed

+246
-23
lines changed

handlebars/src/main/antlr4/com/github/jknack/handlebars/internal/HbsLexer.g4

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -296,6 +296,16 @@ ID_PART
296296
[0-9./]
297297
;
298298
299+
LP
300+
:
301+
'('
302+
;
303+
304+
RP
305+
:
306+
')'
307+
;
308+
299309
WS
300310
: [ \t\r\n] -> skip
301311
;

handlebars/src/main/antlr4/com/github/jknack/handlebars/internal/HbsParser.g4

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -57,16 +57,22 @@ newline
5757

5858
block
5959
:
60-
START_BLOCK nameStart=QID param* hash* END
60+
START_BLOCK sexpr END
6161
thenBody=body
6262
elseBlock?
6363
END_BLOCK nameEnd=QID END
6464
;
6565

66+
sexpr
67+
:
68+
QID param* hash*
69+
;
70+
6671
elseBlock
6772
:
6873
(inverseToken=UNLESS | START inverseToken=ELSE) END unlessBody=body
6974
;
75+
7076
unless
7177
:
7278
UNLESS nameStart=QID END
@@ -76,17 +82,17 @@ unless
7682

7783
tvar
7884
:
79-
START_T QID param* hash* END_T
85+
START_T sexpr END_T
8086
;
8187

8288
ampvar
8389
:
84-
START_AMP QID param* hash* END
90+
START_AMP sexpr END
8591
;
8692

8793
var
8894
:
89-
START QID param* hash* END
95+
START sexpr END
9096
;
9197

9298
delimiters
@@ -114,6 +120,7 @@ param
114120
| INT #intParam
115121
| BOOLEAN #boolParam
116122
| QID #refPram
123+
| LP sexpr RP #subexpression
117124
;
118125

119126
hash

handlebars/src/main/java/com/github/jknack/handlebars/TagType.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,12 @@ public enum TagType {
4343
*/
4444
TRIPLE_VAR,
4545

46+
/**
47+
* Same as {@link #VAR} but can be invoked from inside a helper:
48+
* <code>{{helper (subexpression)}}</code>.
49+
*/
50+
SUB_EXPRESSION,
51+
4652
/**
4753
* <p>
4854
* Sections render blocks of text one or more times, depending on the value of the key in the

handlebars/src/main/java/com/github/jknack/handlebars/internal/Block.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -318,6 +318,7 @@ protected void collect(final Collection<String> result, final TagType tagType) {
318318
if (tagType == TagType.SECTION) {
319319
result.add(name);
320320
}
321+
super.collect(result, tagType);
321322
}
322323

323324
@Override

handlebars/src/main/java/com/github/jknack/handlebars/internal/HelperResolver.java

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,9 @@
1919

2020
import static org.apache.commons.lang3.Validate.notNull;
2121

22+
import java.io.IOException;
2223
import java.util.ArrayList;
24+
import java.util.Collection;
2325
import java.util.Collections;
2426
import java.util.LinkedHashMap;
2527
import java.util.List;
@@ -30,6 +32,7 @@
3032
import com.github.jknack.handlebars.Handlebars;
3133
import com.github.jknack.handlebars.Helper;
3234
import com.github.jknack.handlebars.HelperRegistry;
35+
import com.github.jknack.handlebars.TagType;
3336
import com.github.jknack.handlebars.Template;
3437

3538
/**
@@ -74,8 +77,9 @@ public HelperResolver(final Handlebars handlebars) {
7477
*
7578
* @param context The current context.
7679
* @return A hash object with values in the current context.
80+
* @throws IOException If param can't be applied.
7781
*/
78-
protected Map<String, Object> hash(final Context context) {
82+
protected Map<String, Object> hash(final Context context) throws IOException {
7983
Map<String, Object> result = new LinkedHashMap<String, Object>();
8084
for (Entry<String, Object> entry : hash.entrySet()) {
8185
Object value = entry.getValue();
@@ -90,8 +94,9 @@ protected Map<String, Object> hash(final Context context) {
9094
*
9195
* @param scope The current context.
9296
* @return A parameter list with values in the current context.
97+
* @throws IOException If param can't be applied.
9398
*/
94-
protected Object[] params(final Context scope) {
99+
protected Object[] params(final Context scope) throws IOException {
95100
if (params.size() <= 1) {
96101
return PARAMS;
97102
}
@@ -111,8 +116,9 @@ protected Object[] params(final Context scope) {
111116
*
112117
* @param context The current context.
113118
* @return The current context.
119+
* @throws IOException If param can't be applied.
114120
*/
115-
protected Object determineContext(final Context context) {
121+
protected Object determineContext(final Context context) throws IOException {
116122
if (params.size() == 0) {
117123
return context.model();
118124
}
@@ -218,4 +224,12 @@ protected String hashToString() {
218224
return "";
219225
}
220226

227+
@Override
228+
protected void collect(final Collection<String> result, final TagType tagType) {
229+
for(Object param : this.params) {
230+
if (param instanceof Variable) {
231+
((Variable) param).collect(result, tagType);
232+
}
233+
}
234+
}
221235
}

handlebars/src/main/java/com/github/jknack/handlebars/internal/ParamType.java

Lines changed: 21 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@
1717
*/
1818
package com.github.jknack.handlebars.internal;
1919

20+
import java.io.IOException;
21+
2022
import com.github.jknack.handlebars.Context;
2123

2224
/**
@@ -105,7 +107,21 @@ boolean apply(final Object param) {
105107
Object doParse(final Context scope, final Object param) {
106108
return scope.get((String) param);
107109
}
108-
};
110+
},
111+
112+
SUB_EXPRESSION {
113+
@Override
114+
boolean apply(final Object param) {
115+
return param instanceof Variable;
116+
}
117+
118+
@Override
119+
Object doParse(final Context context, final Object param) throws IOException {
120+
Variable var = (Variable) param;
121+
return var.apply(context);
122+
}
123+
}
124+
;
109125

110126
/**
111127
* True if the current strategy applies for the given value.
@@ -121,17 +137,19 @@ Object doParse(final Context scope, final Object param) {
121137
* @param context The context.
122138
* @param param The candidate param.
123139
* @return A parsed value.
140+
* @throws IOException If param can't be applied.
124141
*/
125-
abstract Object doParse(Context context, Object param);
142+
abstract Object doParse(Context context, Object param) throws IOException;
126143

127144
/**
128145
* Parse the given parameter to a runtime representation.
129146
*
130147
* @param context The current context.
131148
* @param param The candidate parameter.
132149
* @return The parameter value at runtime.
150+
* @throws IOException If param can't be applied.
133151
*/
134-
public static Object parse(final Context context, final Object param) {
152+
public static Object parse(final Context context, final Object param) throws IOException {
135153
return get(param).doParse(context, param);
136154
}
137155

handlebars/src/main/java/com/github/jknack/handlebars/internal/TemplateBuilder.java

Lines changed: 27 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -56,10 +56,12 @@
5656
import com.github.jknack.handlebars.internal.HbsParser.PartialContext;
5757
import com.github.jknack.handlebars.internal.HbsParser.RefHashContext;
5858
import com.github.jknack.handlebars.internal.HbsParser.RefPramContext;
59+
import com.github.jknack.handlebars.internal.HbsParser.SexprContext;
5960
import com.github.jknack.handlebars.internal.HbsParser.SpacesContext;
6061
import com.github.jknack.handlebars.internal.HbsParser.StatementContext;
6162
import com.github.jknack.handlebars.internal.HbsParser.StringHashContext;
6263
import com.github.jknack.handlebars.internal.HbsParser.StringParamContext;
64+
import com.github.jknack.handlebars.internal.HbsParser.SubexpressionContext;
6365
import com.github.jknack.handlebars.internal.HbsParser.TemplateContext;
6466
import com.github.jknack.handlebars.internal.HbsParser.TextContext;
6567
import com.github.jknack.handlebars.internal.HbsParser.TvarContext;
@@ -113,18 +115,20 @@ public Template visit(final ParseTree tree) {
113115

114116
@Override
115117
public Template visitBlock(final BlockContext ctx) {
116-
String nameStart = ctx.nameStart.getText();
118+
SexprContext sexpr = ctx.sexpr();
119+
Token nameStart = sexpr.QID().getSymbol();
120+
String name = nameStart.getText();
117121
String nameEnd = ctx.nameEnd.getText();
118-
if (!nameStart.equals(nameEnd)) {
122+
if (!name.equals(nameEnd)) {
119123
reportError(null, ctx.nameEnd.getLine(), ctx.nameEnd.getCharPositionInLine()
120-
, String.format("found: '%s', expected: '%s'", nameEnd, nameStart));
124+
, String.format("found: '%s', expected: '%s'", nameEnd, name));
121125
}
122126

123127
hasTag(true);
124-
Block block = new Block(handlebars, nameStart, false, params(ctx.param()),
125-
hash(ctx.hash()));
128+
Block block = new Block(handlebars, name, false, params(sexpr.param()),
129+
hash(sexpr.hash()));
126130
block.filename(source.filename());
127-
block.position(ctx.nameStart.getLine(), ctx.nameStart.getCharPositionInLine());
131+
block.position(nameStart.getLine(), nameStart.getCharPositionInLine());
128132
String startDelim = ctx.start.getText();
129133
startDelim = startDelim.substring(0, startDelim.length() - 1);
130134
block.startDelimiter(startDelim);
@@ -171,21 +175,26 @@ public Template visitUnless(final UnlessContext ctx) {
171175
@Override
172176
public Template visitVar(final VarContext ctx) {
173177
hasTag(false);
174-
return newVar(ctx.QID().getSymbol(), TagType.VAR, params(ctx.param()), hash(ctx.hash()),
178+
SexprContext sexpr = ctx.sexpr();
179+
return newVar(sexpr.QID().getSymbol(), TagType.VAR, params(sexpr.param()), hash(sexpr.hash()),
175180
ctx.start.getText(), ctx.stop.getText());
176181
}
177182

178183
@Override
179184
public Template visitTvar(final TvarContext ctx) {
180185
hasTag(false);
181-
return newVar(ctx.QID().getSymbol(), TagType.TRIPLE_VAR, params(ctx.param()), hash(ctx.hash()),
186+
SexprContext sexpr = ctx.sexpr();
187+
return newVar(sexpr.QID().getSymbol(), TagType.TRIPLE_VAR, params(sexpr.param()),
188+
hash(sexpr.hash()),
182189
ctx.start.getText(), ctx.stop.getText());
183190
}
184191

185192
@Override
186193
public Template visitAmpvar(final AmpvarContext ctx) {
187194
hasTag(false);
188-
return newVar(ctx.QID().getSymbol(), TagType.AMP_VAR, params(ctx.param()), hash(ctx.hash()),
195+
SexprContext sexpr = ctx.sexpr();
196+
return newVar(sexpr.QID().getSymbol(), TagType.AMP_VAR, params(sexpr.param()),
197+
hash(sexpr.hash()),
189198
ctx.start.getText(), ctx.stop.getText());
190199
}
191200

@@ -204,7 +213,8 @@ private Template newVar(final Token name, final TagType varType, final List<Obje
204213
final Map<String, Object> hash, final String startDelimiter, final String endDelimiter) {
205214
String varName = name.getText();
206215
Helper<Object> helper = handlebars.helper(varName);
207-
if (helper == null && (params.size() > 0 || hash.size() > 0)) {
216+
if (helper == null
217+
&& ((params.size() > 0 || hash.size() > 0) || varType == TagType.SUB_EXPRESSION)) {
208218
Helper<Object> helperMissing =
209219
handlebars.helper(HelperRegistry.HELPER_MISSING);
210220
if (helperMissing == null) {
@@ -258,6 +268,13 @@ public Object visitBoolParam(final BoolParamContext ctx) {
258268
return Boolean.valueOf(ctx.getText());
259269
}
260270

271+
@Override
272+
public Object visitSubexpression(final SubexpressionContext ctx) {
273+
SexprContext sexpr = ctx.sexpr();
274+
return newVar(sexpr.QID().getSymbol(), TagType.SUB_EXPRESSION, params(sexpr.param()),
275+
hash(sexpr.hash()), ctx.start.getText(), ctx.stop.getText());
276+
}
277+
261278
@Override
262279
public Object visitBoolHash(final BoolHashContext ctx) {
263280
return Boolean.valueOf(ctx.getText());

handlebars/src/main/java/com/github/jknack/handlebars/internal/Variable.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -190,6 +190,7 @@ protected void collect(final Collection<String> result, final TagType tagType) {
190190
if (this.type == tagType) {
191191
result.add(name);
192192
}
193+
super.collect(result, tagType);
193194
}
194195

195196
/**

handlebars/src/test/java/com/github/jknack/handlebars/TagTypeTest.java

Lines changed: 24 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,11 @@ public class TagTypeTest extends AbstractTest {
1515
public CharSequence apply(final Object context, final Options options) throws IOException {
1616
return options.tagType.name();
1717
}
18+
}, "vowels", new Helper<Object>() {
19+
@Override
20+
public CharSequence apply(final Object context, final Options options) throws IOException {
21+
return options.helperName;
22+
}
1823
});
1924

2025
@Test
@@ -44,6 +49,8 @@ public void inline() {
4449
assertTrue(TagType.AMP_VAR.inline());
4550

4651
assertTrue(TagType.TRIPLE_VAR.inline());
52+
53+
assertTrue(TagType.SUB_EXPRESSION.inline());
4754
}
4855

4956
@Test
@@ -57,6 +64,12 @@ public void collectVar() throws IOException {
5764
.collect(TagType.VAR));
5865
}
5966

67+
@Test
68+
public void collectSubexpression() throws IOException {
69+
assertEquals(Arrays.asList("tag"), compile("{{vowels (tag)}}", helpers)
70+
.collect(TagType.SUB_EXPRESSION));
71+
}
72+
6073
@Test
6174
public void collectAmpVar() throws IOException {
6275
assertEquals(Arrays.asList("b"), compile("{{#hello}}{{a}}{{&b}}{{z}}{{/hello}}{{k}}")
@@ -76,10 +89,18 @@ public void collectSection() throws IOException {
7689
.collect(TagType.SECTION));
7790
}
7891

92+
@Test
93+
public void collectSectionWithSubExpression() throws IOException {
94+
assertEquals(Arrays.asList("tag"), compile("{{#hello}}{{vowels (tag)}}{{/hello}}", helpers)
95+
.collect(TagType.SUB_EXPRESSION));
96+
}
97+
7998
@Test
8099
public void collectSectionAndVars() throws IOException {
81-
assertEquals(Arrays.asList("hello", "a", "b", "z", "k"),
82-
compile("{{#hello}}{{a}}{{&b}}{{z}}{{/hello}}{{k}}")
83-
.collect(TagType.SECTION, TagType.VAR, TagType.TRIPLE_VAR, TagType.AMP_VAR));
100+
assertEquals(
101+
Arrays.asList("hello", "a", "b", "z", "k", "vowels", "tag"),
102+
compile("{{#hello}}{{a}}{{&b}}{{z}}{{/hello}}{{k}}{{vowels (tag)}}", helpers)
103+
.collect(TagType.SECTION, TagType.VAR, TagType.TRIPLE_VAR, TagType.AMP_VAR,
104+
TagType.SUB_EXPRESSION));
84105
}
85106
}

0 commit comments

Comments
 (0)