Skip to content

Commit 3c59c70

Browse files
committed
v4: Decorators fixes #442
1 parent 0c14086 commit 3c59c70

File tree

24 files changed

+997
-135
lines changed

24 files changed

+997
-135
lines changed

handlebars-springmvc/src/main/java/com/github/jknack/handlebars/springmvc/HandlebarsViewResolver.java

+11
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@
4141
import org.springframework.web.servlet.view.AbstractTemplateViewResolver;
4242
import org.springframework.web.servlet.view.AbstractUrlBasedView;
4343

44+
import com.github.jknack.handlebars.Decorator;
4445
import com.github.jknack.handlebars.Formatter;
4546
import com.github.jknack.handlebars.Handlebars;
4647
import com.github.jknack.handlebars.Helper;
@@ -558,4 +559,14 @@ public void setTemplateCache(final TemplateCache templateCache) {
558559
this.templateCache = templateCache;
559560
}
560561

562+
@Override
563+
public Decorator decorator(final String name) {
564+
return this.registry.decorator(name);
565+
}
566+
567+
@Override
568+
public HandlebarsViewResolver registerDecorator(final String name, final Decorator decorator) {
569+
registry.registerDecorator(name, decorator);
570+
return this;
571+
}
561572
}

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

+13-6
Original file line numberDiff line numberDiff line change
@@ -170,10 +170,6 @@ UNLESS
170170
: {startToken(start, "^")}? . -> pushMode(VAR)
171171
;
172172

173-
START_DECORATOR
174-
: {startToken(start, "#*")}? . -> pushMode(VAR)
175-
;
176-
177173
START_PARTIAL_BLOCK
178174
: {startToken(start, "#>")}? . -> pushMode(VAR)
179175
;
@@ -231,9 +227,20 @@ END
231227
: {endToken(end)}? . -> mode(DEFAULT_MODE)
232228
;
233229

234-
AS: 'as';
230+
DECORATOR
231+
:
232+
'*'
233+
;
235234

236-
PIPE: '|';
235+
AS
236+
:
237+
'as'
238+
;
239+
240+
PIPE
241+
:
242+
'|'
243+
;
237244

238245
DOUBLE_STRING
239246
:

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

+2-2
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,7 @@ newline
8080

8181
block
8282
:
83-
startToken = (START_BLOCK|START_DECORATOR) sexpr blockParams? END
83+
startToken = START_BLOCK DECORATOR? sexpr blockParams? END
8484
thenBody=body
8585
elseBlock*
8686
END_BLOCK nameEnd=QID END
@@ -131,7 +131,7 @@ ampvar
131131

132132
var
133133
:
134-
START sexpr END
134+
START DECORATOR? sexpr END
135135
;
136136

137137
delimiters

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

+17-1
Original file line numberDiff line numberDiff line change
@@ -384,7 +384,9 @@ private static Context root(final Object model) {
384384
root.extendedContext = new Context(new HashMap<String, Object>());
385385
root.data = new HashMap<String, Object>();
386386
root.data.put(PARTIALS, new HashMap<String, Template>());
387-
root.data.put(INLINE_PARTIALS, new HashMap<String, Template>());
387+
LinkedList<Map<String, Template>> partials = new LinkedList<>();
388+
partials.push(new HashMap<String, Template>());
389+
root.data.put(INLINE_PARTIALS, partials);
388390
root.data.put(INVOCATION_STACK, new LinkedList<TemplateSource>());
389391
root.data.put("root", model);
390392
return root;
@@ -802,4 +804,18 @@ protected Context newChildContext(final Object model) {
802804
return new Context(model);
803805
}
804806

807+
/**
808+
* Creates a new context but keep the <code>data</code> attribute.
809+
*
810+
* @param context Context to extract the <code>data</code> attribute.
811+
* @param model A model/data.
812+
* @return A new context.
813+
*/
814+
public static Context copy(final Context context, final Object model) {
815+
Context ctx = Context.newContext(model);
816+
ctx.data = context.data;
817+
ctx.resolver = context.resolver;
818+
return ctx;
819+
}
820+
805821
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
package com.github.jknack.handlebars;
2+
3+
import java.io.IOException;
4+
5+
/**
6+
* A decorator allows a declarative means to both annotate particular blocks with metadata as
7+
* well as to wrap in behaviors when desired, prior to execution.
8+
*
9+
* @author edgar
10+
* @since 4.0.0
11+
*/
12+
public interface Decorator {
13+
14+
/**
15+
* Decorate a template with metadata.
16+
*
17+
* @param fn Decorated template.
18+
* @param options Options object.
19+
* @throws IOException If something goes wrong.
20+
*/
21+
void apply(Template fn, Options options) throws IOException;
22+
23+
}

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

+11
Original file line numberDiff line numberDiff line change
@@ -1192,4 +1192,15 @@ public static void error(final String message) {
11921192
logger.error(message);
11931193
}
11941194

1195+
@Override
1196+
public Decorator decorator(final String name) {
1197+
return registry.decorator(name);
1198+
}
1199+
1200+
@Override
1201+
public Handlebars registerDecorator(final String name, final Decorator decorator) {
1202+
registry.registerDecorator(name, decorator);
1203+
return this;
1204+
}
1205+
11951206
}

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

