Fix a number of outstanding issues:
* {{}} escape their contents, {{{}}} and {{& }} do not
* Add support in the parser, tokenizer and AST for partials
with context (support is still not there in the runtime)
* Fix some inconsistencies with the old behavior involving
the correct printing of null and undefined
* Add Handlebars.Exception
* Fixed an issue involving ./foo and this/foo
* Fleshed out helperMissing in the specs (this will be
moved out into handlebars proper once registerHelper
and registerPartial are added)
This commit is contained in:
@@ -6,6 +6,9 @@ if(exports) {
|
|||||||
Handlebars.PrintVisitor = require("handlebars/printer").PrintVisitor;
|
Handlebars.PrintVisitor = require("handlebars/printer").PrintVisitor;
|
||||||
Handlebars.Parser = require("handlebars/parser").parser;
|
Handlebars.Parser = require("handlebars/parser").parser;
|
||||||
Handlebars.Runtime = require("handlebars/runtime").Runtime;
|
Handlebars.Runtime = require("handlebars/runtime").Runtime;
|
||||||
|
Handlebars.Utils = require("handlebars/utils").Utils;
|
||||||
|
Handlebars.SafeString = require("handlebars/utils").SafeString;
|
||||||
|
Handlebars.Exception = require("handlebars/utils").Exception;
|
||||||
} else {
|
} else {
|
||||||
Handlebars.Parser = handlebars;
|
Handlebars.Parser = handlebars;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,15 +8,17 @@ Handlebars.AST.ProgramNode = function(statements, inverse) {
|
|||||||
this.inverse = inverse;
|
this.inverse = inverse;
|
||||||
};
|
};
|
||||||
|
|
||||||
Handlebars.AST.MustacheNode = function(params) {
|
Handlebars.AST.MustacheNode = function(params, unescaped) {
|
||||||
this.type = "mustache";
|
this.type = "mustache";
|
||||||
this.id = params[0];
|
this.id = params[0];
|
||||||
this.params = params.slice(1);
|
this.params = params.slice(1);
|
||||||
|
this.escaped = !unescaped;
|
||||||
};
|
};
|
||||||
|
|
||||||
Handlebars.AST.PartialNode = function(id) {
|
Handlebars.AST.PartialNode = function(id, context) {
|
||||||
this.type = "partial";
|
this.type = "partial";
|
||||||
this.id = id;
|
this.id = id;
|
||||||
|
this.context = context;
|
||||||
};
|
};
|
||||||
|
|
||||||
Handlebars.AST.BlockNode = function(mustache, program) {
|
Handlebars.AST.BlockNode = function(mustache, program) {
|
||||||
@@ -39,7 +41,7 @@ Handlebars.AST.IdNode = function(parts) {
|
|||||||
var part = parts[i];
|
var part = parts[i];
|
||||||
|
|
||||||
if(part === "..") { depth++; }
|
if(part === "..") { depth++; }
|
||||||
else if(part === "." || part === "this") { break; }
|
else if(part === "." || part === "this") { continue; }
|
||||||
else { dig.push(part) }
|
else { dig.push(part) }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -96,7 +96,10 @@ Handlebars.HandlebarsLexer.prototype.lex = function() {
|
|||||||
return "OPEN_ENDBLOCK";
|
return "OPEN_ENDBLOCK";
|
||||||
} else if(peek === "^") {
|
} else if(peek === "^") {
|
||||||
this.getchar();
|
this.getchar();
|
||||||
return "OPEN_INVERSE"
|
return "OPEN_INVERSE";
|
||||||
|
} else if(peek === "{" || peek === "&") {
|
||||||
|
this.getchar();
|
||||||
|
return "OPEN_UNESCAPED";
|
||||||
} else if(peek === "!") {
|
} else if(peek === "!") {
|
||||||
this.readchar();
|
this.readchar();
|
||||||
this.setupLex(); // reset the lexer state so the yytext is the comment only
|
this.setupLex(); // reset the lexer state so the yytext is the comment only
|
||||||
|
|||||||
@@ -71,7 +71,9 @@ Handlebars.PrintVisitor.prototype.mustache = function(mustache) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
Handlebars.PrintVisitor.prototype.partial = function(partial) {
|
Handlebars.PrintVisitor.prototype.partial = function(partial) {
|
||||||
return this.pad("{{> " + this.accept(partial.id) + " }}");
|
var content = this.accept(partial.id);
|
||||||
|
if(partial.context) { content = content + " " + this.accept(partial.context); }
|
||||||
|
return this.pad("{{> " + content + " }}");
|
||||||
};
|
};
|
||||||
|
|
||||||
Handlebars.PrintVisitor.prototype.STRING = function(string) {
|
Handlebars.PrintVisitor.prototype.STRING = function(string) {
|
||||||
|
|||||||
@@ -70,7 +70,7 @@ Handlebars.Runtime = function(context, fallback, stack) {
|
|||||||
|
|
||||||
var newContext = this.context = new Handlebars.Context;
|
var newContext = this.context = new Handlebars.Context;
|
||||||
|
|
||||||
if(context.isContext) {
|
if(context && context.isContext) {
|
||||||
newContext.data = context.data;
|
newContext.data = context.data;
|
||||||
newContext.fallback = context.fallback;
|
newContext.fallback = context.fallback;
|
||||||
} else {
|
} else {
|
||||||
@@ -103,17 +103,22 @@ Handlebars.Runtime.prototype = {
|
|||||||
mustache: function(mustache) {
|
mustache: function(mustache) {
|
||||||
var idObj = this.accept(mustache.id);
|
var idObj = this.accept(mustache.id);
|
||||||
var params = mustache.params;
|
var params = mustache.params;
|
||||||
|
var buf;
|
||||||
|
|
||||||
for(var i=0, l=params.length; i<l; i++) {
|
for(var i=0, l=params.length; i<l; i++) {
|
||||||
params[i] = this.accept(params[i]).data;
|
params[i] = this.accept(params[i]).data;
|
||||||
}
|
}
|
||||||
|
|
||||||
if(toString.call(idObj.data) === "[object Function]") {
|
if(toString.call(idObj.data) === "[object Function]") {
|
||||||
this.buffer = this.buffer + idObj.data.apply(this.wrapContext(), params);
|
buf = idObj.data.apply(this.wrapContext(), params);
|
||||||
} else {
|
} else {
|
||||||
if(params.length) { throw new Error(mustache.id.parts.join("/") + " is not a Function, so you cannot have Function parameters"); }
|
if(params.length) { throw new Error(mustache.id.parts.join("/") + " is not a Function, so you cannot have Function parameters"); }
|
||||||
this.buffer = this.buffer + idObj.data;
|
buf = idObj.data;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if(buf && mustache.escaped) { buf = Handlebars.Utils.escapeExpression(buf); }
|
||||||
|
|
||||||
|
this.buffer = this.buffer + (buf == null ? '' : buf);
|
||||||
},
|
},
|
||||||
|
|
||||||
block: function(block) {
|
block: function(block) {
|
||||||
|
|||||||
@@ -0,0 +1,54 @@
|
|||||||
|
if(exports) {
|
||||||
|
var Handlebars = {};
|
||||||
|
}
|
||||||
|
|
||||||
|
Handlebars.Exception = function(message) {
|
||||||
|
this.message = message;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Build out our basic SafeString type
|
||||||
|
Handlebars.SafeString = function(string) {
|
||||||
|
this.string = string;
|
||||||
|
}
|
||||||
|
Handlebars.SafeString.prototype.toString = function() {
|
||||||
|
return this.string.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
Handlebars.Utils = {
|
||||||
|
escapeExpression: function(string) {
|
||||||
|
// don't escape SafeStrings, since they're already safe
|
||||||
|
if (string instanceof Handlebars.SafeString) {
|
||||||
|
return string.toString();
|
||||||
|
}
|
||||||
|
else if (string === null) {
|
||||||
|
string = "";
|
||||||
|
}
|
||||||
|
|
||||||
|
return string.toString().replace(/&(?!\w+;)|["\\<>]/g, function(str) {
|
||||||
|
switch(str) {
|
||||||
|
case "&":
|
||||||
|
return "&";
|
||||||
|
break;
|
||||||
|
case '"':
|
||||||
|
return "\"";
|
||||||
|
case "\\":
|
||||||
|
return "\\\\";
|
||||||
|
break;
|
||||||
|
case "<":
|
||||||
|
return "<";
|
||||||
|
break;
|
||||||
|
case ">":
|
||||||
|
return ">";
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
return str;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if(exports) {
|
||||||
|
exports.Utils = Handlebars.Utils;
|
||||||
|
exports.SafeString = Handlebars.SafeString;
|
||||||
|
exports.Exception = Handlebars.Exception;
|
||||||
|
}
|
||||||
+12
-2
@@ -64,8 +64,10 @@ describe "Parser" do
|
|||||||
pad("{{ #{id} [#{params.join(", ")}] }}")
|
pad("{{ #{id} [#{params.join(", ")}] }}")
|
||||||
end
|
end
|
||||||
|
|
||||||
def partial(id)
|
def partial(id, context = nil)
|
||||||
pad("{{> #{id} }}")
|
content = id.dup
|
||||||
|
content << " #{context}" if context
|
||||||
|
pad("{{> #{content} }}")
|
||||||
end
|
end
|
||||||
|
|
||||||
def comment(comment)
|
def comment(comment)
|
||||||
@@ -97,6 +99,10 @@ describe "Parser" do
|
|||||||
ast_for("{{foo/bar}}").should == program { mustache path("foo", "bar") }
|
ast_for("{{foo/bar}}").should == program { mustache path("foo", "bar") }
|
||||||
end
|
end
|
||||||
|
|
||||||
|
it "parses mustaches with this/foo" do
|
||||||
|
ast_for("{{this/foo}}").should == program { mustache id("foo") }
|
||||||
|
end
|
||||||
|
|
||||||
it "parses mustaches with parameters" do
|
it "parses mustaches with parameters" do
|
||||||
ast_for("{{foo bar}}").should == program { mustache id("foo"), id("bar") }
|
ast_for("{{foo bar}}").should == program { mustache id("foo"), id("bar") }
|
||||||
end
|
end
|
||||||
@@ -116,6 +122,10 @@ describe "Parser" do
|
|||||||
ast_for("{{> foo }}").should == program { partial id("foo") }
|
ast_for("{{> foo }}").should == program { partial id("foo") }
|
||||||
end
|
end
|
||||||
|
|
||||||
|
it "parses a partial with context" do
|
||||||
|
ast_for("{{> foo bar}}").should == program { partial id("foo"), id("bar") }
|
||||||
|
end
|
||||||
|
|
||||||
it "parses a comment" do
|
it "parses a comment" do
|
||||||
ast_for("{{! this is a comment }}").should == program do
|
ast_for("{{! this is a comment }}").should == program do
|
||||||
comment " this is a comment "
|
comment " this is a comment "
|
||||||
|
|||||||
@@ -1,10 +1,19 @@
|
|||||||
module("basic context");
|
module("basic context");
|
||||||
|
|
||||||
var helperMissing = function(context, fn) {
|
var helperMissing = function(context, fn) {
|
||||||
|
var ret = "";
|
||||||
|
|
||||||
if(context === true) {
|
if(context === true) {
|
||||||
return fn(this);
|
return fn(this);
|
||||||
} else if(context === false) {
|
} else if(context === false) {
|
||||||
return "";
|
return "";
|
||||||
|
} else if(Object.prototype.toString.call(context) === "[object Array]") {
|
||||||
|
for(var i=0, j=context.length; i<j; i++) {
|
||||||
|
ret = ret + fn(context[i]);
|
||||||
|
}
|
||||||
|
return ret;
|
||||||
|
} else {
|
||||||
|
return fn(context);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -40,6 +40,7 @@ module Handlebars
|
|||||||
context.load('lib/handlebars/printer.js')
|
context.load('lib/handlebars/printer.js')
|
||||||
context.load('lib/handlebars/parser.js')
|
context.load('lib/handlebars/parser.js')
|
||||||
context.load('lib/handlebars/runtime.js')
|
context.load('lib/handlebars/runtime.js')
|
||||||
|
context.load('lib/handlebars/utils.js')
|
||||||
context.load('lib/handlebars.js')
|
context.load('lib/handlebars.js')
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
+13
-1
@@ -36,12 +36,19 @@ describe "Tokenizer" do
|
|||||||
result[1].should be_token("ID", "foo")
|
result[1].should be_token("ID", "foo")
|
||||||
end
|
end
|
||||||
|
|
||||||
it "tokenizes a path as 'OPEN ID CLOSE'" do
|
it "tokenizes a path as 'OPEN (ID SEP)* ID CLOSE'" do
|
||||||
result = tokenize("{{../foo/bar}}")
|
result = tokenize("{{../foo/bar}}")
|
||||||
result.should match_tokens(%w(OPEN ID SEP ID SEP ID CLOSE))
|
result.should match_tokens(%w(OPEN ID SEP ID SEP ID CLOSE))
|
||||||
result[1].should be_token("ID", "..")
|
result[1].should be_token("ID", "..")
|
||||||
end
|
end
|
||||||
|
|
||||||
|
it "tokenizes a path with this/foo as OPEN ID SEP ID CLOSE" do
|
||||||
|
result = tokenize("{{this/foo}}")
|
||||||
|
result.should match_tokens(%w(OPEN ID SEP ID CLOSE))
|
||||||
|
result[1].should be_token("ID", "this")
|
||||||
|
result[3].should be_token("ID", "foo")
|
||||||
|
end
|
||||||
|
|
||||||
it "tokenizes a simple mustahe with spaces as 'OPEN ID CLOSE'" do
|
it "tokenizes a simple mustahe with spaces as 'OPEN ID CLOSE'" do
|
||||||
result = tokenize("{{ foo }}")
|
result = tokenize("{{ foo }}")
|
||||||
result.should match_tokens(%w(OPEN ID CLOSE))
|
result.should match_tokens(%w(OPEN ID CLOSE))
|
||||||
@@ -60,6 +67,11 @@ describe "Tokenizer" do
|
|||||||
result.should match_tokens(%w(OPEN_PARTIAL ID CLOSE))
|
result.should match_tokens(%w(OPEN_PARTIAL ID CLOSE))
|
||||||
end
|
end
|
||||||
|
|
||||||
|
it "tokenizes a partial with context as 'OPEN_PARTIAL ID ID CLOSE'" do
|
||||||
|
result = tokenize("{{> foo bar }}")
|
||||||
|
result.should match_tokens(%w(OPEN_PARTIAL ID ID CLOSE))
|
||||||
|
end
|
||||||
|
|
||||||
it "tokenizes a partial without spaces as 'OPEN_PARTIAL ID CLOSE'" do
|
it "tokenizes a partial without spaces as 'OPEN_PARTIAL ID CLOSE'" do
|
||||||
result = tokenize("{{>foo}}")
|
result = tokenize("{{>foo}}")
|
||||||
result.should match_tokens(%w(OPEN_PARTIAL ID CLOSE))
|
result.should match_tokens(%w(OPEN_PARTIAL ID CLOSE))
|
||||||
|
|||||||
@@ -34,10 +34,13 @@ closeBlock
|
|||||||
|
|
||||||
mustache
|
mustache
|
||||||
: OPEN inMustache CLOSE { $$ = new yy.MustacheNode($2) }
|
: OPEN inMustache CLOSE { $$ = new yy.MustacheNode($2) }
|
||||||
|
| OPEN_UNESCAPED inMustache CLOSE { $$ = new yy.MustacheNode($2, true) }
|
||||||
;
|
;
|
||||||
|
|
||||||
|
|
||||||
partial
|
partial
|
||||||
: OPEN_PARTIAL path CLOSE { $$ = new yy.PartialNode($2) }
|
: OPEN_PARTIAL path CLOSE { $$ = new yy.PartialNode($2) }
|
||||||
|
| OPEN_PARTIAL path path CLOSE { $$ = new yy.PartialNode($2, $3) }
|
||||||
;
|
;
|
||||||
|
|
||||||
simpleInverse
|
simpleInverse
|
||||||
|
|||||||
Reference in New Issue
Block a user