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

Implement parser for else chaining of helpers #892

Merged
merged 1 commit into from
Nov 8, 2014
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
33 changes: 26 additions & 7 deletions lib/handlebars/compiler/helpers.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,29 @@ export function stripFlags(open, close) {

export function prepareBlock(mustache, program, inverseAndProgram, close, inverted, locInfo) {
/*jshint -W040 */
if (mustache.sexpr.id.original !== close.path.original) {
// When we are chaining inverse calls, we will not have a close path
if (close && close.path && (mustache.sexpr.id.original !== close.path.original)) {
throw new Exception(mustache.sexpr.id.original + ' doesn\'t match ' + close.path.original, mustache);
}

var inverse = inverseAndProgram && inverseAndProgram.program;
// Safely handle a chained inverse that does not have a non-conditional inverse
// (i.e. both inverseAndProgram AND close are undefined)
if (!close) {
close = {strip: {}};
}

// Find the inverse program that is involed with whitespace stripping.
var inverse = inverseAndProgram && inverseAndProgram.program,
firstInverse = inverse,
lastInverse = inverse;
if (inverse && inverse.inverse) {
firstInverse = inverse.statements[0].program;

// Walk the inverse chain to find the last inverse that is actually in the chain.
while (lastInverse.inverse) {
lastInverse = lastInverse.statements[lastInverse.statements.length-1].program;
}
}

var strip = {
left: mustache.strip.left,
Expand All @@ -23,7 +41,7 @@ export function prepareBlock(mustache, program, inverseAndProgram, close, invert
// Determine the standalone candiacy. Basically flag our content as being possibly standalone
// so our parent can determine if we actually are standalone
openStandalone: isNextWhitespace(program.statements),
closeStandalone: isPrevWhitespace((inverse || program).statements)
closeStandalone: isPrevWhitespace((firstInverse || program).statements)
};

if (mustache.strip.right) {
Expand All @@ -36,19 +54,20 @@ export function prepareBlock(mustache, program, inverseAndProgram, close, invert
if (inverseStrip.left) {
omitLeft(program.statements, null, true);
}

if (inverseStrip.right) {
omitRight(inverse.statements, null, true);
omitRight(firstInverse.statements, null, true);
}
if (close.strip.left) {
omitLeft(inverse.statements, null, true);
omitLeft(lastInverse.statements, null, true);
}

// Find standalone else statments
if (isPrevWhitespace(program.statements)
&& isNextWhitespace(inverse.statements)) {
&& isNextWhitespace(firstInverse.statements)) {

omitLeft(program.statements);
omitRight(inverse.statements);
omitRight(firstInverse.statements);
}
} else {
if (close.strip.left) {
Expand Down
20 changes: 20 additions & 0 deletions spec/blocks.js
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,20 @@ describe('blocks', function() {
shouldCompileTo("{{#people}}{{name}}{{^}}{{none}}{{/people}}", {none: "No people"},
"No people");
});
it("chained inverted sections", function() {
shouldCompileTo("{{#people}}{{name}}{{else if none}}{{none}}{{/people}}", {none: "No people"},
"No people");
shouldCompileTo("{{#people}}{{name}}{{else if nothere}}fail{{else unless nothere}}{{none}}{{/people}}", {none: "No people"},
"No people");
shouldCompileTo("{{#people}}{{name}}{{else if none}}{{none}}{{else}}fail{{/people}}", {none: "No people"},
"No people");
});
it("chained inverted sections with mismatch", function() {
shouldThrow(function() {
shouldCompileTo("{{#people}}{{name}}{{else if none}}{{none}}{{/if}}", {none: "No people"},
"No people");
}, Error);
});

it("block inverted sections with empty arrays", function() {
shouldCompileTo("{{#people}}{{name}}{{^}}{{none}}{{/people}}", {none: "No people", people: []},
Expand All @@ -105,6 +119,12 @@ describe('blocks', function() {
shouldCompileTo('{{#people}}\n{{name}}\n{{^}}\n{{none}}\n{{/people}}\n', {none: 'No people'},
'No people\n');
});
it('block standalone chained else sections', function() {
shouldCompileTo('{{#people}}\n{{name}}\n{{else if none}}\n{{none}}\n{{/people}}\n', {none: 'No people'},
'No people\n');
shouldCompileTo('{{#people}}\n{{name}}\n{{else if none}}\n{{none}}\n{{^}}\n{{/people}}\n', {none: 'No people'},
'No people\n');
});
it('should handle nesting', function() {
shouldCompileTo('{{#data}}\n{{#if true}}\n{{.}}\n{{/if}}\n{{/data}}\nOK.', {data: [1, 3, 5]}, '1\n3\n5\nOK.');
});
Expand Down
10 changes: 8 additions & 2 deletions spec/parser.js
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,10 @@ describe('parser', function() {
equals(ast_for("{{#foo}} bar {{else}} baz {{/foo}}"), "BLOCK:\n {{ ID:foo [] }}\n PROGRAM:\n CONTENT[ ' bar ' ]\n {{^}}\n CONTENT[ ' baz ' ]\n");
});

it('parses multiple inverse sections', function() {
equals(ast_for("{{#foo}} bar {{else if bar}}{{else}} baz {{/foo}}"), "BLOCK:\n {{ ID:foo [] }}\n PROGRAM:\n CONTENT[ ' bar ' ]\n {{^}}\n BLOCK:\n {{ ID:if [ID:bar] }}\n PROGRAM:\n {{^}}\n CONTENT[ ' baz ' ]\n");
});

it('parses empty blocks', function() {
equals(ast_for("{{#foo}}{{/foo}}"), "BLOCK:\n {{ ID:foo [] }}\n PROGRAM:\n");
});
Expand Down Expand Up @@ -147,8 +151,10 @@ describe('parser', function() {
it('parses a standalone inverse section', function() {
equals(ast_for("{{^foo}}bar{{/foo}}"), "BLOCK:\n {{ ID:foo [] }}\n {{^}}\n CONTENT[ 'bar' ]\n");
});
it('parses a standalone inverse section', function() {
equals(ast_for("{{else foo}}bar{{/foo}}"), "BLOCK:\n {{ ID:foo [] }}\n {{^}}\n CONTENT[ 'bar' ]\n");
it('throws on old inverse section', function() {
shouldThrow(function() {
equals(ast_for("{{else foo}}bar{{/foo}}"), "BLOCK:\n {{ ID:foo [] }}\n {{^}}\n CONTENT[ 'bar' ]\n");
}, Error);
});

it("raises if there's a Parse error", function() {
Expand Down
2 changes: 1 addition & 1 deletion src/handlebars.l
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ ID [^\s!"#%-,\.\/;->@\[-\^`\{-~]+/{LOOKAHEAD}
<mu>"{{"{LEFT_STRIP}?"^"\s*{RIGHT_STRIP}?"}}" this.popState(); return 'INVERSE';
<mu>"{{"{LEFT_STRIP}?\s*"else"\s*{RIGHT_STRIP}?"}}" this.popState(); return 'INVERSE';
<mu>"{{"{LEFT_STRIP}?"^" return 'OPEN_INVERSE';
<mu>"{{"{LEFT_STRIP}?\s*"else" return 'OPEN_INVERSE';
<mu>"{{"{LEFT_STRIP}?\s*"else" return 'OPEN_INVERSE_CHAIN';
<mu>"{{"{LEFT_STRIP}?"{" return 'OPEN_UNESCAPED';
<mu>"{{"{LEFT_STRIP}?"&" return 'OPEN';
<mu>"{{!--" this.popState(); this.begin('com');
Expand Down
18 changes: 17 additions & 1 deletion src/handlebars.yy
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ openRawBlock
;

block
: openBlock program inverseAndProgram? closeBlock -> yy.prepareBlock($1, $2, $3, $4, false, @$)
: openBlock program inverseChain? closeBlock -> yy.prepareBlock($1, $2, $3, $4, false, @$)
| openInverse program inverseAndProgram? closeBlock -> yy.prepareBlock($1, $2, $3, $4, true, @$)
;

Expand All @@ -42,10 +42,26 @@ openInverse
: OPEN_INVERSE sexpr CLOSE -> new yy.MustacheNode($2, null, $1, yy.stripFlags($1, $3), @$)
;

openInverseChain
: OPEN_INVERSE_CHAIN sexpr CLOSE -> new yy.MustacheNode($2, null, $1, yy.stripFlags($1, $3), @$)
;

inverseAndProgram
: INVERSE program -> { strip: yy.stripFlags($1, $1), program: $2 }
;

inverseChain
: openInverseChain program inverseChain? {
var inverse = yy.prepareBlock($1, $2, $3, $3, false, @$),
program = new yy.ProgramNode(yy.prepareProgram([inverse]), {}, @$);

program.inverse = inverse;

$$ = { strip: $1.strip, program: program, chain: true };
}
| inverseAndProgram -> $1
;

closeBlock
: OPEN_ENDBLOCK path CLOSE -> {path: $2, strip: yy.stripFlags($1, $3)}
;
Expand Down