+23-2
Original file line numberDiff line numberDiff line change
@@ -136,7 +136,9 @@ public interface HelperRegistry {
136136
* </ul>
137137
*
138138
* Only static methods will be registered as helpers.
139-
* <p>Enums are supported too</p>
139+
* <p>
140+
* Enums are supported too
141+
* </p>
140142
*
141143
* @param helperSource The helper source. Enums are supported. Required.
142144
* @return This handlebars object.
@@ -297,9 +299,28 @@ HelperRegistry registerHelpers(String filename, InputStream source)
297299
*
298300
* @param filename The file name (just for debugging purpose). Required.
299301
* @param source The JavaScript source. Required.
300-
* @return This handlebars object.
302+
* @return This registry.
301303
* @throws Exception If the JavaScript helpers can't be registered.
302304
*/
303305
HelperRegistry registerHelpers(String filename, String source) throws Exception;
304306

307+
/**
308+
* Find a decorator by name.
309+
*
310+
* @param name A decorator's name.
311+
* @return A decorator or <code>null</code>.
312+
* @since 4.0.0
313+
*/
314+
Decorator decorator(String name);
315+
316+
/**
317+
* Register a decorator and make it accessible via {@link #decorator(String)}.
318+
*
319+
* @param name A decorator's name. Required.
320+
* @param decorator A decorator. Required.
321+
* @return This registry.
322+
* @since 4.0.0
323+
*/
324+
HelperRegistry registerDecorator(String name, Decorator decorator);
325+
305326
}

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

