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.Parser = require("handlebars/parser").parser;
|
||||
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 {
|
||||
Handlebars.Parser = handlebars;
|
||||
}
|
||||
|
||||
@@ -8,15 +8,17 @@ Handlebars.AST.ProgramNode = function(statements, inverse) {
|
||||
this.inverse = inverse;
|
||||
};
|
||||
|
||||
Handlebars.AST.MustacheNode = function(params) {
|
||||
Handlebars.AST.MustacheNode = function(params, unescaped) {
|
||||
this.type = "mustache";
|
||||
this.id = params[0];
|
||||
this.params = params.slice(1);
|
||||
this.escaped = !unescaped;
|
||||
};
|
||||
|
||||
Handlebars.AST.PartialNode = function(id) {
|
||||
this.type = "partial";
|
||||
this.id = id;
|
||||
Handlebars.AST.PartialNode = function(id, context) {
|
||||
this.type = "partial";
|
||||
this.id = id;
|
||||
this.context = context;
|
||||
};
|
||||
|
||||
Handlebars.AST.BlockNode = function(mustache, program) {
|
||||
@@ -39,7 +41,7 @@ Handlebars.AST.IdNode = function(parts) {
|
||||
var part = parts[i];
|
||||
|
||||
if(part === "..") { depth++; }
|
||||
else if(part === "." || part === "this") { break; }
|
||||
else if(part === "." || part === "this") { continue; }
|
||||
else { dig.push(part) }
|
||||
}
|
||||
|
||||
|
||||
@@ -96,7 +96,10 @@ Handlebars.HandlebarsLexer.prototype.lex = function() {
|
||||
return "OPEN_ENDBLOCK";
|
||||
} else if(peek === "^") {
|
||||
this.getchar();
|
||||
return "OPEN_INVERSE"
|
||||
return "OPEN_INVERSE";
|
||||
} else if(peek === "{" || peek === "&") {
|
||||
this.getchar();
|
||||
return "OPEN_UNESCAPED";
|
||||
} else if(peek === "!") {
|
||||
this.readchar();
|
||||
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) {
|
||||
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) {
|
||||
|
||||
@@ -70,7 +70,7 @@ Handlebars.Runtime = function(context, fallback, stack) {
|
||||
|
||||
var newContext = this.context = new Handlebars.Context;
|
||||
|
||||
if(context.isContext) {
|
||||
if(context && context.isContext) {
|
||||
newContext.data = context.data;
|
||||
newContext.fallback = context.fallback;
|
||||
} else {
|
||||
@@ -103,17 +103,22 @@ Handlebars.Runtime.prototype = {
|
||||
mustache: function(mustache) {
|
||||
var idObj = this.accept(mustache.id);
|
||||
var params = mustache.params;
|
||||
var buf;
|
||||
|
||||
for(var i=0, l=params.length; i<l; i++) {
|
||||
params[i] = this.accept(params[i]).data;
|
||||
}
|
||||
|
||||
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 {
|
||||
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) {
|
||||
|
||||
@@ -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(", ")}] }}")
|
||||
end
|
||||
|
||||
def partial(id)
|
||||
pad("{{> #{id} }}")
|
||||
def partial(id, context = nil)
|
||||
content = id.dup
|
||||
content << " #{context}" if context
|
||||
pad("{{> #{content} }}")
|
||||
end
|
||||
|
||||
def comment(comment)
|
||||
@@ -97,6 +99,10 @@ describe "Parser" do
|
||||
ast_for("{{foo/bar}}").should == program { mustache path("foo", "bar") }
|
||||
end
|
||||
|
||||
it "parses mustaches with this/foo" do
|
||||
ast_for("{{this/foo}}").should == program { mustache id("foo") }
|
||||
end
|
||||
|
||||
it "parses mustaches with parameters" do
|
||||
ast_for("{{foo bar}}").should == program { mustache id("foo"), id("bar") }
|
||||
end
|
||||
@@ -116,6 +122,10 @@ describe "Parser" do
|
||||
ast_for("{{> foo }}").should == program { partial id("foo") }
|
||||
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
|
||||
ast_for("{{! this is a comment }}").should == program do
|
||||
comment " this is a comment "
|
||||
|
||||
+10
-1
@@ -1,11 +1,20 @@
|
||||
module("basic context");
|
||||
|
||||
var helperMissing = function(context, fn) {
|
||||
var ret = "";
|
||||
|
||||
if(context === true) {
|
||||
return fn(this);
|
||||
} else if(context === false) {
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
var shouldCompileTo = function(string, hash, expected, message) {
|
||||
|
||||
@@ -40,6 +40,7 @@ module Handlebars
|
||||
context.load('lib/handlebars/printer.js')
|
||||
context.load('lib/handlebars/parser.js')
|
||||
context.load('lib/handlebars/runtime.js')
|
||||
context.load('lib/handlebars/utils.js')
|
||||
context.load('lib/handlebars.js')
|
||||
end
|
||||
end
|
||||
|
||||
+13
-1
@@ -36,12 +36,19 @@ describe "Tokenizer" do
|
||||
result[1].should be_token("ID", "foo")
|
||||
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.should match_tokens(%w(OPEN ID SEP ID SEP ID CLOSE))
|
||||
result[1].should be_token("ID", "..")
|
||||
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
|
||||
result = tokenize("{{ foo }}")
|
||||
result.should match_tokens(%w(OPEN ID CLOSE))
|
||||
@@ -60,6 +67,11 @@ describe "Tokenizer" do
|
||||
result.should match_tokens(%w(OPEN_PARTIAL ID CLOSE))
|
||||
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
|
||||
result = tokenize("{{>foo}}")
|
||||
result.should match_tokens(%w(OPEN_PARTIAL ID CLOSE))
|
||||
|
||||
@@ -34,10 +34,13 @@ closeBlock
|
||||
|
||||
mustache
|
||||
: OPEN inMustache CLOSE { $$ = new yy.MustacheNode($2) }
|
||||
| OPEN_UNESCAPED inMustache CLOSE { $$ = new yy.MustacheNode($2, true) }
|
||||
;
|
||||
|
||||
|
||||
partial
|
||||
: OPEN_PARTIAL path CLOSE { $$ = new yy.PartialNode($2) }
|
||||
| OPEN_PARTIAL path path CLOSE { $$ = new yy.PartialNode($2, $3) }
|
||||
;
|
||||
|
||||
simpleInverse
|
||||
|
||||
Reference in New Issue
Block a user