Merge pull request #892 from wycats/else-if
Implement parser for else chaining of helpers
This commit is contained in:
@@ -15,11 +15,29 @@ export function stripComment(comment) {
|
||||
|
||||
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,
|
||||
@@ -28,7 +46,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) {
|
||||
@@ -41,19 +59,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) {
|
||||
|
||||
@@ -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: []},
|
||||
@@ -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.');
|
||||
});
|
||||
|
||||
+8
-2
@@ -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");
|
||||
});
|
||||
@@ -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() {
|
||||
|
||||
+1
-1
@@ -76,7 +76,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>"{{"{LEFT_STRIP}?"!--" {
|
||||
|
||||
+17
-1
@@ -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, @$)
|
||||
;
|
||||
|
||||
@@ -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)}
|
||||
;
|
||||
|
||||
Reference in New Issue
Block a user