+15
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,11 @@ public enum TagType {
3131
*/
3232
VAR,
3333

34+
/**
35+
* A var decorator tag, like: <code>{{*name}}</code>.
36+
*/
37+
STAR_VAR,
38+
3439
/**
3540
* All variables are HTML escaped by default. If you want to return unescaped HTML, use the
3641
* triple mustache: <code>{{{@literal &}name}}</code>.
@@ -65,6 +70,16 @@ public enum TagType {
6570
public boolean inline() {
6671
return false;
6772
}
73+
},
74+
75+
/**
76+
* Like {{#* decorator}}success{{/decorator}}.
77+
*/
78+
START_SECTION {
79+
@Override
80+
public boolean inline() {
81+
return false;
82+
}
6883
};
6984

7085
/**

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

+27-1
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,14 @@ public void apply(final Object context, final Writer writer)
5959
throws IOException {
6060
}
6161

62+
@Override
63+
public void before(final Context context, final Writer writer) throws IOException {
64+
}
65+
66+
@Override
67+
public void after(final Context context, final Writer writer) throws IOException {
68+
}
69+
6270
@Override
6371
public String toJavaScript() {
6472
return "";
@@ -71,7 +79,7 @@ public String filename() {
7179

7280
@Override
7381
public int[] position() {
74-
return new int[] {0, 0};
82+
return new int[]{0, 0 };
7583
}
7684

7785
@SuppressWarnings({"rawtypes", "unchecked" })
@@ -135,6 +143,24 @@ public List<String> collectReferenceParameters() {
135143
*/
136144
void apply(Context context, Writer writer) throws IOException;
137145

146+
/**
147+
* Notify that template is going to be processed.
148+
*
149+
* @param context The context object. Required.
150+
* @param writer The writer object. Required.
151+
* @throws IOException If a resource cannot be loaded.
152+
*/
153+
void before(Context context, Writer writer) throws IOException;
154+
155+
/**
156+
* Notify that template has been processed.
157+
*
158+
* @param context The context object. Required.
159+
* @param writer The writer object. Required.
160+
* @throws IOException If a resource cannot be loaded.
161+
*/
162+
void after(Context context, Writer writer) throws IOException;
163+
138164
/**
139165
* Merge the template tree using the given context.
140166
*

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

+26-3
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@
4747
import java.util.Map.Entry;
4848
import java.util.Set;
4949

50+
import com.github.jknack.handlebars.Decorator;
5051
import com.github.jknack.handlebars.Handlebars;
5152
import com.github.jknack.handlebars.Helper;
5253
import com.github.jknack.handlebars.HelperRegistry;
@@ -64,8 +65,10 @@ public class DefaultHelperRegistry implements HelperRegistry {
6465
/**
6566
* The helper registry.
6667
*/
67-
private final Map<String, Helper<?>> helpers =
68-
new HashMap<String, Helper<?>>();
68+
private final Map<String, Helper<?>> helpers = new HashMap<String, Helper<?>>();
69+
70+
/** Decorators. */
71+
private final Map<String, Decorator> decorators = new HashMap<>();
6972

7073
/**
7174
* A Handlebars.js implementation.
@@ -222,7 +225,27 @@ private static void registerBuiltinsHelpers(final HelperRegistry registry) {
222225
registry.registerHelper("i18n", I18nHelper.i18n);
223226
registry.registerHelper("i18nJs", I18nHelper.i18nJs);
224227
registry.registerHelper(LookupHelper.NAME, LookupHelper.INSTANCE);
225-
registry.registerHelper("inline", InlineHelper.INSTANCE);
228+
229+
// decorator
230+
registry.registerDecorator("inline", InlineDecorator.INSTANCE);
231+
}
232+
233+
@Override
234+
public Decorator decorator(final String name) {
235+
notEmpty(name, "A decorator's name is required.");
236+
return decorators.get(name);
237+
}
238+
239+
@Override
240+
public HelperRegistry registerDecorator(final String name, final Decorator decorator) {
241+
notEmpty(name, "A decorator's name is required.");
242+
notNull(decorator, "A decorator is required.");
243+
244+
Decorator old = decorators.put(name, decorator);
245+
if (old != null) {
246+
Handlebars.warn("Decorator '%s' has been replaced by '%s'", name, decorator);
247+
}
248+
return this;
226249
}
227250

228251
}
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,16 @@
11
package com.github.jknack.handlebars.helper;
22

33
import java.io.IOException;
4+
import java.util.Deque;
45
import java.util.Map;
56

67
import com.github.jknack.handlebars.Context;
7-
import com.github.jknack.handlebars.Helper;
8+
import com.github.jknack.handlebars.Decorator;
89
import com.github.jknack.handlebars.Options;
10+
import com.github.jknack.handlebars.Template;
911

1012
/**
11-
* Inline partials are implemented via helpers (not decorators like in handlebars.js). Decorators
12-
* are not supported in handlebars.java.
13+
* Inline partials via {@link Decorator} API.
1314
*
1415
* <pre>
1516
* {{#*inline \"myPartial\"}}success{{/inline}}{{> myPartial}}
@@ -18,18 +19,17 @@
1819
* @author edgar
1920
* @since 4.0.0
2021
*/
21-
public class InlineHelper implements Helper<String> {
22+
public class InlineDecorator implements Decorator {
2223

2324
/**
2425
* A singleton instance of this helper.
2526
*/
26-
public static final Helper<String> INSTANCE = new InlineHelper();
27+
public static final Decorator INSTANCE = new InlineDecorator();
2728

2829
@Override
29-
public CharSequence apply(final String path, final Options options) throws IOException {
30-
Map<String, Object> partials = options.data(Context.INLINE_PARTIALS);
31-
partials.put(path, options.fn);
32-
return null;
30+
public void apply(final Template fn, final Options options) throws IOException {
31+
Deque<Map<String, Template>> partials = options.data(Context.INLINE_PARTIALS);
32+
partials.getLast().put((String) options.param(0), options.fn);
3333
}
3434

3535
}

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

+11
Original file line numberDiff line numberDiff line change
@@ -121,6 +121,7 @@ public String apply(final Context context) throws IOException {
121121
public void apply(final Context context, final Writer writer)
122122
throws IOException {
123123
try {
124+
before(context, writer);
124125
merge(context, writer);
125126
} catch (HandlebarsException ex) {
126127
throw ex;
@@ -136,6 +137,8 @@ public void apply(final Context context, final Writer writer)
136137
// Override the stack-trace
137138
hex.setStackTrace(ex.getStackTrace());
138139
throw hex;
140+
} finally {
141+
after(context, writer);
139142
}
140143
}
141144

@@ -152,6 +155,14 @@ private static Context wrap(final Object candidate) {
152155
return Context.newContext(candidate);
153156
}
154157

158+
@Override
159+
public void before(final Context context, final Writer writer) throws IOException {
160+
}
161+
162+
@Override
163+
public void after(final Context context, final Writer writer) throws IOException {
164+
}
165+
155166
/**
156167
* Merge a child template into the writer.
157168
*

0 commit comments

Comments
 (0)