From 4bb794d814d068361a101a87735e2b9bbcee5115 Mon Sep 17 00:00:00 2001 From: Les Hill Date: Thu, 13 Dec 2012 10:33:20 -0800 Subject: [PATCH 1/2] Partials can be paths Allows partials with slashes, a common partial syntax. For example: {{> shared/dude}} --- lib/handlebars/compiler/ast.js | 16 +++++++++------- lib/handlebars/compiler/compiler.js | 4 ++-- lib/handlebars/compiler/printer.js | 6 +++++- spec/parser_spec.rb | 12 ++++++++++-- spec/qunit_spec.js | 6 +++--- spec/tokenizer_spec.rb | 16 ++++++++-------- src/handlebars.l | 6 ++++-- src/handlebars.yy | 8 ++++++-- 8 files changed, 47 insertions(+), 27 deletions(-) diff --git a/lib/handlebars/compiler/ast.js b/lib/handlebars/compiler/ast.js index 25abe0a3a..459b8636f 100644 --- a/lib/handlebars/compiler/ast.js +++ b/lib/handlebars/compiler/ast.js @@ -33,13 +33,10 @@ var Handlebars = require('./base'); // pass or at runtime. }; - Handlebars.AST.PartialNode = function(id, context) { - this.type = "partial"; - - // TODO: disallow complex IDs - - this.id = id; - this.context = context; + Handlebars.AST.PartialNode = function(partialName, context) { + this.type = "partial"; + this.partialName = partialName; + this.context = context; }; var verifyMatch = function(open, close) { @@ -93,6 +90,11 @@ var Handlebars = require('./base'); this.isSimple = parts.length === 1 && !this.isScoped && depth === 0; }; + Handlebars.AST.PartialNameNode = function(name) { + this.type = "PARTIAL_NAME"; + this.name = name; + }; + Handlebars.AST.DataNode = function(id) { this.type = "DATA"; this.id = id; diff --git a/lib/handlebars/compiler/compiler.js b/lib/handlebars/compiler/compiler.js index 7578dd2d7..31f9f4b5d 100644 --- a/lib/handlebars/compiler/compiler.js +++ b/lib/handlebars/compiler/compiler.js @@ -160,7 +160,7 @@ Handlebars.JavaScriptCompiler = function() {}; }, partial: function(partial) { - var id = partial.id; + var partialName = partial.partialName; this.usePartial = true; if(partial.context) { @@ -169,7 +169,7 @@ Handlebars.JavaScriptCompiler = function() {}; this.opcode('push', 'depth0'); } - this.opcode('invokePartial', id.original); + this.opcode('invokePartial', partialName.name); this.opcode('append'); }, diff --git a/lib/handlebars/compiler/printer.js b/lib/handlebars/compiler/printer.js index 7a42a6677..853a4cace 100644 --- a/lib/handlebars/compiler/printer.js +++ b/lib/handlebars/compiler/printer.js @@ -72,7 +72,7 @@ Handlebars.PrintVisitor.prototype.mustache = function(mustache) { }; Handlebars.PrintVisitor.prototype.partial = function(partial) { - var content = this.accept(partial.id); + var content = this.accept(partial.partialName); if(partial.context) { content = content + " " + this.accept(partial.context); } return this.pad("{{> " + content + " }}"); }; @@ -111,6 +111,10 @@ Handlebars.PrintVisitor.prototype.ID = function(id) { } }; +Handlebars.PrintVisitor.prototype.PARTIAL_NAME = function(partialName) { + return "PARTIAL:" + partialName.name; +}; + Handlebars.PrintVisitor.prototype.DATA = function(data) { return "@" + data.id; }; diff --git a/spec/parser_spec.rb b/spec/parser_spec.rb index 2b234d364..11cd77ef9 100644 --- a/spec/parser_spec.rb +++ b/spec/parser_spec.rb @@ -114,6 +114,10 @@ def data(id) "@#{id}" end + def partial_name(name) + "PARTIAL:#{name}" + end + def path(*parts) "PATH:#{parts.join("/")}" end @@ -218,11 +222,15 @@ def path(*parts) end it "parses a partial" do - ast_for("{{> foo }}").should == root { partial id("foo") } + ast_for("{{> foo }}").should == root { partial partial_name("foo") } end it "parses a partial with context" do - ast_for("{{> foo bar}}").should == root { partial id("foo"), id("bar") } + ast_for("{{> foo bar}}").should == root { partial partial_name("foo"), id("bar") } + end + + it "parses a partial with a complex name" do + ast_for("{{> shared/partial}}").should == root { partial partial_name("shared/partial") } end it "parses a comment" do diff --git a/spec/qunit_spec.js b/spec/qunit_spec.js index 5c9425396..9999cb288 100644 --- a/spec/qunit_spec.js +++ b/spec/qunit_spec.js @@ -521,11 +521,11 @@ test("GH-14: a partial preceding a selector", function() { shouldCompileToWithPartials(string, [hash, {}, {dude:dude}], true, "Dudes: Jeepers Creepers", "Regular selectors can follow a partial"); }); -test("Partials with literal paths", function() { - var string = "Dudes: {{> [dude]}}"; +test("Partials with slash paths", function() { + var string = "Dudes: {{> shared/dude}}"; var dude = "{{name}}"; var hash = {name:"Jeepers", another_dude:"Creepers"}; - shouldCompileToWithPartials(string, [hash, {}, {dude:dude}], true, "Dudes: Jeepers", "Partials can use literal paths"); + shouldCompileToWithPartials(string, [hash, {}, {'shared/dude':dude}], true, "Dudes: Jeepers", "Partials can use literal paths"); }); suite("String literal parameters"); diff --git a/spec/tokenizer_spec.rb b/spec/tokenizer_spec.rb index 7a771ba2f..cb7f6eb09 100644 --- a/spec/tokenizer_spec.rb +++ b/spec/tokenizer_spec.rb @@ -132,24 +132,24 @@ def tokenize(string) result[4].should be_token("CONTENT", " baz") end - it "tokenizes a partial as 'OPEN_PARTIAL ID CLOSE'" do + it "tokenizes a partial as 'OPEN_PARTIAL PARTIAL_NAME CLOSE'" do result = tokenize("{{> foo}}") - result.should match_tokens(%w(OPEN_PARTIAL ID CLOSE)) + result.should match_tokens(%w(OPEN_PARTIAL PARTIAL_NAME CLOSE)) end - it "tokenizes a partial with context as 'OPEN_PARTIAL ID ID CLOSE'" do + it "tokenizes a partial with context as 'OPEN_PARTIAL PARTIAL_NAME ID CLOSE'" do result = tokenize("{{> foo bar }}") - result.should match_tokens(%w(OPEN_PARTIAL ID ID CLOSE)) + result.should match_tokens(%w(OPEN_PARTIAL PARTIAL_NAME ID CLOSE)) end - it "tokenizes a partial without spaces as 'OPEN_PARTIAL ID CLOSE'" do + it "tokenizes a partial without spaces as 'OPEN_PARTIAL PARTIAL_NAME CLOSE'" do result = tokenize("{{>foo}}") - result.should match_tokens(%w(OPEN_PARTIAL ID CLOSE)) + result.should match_tokens(%w(OPEN_PARTIAL PARTIAL_NAME CLOSE)) end - it "tokenizes a partial space at the end as 'OPEN_PARTIAL ID CLOSE'" do + it "tokenizes a partial space at the end as 'OPEN_PARTIAL PARTIAL_NAME CLOSE'" do result = tokenize("{{>foo }}") - result.should match_tokens(%w(OPEN_PARTIAL ID CLOSE)) + result.should match_tokens(%w(OPEN_PARTIAL PARTIAL_NAME CLOSE)) end it "tokenizes a comment as 'COMMENT'" do diff --git a/src/handlebars.l b/src/handlebars.l index 87dce261d..04c7c4c2b 100644 --- a/src/handlebars.l +++ b/src/handlebars.l @@ -1,5 +1,5 @@ -%x mu emu com +%x mu emu com par %% @@ -19,7 +19,7 @@ [\s\S]*?"--}}" { yytext = yytext.substr(0, yyleng-4); this.popState(); return 'COMMENT'; } -"{{>" { return 'OPEN_PARTIAL'; } +"{{>" { this.begin("par"); return 'OPEN_PARTIAL'; } "{{#" { return 'OPEN_BLOCK'; } "{{/" { return 'OPEN_ENDBLOCK'; } "{{^" { return 'OPEN_INVERSE'; } @@ -46,6 +46,8 @@ [a-zA-Z0-9_$-]+/[=}\s\/.] { return 'ID'; } '['[^\]]*']' { yytext = yytext.substr(1, yyleng-2); return 'ID'; } . { return 'INVALID'; } +\s+ { /*ignore whitespace*/ } +[a-zA-Z0-9_$-/]+ { this.popState(); return 'PARTIAL_NAME'; } <> { return 'EOF'; } diff --git a/src/handlebars.yy b/src/handlebars.yy index 70b777714..892969313 100644 --- a/src/handlebars.yy +++ b/src/handlebars.yy @@ -45,8 +45,8 @@ mustache partial - : OPEN_PARTIAL path CLOSE { $$ = new yy.PartialNode($2); } - | OPEN_PARTIAL path path CLOSE { $$ = new yy.PartialNode($2, $3); } + : OPEN_PARTIAL partialName CLOSE { $$ = new yy.PartialNode($2); } + | OPEN_PARTIAL partialName path CLOSE { $$ = new yy.PartialNode($2, $3); } ; simpleInverse @@ -91,6 +91,10 @@ hashSegment | ID EQUALS DATA { $$ = [$1, new yy.DataNode($3)]; } ; +partialName + : PARTIAL_NAME { $$ = new yy.PartialNameNode($1); } + ; + path : pathSegments { $$ = new yy.IdNode($1); } ; From b58c2dd0ad9b3454c2f1b39e5680d473693514f9 Mon Sep 17 00:00:00 2001 From: Les Hill Date: Thu, 13 Dec 2012 10:43:10 -0800 Subject: [PATCH 2/2] Test for #84 --- spec/qunit_spec.js | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/spec/qunit_spec.js b/spec/qunit_spec.js index 9999cb288..3b21ba18b 100644 --- a/spec/qunit_spec.js +++ b/spec/qunit_spec.js @@ -528,6 +528,14 @@ test("Partials with slash paths", function() { shouldCompileToWithPartials(string, [hash, {}, {'shared/dude':dude}], true, "Dudes: Jeepers", "Partials can use literal paths"); }); +test("Partials with integer path", function() { + var string = "Dudes: {{> 404}}"; + var dude = "{{name}}"; + var hash = {name:"Jeepers", another_dude:"Creepers"}; + shouldCompileToWithPartials(string, [hash, {}, {404:dude}], true, "Dudes: Jeepers", "Partials can use literal paths"); +}); + + suite("String literal parameters"); test("simple literals work", function() {