Skip to content

Commit 81df990

Browse files
committed
dynamic partials fix #375
+ dynamic partials + lookup helper
1 parent a3ca078 commit 81df990

File tree

10 files changed

+323
-47
lines changed

10 files changed

+323
-47
lines changed

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

+12-20
Original file line numberDiff line numberDiff line change
@@ -179,7 +179,7 @@ START_DELIM
179179
;
180180

181181
START_PARTIAL
182-
: {startToken(start, ">")}? . -> pushMode(PARTIAL)
182+
: {startToken(start, ">")}? . -> pushMode(VAR)
183183
;
184184

185185
END_BLOCK
@@ -213,25 +213,6 @@ WS_DELIM
213213

214214
DELIM: .;
215215

216-
mode PARTIAL;
217-
218-
PATH
219-
:
220-
(
221-
'[' PATH_SEGMENT ']'
222-
| PATH_SEGMENT
223-
) -> mode(VAR)
224-
;
225-
226-
fragment
227-
PATH_SEGMENT
228-
: [a-zA-Z0-9_$'/.:\-]+
229-
;
230-
231-
WS_PATH
232-
: [ \t\r\n] -> skip
233-
;
234-
235216
mode VAR;
236217

237218
END_T
@@ -283,6 +264,17 @@ QID
283264
| ID
284265
;
285266

267+
PATH
268+
:
269+
'[' PATH_SEGMENT ']'
270+
| PATH_SEGMENT
271+
;
272+
273+
fragment
274+
PATH_SEGMENT
275+
: [a-zA-Z0-9_$'/.:\-]+
276+
;
277+
286278
fragment
287279
ID_SEPARATOR
288280
: ('.'|'/'|'-');

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

+7-1
Original file line numberDiff line numberDiff line change
@@ -132,7 +132,13 @@ delimiters
132132

133133
partial
134134
:
135-
START_PARTIAL PATH QID? hash* END
135+
START_PARTIAL pexpr END
136+
;
137+
138+
pexpr
139+
:
140+
LP sexpr RP hash* #dynamicPath
141+
| path = (QID|PATH) QID? hash* #staticPath
136142
;
137143

138144
param

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

+1-1
Original file line numberDiff line numberDiff line change
@@ -1093,7 +1093,7 @@ public String handlebarsJsFile() {
10931093
/**
10941094
* Return a parser factory.
10951095
*
1096-
* @return A parsert factory.
1096+
* @return A parser factory.
10971097
*/
10981098
public ParserFactory getParserFactory() {
10991099
return parserFactory;

handlebars/src/main/java/com/github/jknack/handlebars/helper/DefaultHelperRegistry.java

+1
Original file line numberDiff line numberDiff line change
@@ -221,6 +221,7 @@ private static void registerBuiltinsHelpers(final HelperRegistry registry) {
221221
registry.registerHelper(PrecompileHelper.NAME, PrecompileHelper.INSTANCE);
222222
registry.registerHelper("i18n", I18nHelper.i18n);
223223
registry.registerHelper("i18nJs", I18nHelper.i18nJs);
224+
registry.registerHelper(LookupHelper.NAME, LookupHelper.INSTANCE);
224225
}
225226

226227
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
/**
2+
* Copyright (c) 2012-2013 Edgar Espina
3+
*
4+
* This file is part of Handlebars.java.
5+
*
6+
* Licensed under the Apache License, Version 2.0 (the "License");
7+
* you may not use this file except in compliance with the License.
8+
* You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing, software
13+
* distributed under the License is distributed on an "AS IS" BASIS,
14+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15+
* See the License for the specific language governing permissions and
16+
* limitations under the License.
17+
*/
18+
package com.github.jknack.handlebars.helper;
19+
20+
import java.io.IOException;
21+
22+
import com.github.jknack.handlebars.Context;
23+
import com.github.jknack.handlebars.Helper;
24+
import com.github.jknack.handlebars.Options;
25+
26+
/**
27+
* Lookup helper, which allows to get a context variable... It is kind of useless, but it it present
28+
* to keep better integration with handlebars.js.
29+
*
30+
* It was introduced with dynamic partials:
31+
*
32+
* <pre>
33+
* {{> (lookup '.' 'myVariable') }}
34+
* </pre>
35+
*
36+
* This helper is useless bc it shouldn't be required to get a variable via a helper, like:
37+
*
38+
* <pre>
39+
* {{> (myVariable) }}
40+
* </pre>
41+
*
42+
* For now, previous expression isn't supported in Handlebars.java... but the only reason of that is
43+
* handlebars.js
44+
*
45+
* @author edgar
46+
* @since 2.2.0
47+
*/
48+
public class LookupHelper implements Helper<Object> {
49+
50+
/**
51+
* A singleton instance of this helper.
52+
*/
53+
public static final Helper<Object> INSTANCE = new LookupHelper();
54+
55+
/**
56+
* The helper's name.
57+
*/
58+
public static final String NAME = "lookup";
59+
60+
@Override
61+
public CharSequence apply(final Object context, final Options options)
62+
throws IOException {
63+
if (options.params.length <= 0) {
64+
return context.toString();
65+
}
66+
Context ctx = options.context;
67+
while (ctx != null && context != ctx.model()) {
68+
ctx = ctx.parent();
69+
}
70+
if (ctx == null) {
71+
return null;
72+
}
73+
Object lookup = ctx.get(options.param(0).toString());
74+
if (lookup == null) {
75+
return null;
76+
}
77+
return lookup.toString();
78+
}
79+
80+
}

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

+8-6
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@
1919

2020
import static org.apache.commons.lang3.StringUtils.isEmpty;
2121
import static org.apache.commons.lang3.StringUtils.join;
22-
import static org.apache.commons.lang3.Validate.notEmpty;
22+
import static org.apache.commons.lang3.Validate.notNull;
2323

2424
import java.io.IOException;
2525
import java.io.Writer;
@@ -50,7 +50,7 @@ class Partial extends HelperResolver {
5050
/**
5151
* The partial path.
5252
*/
53-
private String path;
53+
private Template path;
5454

5555
/**
5656
* A partial context. Optional.
@@ -80,10 +80,10 @@ class Partial extends HelperResolver {
8080
* @param context The template context.
8181
* @param hash Template params
8282
*/
83-
public Partial(final Handlebars handlebars, final String path, final String context,
83+
public Partial(final Handlebars handlebars, final Template path, final String context,
8484
final Map<String, Object> hash) {
8585
super(handlebars);
86-
this.path = notEmpty(path, "The path is required.");
86+
this.path = notNull(path, "The path is required.");
8787
this.context = context;
8888
this.hash(hash);
8989
}
@@ -95,6 +95,8 @@ public void merge(final Context context, final Writer writer)
9595
try {
9696
LinkedList<TemplateSource> invocationStack = context.data(Context.INVOCATION_STACK);
9797

98+
String path = this.path.apply(context);
99+
98100
TemplateSource source = loader.sourceAt(path);
99101

100102
if (exists(invocationStack, source.filename())) {
@@ -129,7 +131,7 @@ public void merge(final Context context, final Writer writer)
129131
template.apply(Context.newContext(context, context.get(key)).data(hash(context)), writer);
130132
} catch (IOException ex) {
131133
String reason = String.format("The partial '%s' could not be found",
132-
loader.resolve(path));
134+
loader.resolve(path.text()));
133135
String message = String.format("%s:%s:%s: %s", filename, line, column, reason);
134136
HandlebarsError error = new HandlebarsError(filename, line,
135137
column, reason, text(), message);
@@ -222,7 +224,7 @@ private String partialInput(final String input, final String indent) {
222224
public String text() {
223225
StringBuilder buffer = new StringBuilder(startDelimiter)
224226
.append('>')
225-
.append(path);
227+
.append(path.text());
226228

227229
if (context != null) {
228230
buffer.append(' ').append(context);

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

+66-14
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@
4848
import com.github.jknack.handlebars.internal.HbsParser.BoolParamContext;
4949
import com.github.jknack.handlebars.internal.HbsParser.CharParamContext;
5050
import com.github.jknack.handlebars.internal.HbsParser.CommentContext;
51+
import com.github.jknack.handlebars.internal.HbsParser.DynamicPathContext;
5152
import com.github.jknack.handlebars.internal.HbsParser.ElseBlockContext;
5253
import com.github.jknack.handlebars.internal.HbsParser.EscapeContext;
5354
import com.github.jknack.handlebars.internal.HbsParser.HashContext;
@@ -59,6 +60,7 @@
5960
import com.github.jknack.handlebars.internal.HbsParser.SexprContext;
6061
import com.github.jknack.handlebars.internal.HbsParser.SpacesContext;
6162
import com.github.jknack.handlebars.internal.HbsParser.StatementContext;
63+
import com.github.jknack.handlebars.internal.HbsParser.StaticPathContext;
6264
import com.github.jknack.handlebars.internal.HbsParser.StringParamContext;
6365
import com.github.jknack.handlebars.internal.HbsParser.SubParamExprContext;
6466
import com.github.jknack.handlebars.internal.HbsParser.TemplateContext;
@@ -76,6 +78,28 @@
7678
*/
7779
abstract class TemplateBuilder extends HbsParserBaseVisitor<Object> {
7880

81+
/**
82+
* Get partial info: static vs dynamic.
83+
*
84+
* @author edgar
85+
* @since 2.2.0
86+
*/
87+
private static class PartialInfo {
88+
89+
/** Token to report errors. */
90+
private Token token;
91+
92+
/** Partial params. */
93+
private Map<String, Object> hash;
94+
95+
/** Partial path: static vs subexpression. */
96+
private Template path;
97+
98+
/** Template context. */
99+
private String context;
100+
101+
}
102+
79103
/**
80104
* A handlebars object. required.
81105
*/
@@ -384,16 +408,6 @@ protected void afterApply(final Context context) {
384408
@Override
385409
public Template visitPartial(final PartialContext ctx) {
386410
hasTag(true);
387-
Token pathToken = ctx.PATH().getSymbol();
388-
String uri = pathToken.getText();
389-
if (uri.startsWith("[") && uri.endsWith("]")) {
390-
uri = uri.substring(1, uri.length() - 1);
391-
}
392-
393-
if (uri.startsWith("/")) {
394-
String message = "found: '/', partial shouldn't start with '/'";
395-
reportError(null, pathToken.getLine(), pathToken.getCharPositionInLine(), message);
396-
}
397411

398412
String indent = line.toString();
399413
if (hasTag()) {
@@ -404,16 +418,54 @@ public Template visitPartial(final PartialContext ctx) {
404418
indent = null;
405419
}
406420

407-
TerminalNode partialContext = ctx.QID();
421+
PartialInfo info = (PartialInfo) super.visit(ctx.pexpr());
422+
408423
String startDelim = ctx.start.getText();
409-
Template partial = new Partial(handlebars, uri,
410-
partialContext != null ? partialContext.getText() : null, hash(ctx.hash()))
424+
Template partial = new Partial(handlebars, info.path, info.context, info.hash)
411425
.startDelimiter(startDelim.substring(0, startDelim.length() - 1))
412426
.endDelimiter(ctx.stop.getText())
413427
.indent(indent)
414428
.filename(source.filename())
415-
.position(pathToken.getLine(), pathToken.getCharPositionInLine());
429+
.position(info.token.getLine(), info.token.getCharPositionInLine());
430+
431+
return partial;
432+
}
433+
434+
@Override
435+
public PartialInfo visitStaticPath(final StaticPathContext ctx) {
436+
Token pathToken = ctx.path;
437+
String uri = pathToken.getText();
438+
if (uri.startsWith("[") && uri.endsWith("]")) {
439+
uri = uri.substring(1, uri.length() - 1);
440+
}
441+
442+
if (uri.startsWith("/")) {
443+
String message = "found: '/', partial shouldn't start with '/'";
444+
reportError(null, pathToken.getLine(), pathToken.getCharPositionInLine(), message);
445+
}
446+
447+
TerminalNode partialContext = ctx.QID(1);
448+
449+
PartialInfo partial = new PartialInfo();
450+
partial.token = pathToken;
451+
partial.path = new Text(handlebars, uri);
452+
partial.hash = hash(ctx.hash());
453+
partial.context = partialContext != null ? partialContext.getText() : null;
454+
return partial;
455+
}
456+
457+
@Override
458+
public PartialInfo visitDynamicPath(final DynamicPathContext ctx) {
459+
SexprContext sexpr = ctx.sexpr();
460+
TerminalNode qid = sexpr.QID();
461+
Template expression = newVar(qid.getSymbol(), TagType.SUB_EXPRESSION, params(sexpr.param()),
462+
hash(sexpr.hash()), ctx.start.getText(), ctx.stop.getText());
416463

464+
PartialInfo partial = new PartialInfo();
465+
partial.path = expression;
466+
partial.hash = hash(ctx.hash());
467+
partial.context = null;
468+
partial.token = qid.getSymbol();
417469
return partial;
418470
}
419471

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

-5
Original file line numberDiff line numberDiff line change
@@ -17,9 +17,6 @@
1717
*/
1818
package com.github.jknack.handlebars;
1919

20-
import static org.apache.commons.lang3.Validate.notEmpty;
21-
import static org.apache.commons.lang3.Validate.notNull;
22-
2320
import java.io.FileNotFoundException;
2421
import java.net.URL;
2522
import java.util.HashMap;
@@ -54,8 +51,6 @@ public MapTemplateLoader define(final String name, final String content) {
5451

5552
@Override
5653
public TemplateSource sourceAt(final String uri) throws FileNotFoundException {
57-
notNull(uri, "The uri is required.");
58-
notEmpty(uri.toString(), "The uri is required.");
5954
String location = resolve(normalize(uri));
6055
String text = map.get(location);
6156
if (text == null) {

0 commit comments

Comments
 (0)