Skip to content

Commit

Permalink
v4: Inline Partial Definitions fix #429
Browse files Browse the repository at this point in the history
  • Loading branch information
jknack committed Nov 8, 2015
1 parent d1d37e7 commit b046834
Show file tree
Hide file tree
Showing 10 changed files with 230 additions and 83 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -170,6 +170,10 @@ UNLESS
: {startToken(start, "^")}? . -> pushMode(VAR)
;

START_DECORATOR
: {startToken(start, "#*")}? . -> pushMode(VAR)
;

START_BLOCK
: {startToken(start, "#")}? . -> pushMode(VAR)
;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ newline

block
:
START_BLOCK sexpr blockParams? END
startToken = (START_BLOCK|START_DECORATOR) sexpr blockParams? END
thenBody=body
elseBlock*
END_BLOCK nameEnd=QID END
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -320,6 +320,11 @@ public Context build() {
*/
public static final String PARTIALS = Context.class.getName() + "#partials";

/**
* Inline partials.
*/
public static final String INLINE_PARTIALS = "__inline_partials_";

/**
* The qualified name for partials. Internal use.
*/
Expand Down Expand Up @@ -378,6 +383,7 @@ private static Context root(final Object model) {
root.extendedContext = new Context(new HashMap<String, Object>());
root.data = new HashMap<String, Object>();
root.data.put(PARTIALS, new HashMap<String, Template>());
root.data.put(INLINE_PARTIALS, new HashMap<String, Template>());
root.data.put(INVOCATION_STACK, new LinkedList<TemplateSource>());
root.data.put("root", model);
return root;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -222,6 +222,7 @@ private static void registerBuiltinsHelpers(final HelperRegistry registry) {
registry.registerHelper("i18n", I18nHelper.i18n);
registry.registerHelper("i18nJs", I18nHelper.i18nJs);
registry.registerHelper(LookupHelper.NAME, LookupHelper.INSTANCE);
registry.registerHelper("inline", InlineHelper.INSTANCE);
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package com.github.jknack.handlebars.helper;

import java.io.IOException;
import java.util.Map;

import com.github.jknack.handlebars.Context;
import com.github.jknack.handlebars.Helper;
import com.github.jknack.handlebars.Options;

/**
* Inline partials are implemented via helpers (not decorators like in handlebars.js). Decorators
* are not supported in handlebars.java.
*
* <pre>
* {{#*inline \"myPartial\"}}success{{/inline}}{{> myPartial}}
* </pre>
*
* @author edgar
* @since 4.0.0
*/
public class InlineHelper implements Helper<String> {

/**
* A singleton instance of this helper.
*/
public static final Helper<String> INSTANCE = new InlineHelper();

@Override
public CharSequence apply(final String path, final Options options) throws IOException {
Map<String, Object> partials = options.data(Context.INLINE_PARTIALS);
partials.put(path, options.fn);
return null;
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
import java.io.Writer;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
Expand Down Expand Up @@ -100,6 +101,12 @@ class Block extends HelperResolver {
/** Block param names. */
private List<String> blockParams;

/** True, if this block has a decorator char: <code>*</code>. */
private String typeSuffix;

/** True, if this block is a decorator (it has an <code>*</code>). */
private boolean decorator;

/**
* Creates a new {@link Block}.
*
Expand All @@ -109,11 +116,11 @@ class Block extends HelperResolver {
* @param params The parameter list.
* @param hash The hash.
* @param blockParams The block param names.
* @param decorator True, if this block has a decorator char: <code>*</code>.
*/
public Block(final Handlebars handlebars, final String name,
final boolean inverted, final List<Object> params,
final Map<String, Object> hash,
final List<String> blockParams) {
final Map<String, Object> hash, final List<String> blockParams, final boolean decorator) {
super(handlebars);
this.name = notNull(name, "The name is required.");
this.inverted = inverted;
Expand All @@ -122,6 +129,8 @@ public Block(final Handlebars handlebars, final String name,
hash(hash);
this.blockParams = blockParams;
this.helper = helper(name);
this.decorator = decorator;
this.typeSuffix = decorator ? "*" : "";
}

@SuppressWarnings("unchecked")
Expand All @@ -130,55 +139,67 @@ protected void merge(final Context context, final Writer writer) throws IOExcept
if (body == null) {
return;
}
final String helperName;
Helper<Object> helper = this.helper;
Template template = body;
final Object it;
Context itCtx = context;
if (helper == null) {
it = Transformer.transform(context.get(name));
if (inverted) {
helperName = UnlessHelper.NAME;
} else if (it instanceof Iterable) {
helperName = EachHelper.NAME;
} else if (it instanceof Boolean) {
helperName = IfHelper.NAME;
} else if (it instanceof Lambda) {
helperName = WithHelper.NAME;
template = Lambdas
.compile(handlebars, (Lambda<Object, Object>) it, context, template,
startDelimiter, endDelimiter);
} else {
helperName = WithHelper.NAME;
itCtx = Context.newContext(context, it);
}
// A built-in helper might be override it.
helper = handlebars.helper(helperName);

if (it == null) {
Helper<Object> missing = helper(Handlebars.HELPER_MISSING);
if (missing != null) {
// use missing here
helper = missing;

Map<String, Object> partials = context.data(Context.INLINE_PARTIALS);
Map<String, Object> localPartials = new HashMap<>(partials);
context.data(Context.INLINE_PARTIALS, localPartials);
try {
final String helperName;
Helper<Object> helper = this.helper;
Template template = body;
final Object it;
Context itCtx = context;
if (helper == null) {
it = Transformer.transform(context.get(name));
if (inverted) {
helperName = UnlessHelper.NAME;
} else if (it instanceof Iterable) {
helperName = EachHelper.NAME;
} else if (it instanceof Boolean) {
helperName = IfHelper.NAME;
} else if (it instanceof Lambda) {
helperName = WithHelper.NAME;
template = Lambdas
.compile(handlebars, (Lambda<Object, Object>) it, context, template,
startDelimiter, endDelimiter);
} else {
helperName = WithHelper.NAME;
itCtx = Context.newContext(context, it);
}
// A built-in helper might be override it.
helper = handlebars.helper(helperName);

if (it == null) {
Helper<Object> missing = helper(Handlebars.HELPER_MISSING);
if (missing != null) {
// use missing here
helper = missing;
}
}
} else {
helperName = name;
it = Transformer.transform(determineContext(context));
}
} else {
helperName = name;
it = Transformer.transform(determineContext(context));
}

Options options = new Options.Builder(handlebars, helperName, TagType.SECTION, itCtx, template)
.setInverse(inverse)
.setParams(params(itCtx))
.setHash(hash(itCtx))
.setBlockParams(blockParams)
.setWriter(writer)
.build();
options.data(Context.PARAM_SIZE, this.params.size());

CharSequence result = helper.apply(it, options);
if (result != null) {
writer.append(result);
Options options = new Options.Builder(handlebars, helperName, TagType.SECTION, itCtx,
template)
.setInverse(inverse)
.setParams(params(itCtx))
.setHash(hash(itCtx))
.setBlockParams(blockParams)
.setWriter(writer)
.build();
options.data(Context.PARAM_SIZE, this.params.size());

CharSequence result = helper.apply(it, options);
if (result != null) {
writer.append(result);
}
} finally {
if (!decorator) {
context.data(Context.INLINE_PARTIALS, partials);
localPartials.clear();
}
}
}

Expand Down Expand Up @@ -280,7 +301,7 @@ public String text() {
*/
private String text(final boolean complete) {
StringBuilder buffer = new StringBuilder();
buffer.append(startDelimiter).append(type).append(name);
buffer.append(startDelimiter).append(type).append(typeSuffix).append(name);
String params = paramsToString(this.params);
if (params.length() > 0) {
buffer.append(" ").append(params);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -100,36 +100,42 @@ protected void merge(final Context context, final Writer writer)

String path = this.path.apply(context);

TemplateSource source = loader.sourceAt(path);

if (exists(invocationStack, source.filename())) {
TemplateSource caller = invocationStack.removeLast();
Collections.reverse(invocationStack);

final String message;
final String reason;
if (invocationStack.isEmpty()) {
reason = String.format("infinite loop detected, partial '%s' is calling itself",
source.filename());

message = String.format("%s:%s:%s: %s", caller.filename(), line, column, reason);
} else {
reason = String.format(
"infinite loop detected, partial '%s' was previously loaded", source.filename());
/** Inline partial? */
Map<String, Template> inlineTemplates = context.data(Context.INLINE_PARTIALS);
Template template = inlineTemplates.get(path);

if (template == null) {
TemplateSource source = loader.sourceAt(path);

if (exists(invocationStack, source.filename())) {
TemplateSource caller = invocationStack.removeLast();
Collections.reverse(invocationStack);

final String message;
final String reason;
if (invocationStack.isEmpty()) {
reason = String.format("infinite loop detected, partial '%s' is calling itself",
source.filename());

message = String.format("%s:%s:%s: %s", caller.filename(), line, column, reason);
} else {
reason = String.format(
"infinite loop detected, partial '%s' was previously loaded", source.filename());

message = String.format("%s:%s:%s: %s\n%s", caller.filename(), line, column, reason,
"at " + join(invocationStack, "\nat "));
}
HandlebarsError error = new HandlebarsError(caller.filename(), line,
column, reason, text(), message);
throw new HandlebarsException(error);
}

message = String.format("%s:%s:%s: %s\n%s", caller.filename(), line, column, reason,
"at " + join(invocationStack, "\nat "));
if (indent != null) {
source = partial(source, indent);
}
HandlebarsError error = new HandlebarsError(caller.filename(), line,
column, reason, text(), message);
throw new HandlebarsException(error);
}

if (indent != null) {
source = partial(source, indent);
template = handlebars.compile(source);
}

Template template = handlebars.compile(source);
String key = isEmpty(this.context) ? "this" : this.context;
hash = hash(context);
ctx = Context.newContext(context, context.get(key)).data(hash);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,7 @@ public Template visit(final ParseTree tree) {
@Override
public Template visitBlock(final BlockContext ctx) {
SexprContext sexpr = ctx.sexpr();
boolean decorator = ctx.start.getText().endsWith("#*");
Token nameStart = sexpr.QID().getSymbol();
String name = nameStart.getText();
qualifier.addLast(name);
Expand All @@ -158,11 +159,12 @@ public Template visitBlock(final BlockContext ctx) {

hasTag(true);
Block block = new Block(handlebars, name, false, params(sexpr.param()),
hash(sexpr.hash()), blockParams(ctx.blockParams()));
hash(sexpr.hash()), blockParams(ctx.blockParams()), decorator);
block.filename(source.filename());
block.position(nameStart.getLine(), nameStart.getCharPositionInLine());
int to = decorator ? 2 : 1;
String startDelim = ctx.start.getText();
startDelim = startDelim.substring(0, startDelim.length() - 1);
startDelim = startDelim.substring(0, startDelim.length() - to);
block.startDelimiter(startDelim);
block.endDelimiter(ctx.stop.getText());

Expand Down Expand Up @@ -191,7 +193,7 @@ public Template visitBlock(final BlockContext ctx) {
Token elsenameStart = elseexpr.QID().getSymbol();
String elsename = elsenameStart.getText();
Block elseblock = new Block(handlebars, elsename, false, params(elseexpr.param()),
hash(elseexpr.hash()), blockParams(elseStmtChain.blockParams()));
hash(elseexpr.hash()), blockParams(elseStmtChain.blockParams()), false);
elseblock.filename(source.filename());
elseblock.position(elsenameStart.getLine(), elsenameStart.getCharPositionInLine());
elseblock.startDelimiter(startDelim);
Expand Down Expand Up @@ -225,7 +227,7 @@ public Template visitUnless(final UnlessContext ctx) {
String.format("found: '%s', expected: '%s'", nameEnd, name));
}
Block block = new Block(handlebars, name, true, Collections.emptyList(),
Collections.<String, Object> emptyMap(), blockParams(ctx.blockParams()));
Collections.<String, Object> emptyMap(), blockParams(ctx.blockParams()), false);
block.filename(source.filename());
block.position(nameStart.getLine(), nameStart.getCharPositionInLine());
String startDelim = ctx.start.getText();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@
import java.io.IOException;
import java.util.Arrays;

import org.junit.Ignore;
import org.junit.Test;

public class BlockParamsTest extends AbstractTest {
Expand Down Expand Up @@ -88,7 +87,6 @@ public CharSequence apply(final Object context, final Options options)
}

@Test
@Ignore
public void shouldAllowBlockParamsOnChainedHelpers() throws IOException {
shouldCompileTo(
"{{#if bar}}{{else goodbyes as |value|}}{{value}}{{/if}}{{value}}",
Expand All @@ -104,7 +102,7 @@ public CharSequence apply(final Object context, final Options options)
}
return options.fn($("value", "bar"));
}
}), "13foo");
}), "1foo");
}

@Test
Expand Down
Loading

0 comments on commit b046834

Please sign in to comment.