From 762329913d447ee6b873d99454715bf5601214f1 Mon Sep 17 00:00:00 2001 From: wycats Date: Thu, 2 Dec 2010 01:13:24 -0500 Subject: [PATCH] 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) --- lib/handlebars.js | 3 ++ lib/handlebars/ast.js | 12 ++++--- lib/handlebars/handlebars_lexer.js | 5 ++- lib/handlebars/printer.js | 4 ++- lib/handlebars/runtime.js | 11 ++++-- lib/handlebars/utils.js | 54 ++++++++++++++++++++++++++++++ spec/parser_spec.rb | 14 ++++++-- spec/qunit_spec.js | 11 +++++- spec/spec_helper.rb | 1 + spec/tokenizer_spec.rb | 14 +++++++- src/handlebars.yy | 3 ++ 11 files changed, 118 insertions(+), 14 deletions(-) create mode 100644 lib/handlebars/utils.js diff --git a/lib/handlebars.js b/lib/handlebars.js index 5d53ed59..880a86bb 100644 --- a/lib/handlebars.js +++ b/lib/handlebars.js @@ -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; } diff --git a/lib/handlebars/ast.js b/lib/handlebars/ast.js index fdd37a30..bfa4030f 100644 --- a/lib/handlebars/ast.js +++ b/lib/handlebars/ast.js @@ -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) } } diff --git a/lib/handlebars/handlebars_lexer.js b/lib/handlebars/handlebars_lexer.js index af2da754..c1b06ea5 100644 --- a/lib/handlebars/handlebars_lexer.js +++ b/lib/handlebars/handlebars_lexer.js @@ -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 diff --git a/lib/handlebars/printer.js b/lib/handlebars/printer.js index a8fcb730..97c82d87 100644 --- a/lib/handlebars/printer.js +++ b/lib/handlebars/printer.js @@ -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) { diff --git a/lib/handlebars/runtime.js b/lib/handlebars/runtime.js index ad1ced65..fe6523e9 100644 --- a/lib/handlebars/runtime.js +++ b/lib/handlebars/runtime.js @@ -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]/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; +} diff --git a/spec/parser_spec.rb b/spec/parser_spec.rb index 62919066..fea61353 100644 --- a/spec/parser_spec.rb +++ b/spec/parser_spec.rb @@ -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 " diff --git a/spec/qunit_spec.js b/spec/qunit_spec.js index 00be18c5..264f46a0 100644 --- a/spec/qunit_spec.js +++ b/spec/qunit_spec.js @@ -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 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)) diff --git a/src/handlebars.yy b/src/handlebars.yy index 2e46f8b1..6a7c4560 100644 --- a/src/handlebars.yy +++ b/src/handlebars.yy @@ -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