From efca3c8ef5665fe3cf2802f866db0c73755824e0 Mon Sep 17 00:00:00 2001 From: Spike Brehm Date: Mon, 27 May 2013 11:22:25 -0700 Subject: [PATCH 001/200] Support AMD and CommonJS out of the box --- dist/handlebars.js | 19 +++++++++++++++---- dist/handlebars.runtime.js | 19 +++++++++++++++---- lib/handlebars/browser-prefix.js | 5 ++--- lib/handlebars/browser-suffix.js | 14 +++++++++++++- 4 files changed, 45 insertions(+), 12 deletions(-) diff --git a/dist/handlebars.js b/dist/handlebars.js index b0ab5ae25..07d396312 100644 --- a/dist/handlebars.js +++ b/dist/handlebars.js @@ -23,9 +23,8 @@ THE SOFTWARE. */ // lib/handlebars/browser-prefix.js -var Handlebars = {}; - -(function(Handlebars, undefined) { +(function(undefined) { + var Handlebars = {}; ; // lib/handlebars/base.js @@ -2235,5 +2234,17 @@ Handlebars.VM = { Handlebars.template = Handlebars.VM.template; ; // lib/handlebars/browser-suffix.js -})(Handlebars); + if (typeof module === 'object' && module.exports) { + // CommonJS + module.exports = Handlebars; + + } else if (typeof define === "function" && define.amd) { + // AMD modules + define(function() { return Handlebars; }); + + } else { + // other, i.e. browser + this.Handlebars = Handlebars; + } +}).call(this); ; diff --git a/dist/handlebars.runtime.js b/dist/handlebars.runtime.js index 8744aa77b..04ecac3f9 100644 --- a/dist/handlebars.runtime.js +++ b/dist/handlebars.runtime.js @@ -23,9 +23,8 @@ THE SOFTWARE. */ // lib/handlebars/browser-prefix.js -var Handlebars = {}; - -(function(Handlebars, undefined) { +(function(undefined) { + var Handlebars = {}; ; // lib/handlebars/base.js @@ -341,5 +340,17 @@ Handlebars.VM = { Handlebars.template = Handlebars.VM.template; ; // lib/handlebars/browser-suffix.js -})(Handlebars); + if (typeof module === 'object' && module.exports) { + // CommonJS + module.exports = Handlebars; + + } else if (typeof define === "function" && define.amd) { + // AMD modules + define(function() { return Handlebars; }); + + } else { + // other, i.e. browser + this.Handlebars = Handlebars; + } +}).call(this); ; diff --git a/lib/handlebars/browser-prefix.js b/lib/handlebars/browser-prefix.js index 138467d68..a8f172231 100644 --- a/lib/handlebars/browser-prefix.js +++ b/lib/handlebars/browser-prefix.js @@ -1,3 +1,2 @@ -var Handlebars = {}; - -(function(Handlebars, undefined) { +(function(undefined) { + var Handlebars = {}; diff --git a/lib/handlebars/browser-suffix.js b/lib/handlebars/browser-suffix.js index 36c52c091..d357c720a 100644 --- a/lib/handlebars/browser-suffix.js +++ b/lib/handlebars/browser-suffix.js @@ -1 +1,13 @@ -})(Handlebars); + if (typeof module === 'object' && module.exports) { + // CommonJS + module.exports = Handlebars; + + } else if (typeof define === "function" && define.amd) { + // AMD modules + define(function() { return Handlebars; }); + + } else { + // other, i.e. browser + this.Handlebars = Handlebars; + } +}).call(this); From e6b6ae8fdb39be2d188d1b084add7e894057c5bc Mon Sep 17 00:00:00 2001 From: Kevin Decker Date: Fri, 31 May 2013 14:29:13 -0400 Subject: [PATCH 002/200] Add version compat note --- release-notes.md | 1 + 1 file changed, 1 insertion(+) diff --git a/release-notes.md b/release-notes.md index 8e6761944..7de9ef2a0 100644 --- a/release-notes.md +++ b/release-notes.md @@ -21,6 +21,7 @@ Compatibility notes: - The parser is now stricter on `{{{`, requiring that the end token be `}}}`. Templates that do not follow this convention should add the additional brace value. - Code that relies on global the namespace being muted when custom helpers or partials are passed will need to explicitly pass an `undefined` value for any helpers that should not be available. +- The compiler version has changed. Precompiled templates with 1.0.12 or higher must use the 1.0.0 or higher runtime. [Commits](https://github.com/wycats/handlebars.js/compare/v1.0.11...v1.0.12) From 265d6249f7bde57d2b6bc6bd7d005d3ee66f21a3 Mon Sep 17 00:00:00 2001 From: Stanley Stuart Date: Sun, 19 May 2013 10:01:14 -0600 Subject: [PATCH 003/200] publish passing master builds to s3 --- .gitignore | 2 +- .travis.yml | 15 + Gemfile | 1 + Gemfile.lock | 8 + README.markdown | 14 +- Rakefile | 26 + dist/handlebars.js | 2278 ------------------------------------ dist/handlebars.runtime.js | 362 ------ 8 files changed, 64 insertions(+), 2642 deletions(-) create mode 100644 .travis.yml delete mode 100644 dist/handlebars.js delete mode 100644 dist/handlebars.runtime.js diff --git a/.gitignore b/.gitignore index 6e326b1ba..db9d40a00 100644 --- a/.gitignore +++ b/.gitignore @@ -2,7 +2,7 @@ vendor .rvmrc .DS_Store lib/handlebars/compiler/parser.js -dist/*.min.js +dist node_modules *.sublime-project *.sublime-workspace diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 000000000..a3f385064 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,15 @@ +--- +after_success: bundle exec rake publish +env: + global: + - S3_BUCKET_NAME=builds.handlebarsjs.com + - secure: ! 'PJaukuvkBBsSDOqbIcNSSMgb96VVEaIt/eq9GPjXPeFbSd3hXgwhwVE62Lrq + + tJO8BaUfX+PzpiQjEl4D5/KBmvlFZ057Hqmy0zmPOT5mDZfJe8Ja5zyvTMb+ + + KkCWN/tjAp8kawHojE04pn6jIpPdwXFnAYwPhaHbATFrmdt9fdg=' + - secure: ! 'mBcGL2tnmiRujJdV/4fxrVd8E8wn6AW9IQKVcMv8tvOc7i5dOzZ39rpBKLuT + + MRXDtMV1LyLiuKYb1pHj1IyeadEahcLYFfGygF4LG7Yzp4NWHtRzQ7Q8LXaJ + + V7dXDboYCFkn2a8/Rtx1YSVh/sCONf5UoRC+MUIqrj4UiHN9r3s=' diff --git a/Gemfile b/Gemfile index cf092ce28..37d5e93d2 100644 --- a/Gemfile +++ b/Gemfile @@ -3,3 +3,4 @@ source "http://rubygems.org" gem "rake" gem "therubyracer", ">= 0.9.8", "< 0.11" gem "rspec" +gem "aws-sdk" diff --git a/Gemfile.lock b/Gemfile.lock index ce8338f4e..67b86e5a5 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,8 +1,14 @@ GEM remote: http://rubygems.org/ specs: + aws-sdk (1.10.0) + json (~> 1.4) + nokogiri (>= 1.4.4) + uuidtools (~> 2.1) diff-lcs (1.1.3) + json (1.8.0) libv8 (3.3.10.4) + nokogiri (1.5.9) rake (10.0.3) rspec (2.12.0) rspec-core (~> 2.12.0) @@ -14,11 +20,13 @@ GEM rspec-mocks (2.12.1) therubyracer (0.10.2) libv8 (~> 3.3.10) + uuidtools (2.1.4) PLATFORMS ruby DEPENDENCIES + aws-sdk rake rspec therubyracer (>= 0.9.8, < 0.11) diff --git a/README.markdown b/README.markdown index f775815e2..631b96d66 100644 --- a/README.markdown +++ b/README.markdown @@ -1,5 +1,6 @@ [![Build Status](https://travis-ci.org/wycats/handlebars.js.png?branch=master)](https://travis-ci.org/wycats/handlebars.js) + Handlebars.js ============= @@ -11,13 +12,23 @@ keep the view and the code separated like we all know they should be. Checkout the official Handlebars docs site at [http://www.handlebarsjs.com](http://www.handlebarsjs.com). - Installing ---------- Installing Handlebars is easy. Simply download the package [from the official site](http://handlebarsjs.com/) and add it to your web pages (you should usually use the most recent version). +Alternatively, if you prefer having the latest version of handlebars from +the 'master' branch, passing builds of the 'master' branch are automatically +published to S3. You may download the latest passing master build by grabbing +a `handlebars-latest.js` file from the [builds page][builds-page]. When the +build is published, it is also available as a `handlebars-gitSHA.js` file on +the builds page if you need a version to refer to others. +`handlebars-runtime.js` builds are also available. + +**Note**: The S3 builds page is provided as a convenience for the community, +but you should not use it for hosting Handlebars in production. + Usage ----- In general, the syntax of Handlebars.js templates is a superset @@ -389,3 +400,4 @@ License ------- Handlebars.js is released under the MIT license. +[builds-page]: http://builds.handlebarsjs.com.s3.amazonaws.com/index.html diff --git a/Rakefile b/Rakefile index 794686f1e..219f3cfa6 100644 --- a/Rakefile +++ b/Rakefile @@ -134,3 +134,29 @@ task :bench => "vendor" do system "node bench/handlebars.js" end + +task :publish do + access_key_id = ENV['S3_ACCESS_KEY_ID'] + secret_access_key = ENV['S3_SECRET_ACCESS_KEY'] + bucket_name = ENV['S3_BUCKET_NAME'] + rev = `git rev-list HEAD -n 1`.to_s.strip + master_rev = `git rev-list origin/master -n 1`.to_s.strip + upload = true if rev == master_rev + upload = upload && access_key_id && secret_access_key && bucket_name + if upload + require 'aws-sdk' + root = File.expand_path(File.dirname(__FILE__)) + '/dist/' + s3 = AWS::S3.new(access_key_id: access_key_id,secret_access_key: secret_access_key) + bucket = s3.buckets[bucket_name] + files = ['handlebars.js', 'handlebars.runtime.js'].map { |file| root + file } + files.each do |file| + basename = Pathname.new(file).basename.sub_ext('') + s3_objs = ["#{basename}-latest.js", "#{basename}-#{rev}.js"].map do |file| + bucket.objects[file] + end + s3_objs.each { |obj| obj.write(Pathname.new(file)) } + end + else + puts "Not uploading any files to S3!" + end +end diff --git a/dist/handlebars.js b/dist/handlebars.js deleted file mode 100644 index c70f09d1d..000000000 --- a/dist/handlebars.js +++ /dev/null @@ -1,2278 +0,0 @@ -/* - -Copyright (C) 2011 by Yehuda Katz - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -THE SOFTWARE. - -*/ - -// lib/handlebars/browser-prefix.js -var Handlebars = {}; - -(function(Handlebars, undefined) { -; -// lib/handlebars/base.js - -Handlebars.VERSION = "1.0.0"; -Handlebars.COMPILER_REVISION = 4; - -Handlebars.REVISION_CHANGES = { - 1: '<= 1.0.rc.2', // 1.0.rc.2 is actually rev2 but doesn't report it - 2: '== 1.0.0-rc.3', - 3: '== 1.0.0-rc.4', - 4: '>= 1.0.0' -}; - -Handlebars.helpers = {}; -Handlebars.partials = {}; - -var toString = Object.prototype.toString, - functionType = '[object Function]', - objectType = '[object Object]'; - -Handlebars.registerHelper = function(name, fn, inverse) { - if (toString.call(name) === objectType) { - if (inverse || fn) { throw new Handlebars.Exception('Arg not supported with multiple helpers'); } - Handlebars.Utils.extend(this.helpers, name); - } else { - if (inverse) { fn.not = inverse; } - this.helpers[name] = fn; - } -}; - -Handlebars.registerPartial = function(name, str) { - if (toString.call(name) === objectType) { - Handlebars.Utils.extend(this.partials, name); - } else { - this.partials[name] = str; - } -}; - -Handlebars.registerHelper('helperMissing', function(arg) { - if(arguments.length === 2) { - return undefined; - } else { - throw new Error("Missing helper: '" + arg + "'"); - } -}); - -Handlebars.registerHelper('blockHelperMissing', function(context, options) { - var inverse = options.inverse || function() {}, fn = options.fn; - - var type = toString.call(context); - - if(type === functionType) { context = context.call(this); } - - if(context === true) { - return fn(this); - } else if(context === false || context == null) { - return inverse(this); - } else if(type === "[object Array]") { - if(context.length > 0) { - return Handlebars.helpers.each(context, options); - } else { - return inverse(this); - } - } else { - return fn(context); - } -}); - -Handlebars.K = function() {}; - -Handlebars.createFrame = Object.create || function(object) { - Handlebars.K.prototype = object; - var obj = new Handlebars.K(); - Handlebars.K.prototype = null; - return obj; -}; - -Handlebars.logger = { - DEBUG: 0, INFO: 1, WARN: 2, ERROR: 3, level: 3, - - methodMap: {0: 'debug', 1: 'info', 2: 'warn', 3: 'error'}, - - // can be overridden in the host environment - log: function(level, obj) { - if (Handlebars.logger.level <= level) { - var method = Handlebars.logger.methodMap[level]; - if (typeof console !== 'undefined' && console[method]) { - console[method].call(console, obj); - } - } - } -}; - -Handlebars.log = function(level, obj) { Handlebars.logger.log(level, obj); }; - -Handlebars.registerHelper('each', function(context, options) { - var fn = options.fn, inverse = options.inverse; - var i = 0, ret = "", data; - - var type = toString.call(context); - if(type === functionType) { context = context.call(this); } - - if (options.data) { - data = Handlebars.createFrame(options.data); - } - - if(context && typeof context === 'object') { - if(context instanceof Array){ - for(var j = context.length; i 2) { - expected.push("'" + this.terminals_[p] + "'"); - } - if (this.lexer.showPosition) { - errStr = "Parse error on line " + (yylineno + 1) + ":\n" + this.lexer.showPosition() + "\nExpecting " + expected.join(", ") + ", got '" + (this.terminals_[symbol] || symbol) + "'"; - } else { - errStr = "Parse error on line " + (yylineno + 1) + ": Unexpected " + (symbol == 1?"end of input":"'" + (this.terminals_[symbol] || symbol) + "'"); - } - this.parseError(errStr, {text: this.lexer.match, token: this.terminals_[symbol] || symbol, line: this.lexer.yylineno, loc: yyloc, expected: expected}); - } - } - if (action[0] instanceof Array && action.length > 1) { - throw new Error("Parse Error: multiple actions possible at state: " + state + ", token: " + symbol); - } - switch (action[0]) { - case 1: - stack.push(symbol); - vstack.push(this.lexer.yytext); - lstack.push(this.lexer.yylloc); - stack.push(action[1]); - symbol = null; - if (!preErrorSymbol) { - yyleng = this.lexer.yyleng; - yytext = this.lexer.yytext; - yylineno = this.lexer.yylineno; - yyloc = this.lexer.yylloc; - if (recovering > 0) - recovering--; - } else { - symbol = preErrorSymbol; - preErrorSymbol = null; - } - break; - case 2: - len = this.productions_[action[1]][1]; - yyval.$ = vstack[vstack.length - len]; - yyval._$ = {first_line: lstack[lstack.length - (len || 1)].first_line, last_line: lstack[lstack.length - 1].last_line, first_column: lstack[lstack.length - (len || 1)].first_column, last_column: lstack[lstack.length - 1].last_column}; - if (ranges) { - yyval._$.range = [lstack[lstack.length - (len || 1)].range[0], lstack[lstack.length - 1].range[1]]; - } - r = this.performAction.call(yyval, yytext, yyleng, yylineno, this.yy, action[1], vstack, lstack); - if (typeof r !== "undefined") { - return r; - } - if (len) { - stack = stack.slice(0, -1 * len * 2); - vstack = vstack.slice(0, -1 * len); - lstack = lstack.slice(0, -1 * len); - } - stack.push(this.productions_[action[1]][0]); - vstack.push(yyval.$); - lstack.push(yyval._$); - newState = table[stack[stack.length - 2]][stack[stack.length - 1]]; - stack.push(newState); - break; - case 3: - return true; - } - } - return true; -} -}; -/* Jison generated lexer */ -var lexer = (function(){ -var lexer = ({EOF:1, -parseError:function parseError(str, hash) { - if (this.yy.parser) { - this.yy.parser.parseError(str, hash); - } else { - throw new Error(str); - } - }, -setInput:function (input) { - this._input = input; - this._more = this._less = this.done = false; - this.yylineno = this.yyleng = 0; - this.yytext = this.matched = this.match = ''; - this.conditionStack = ['INITIAL']; - this.yylloc = {first_line:1,first_column:0,last_line:1,last_column:0}; - if (this.options.ranges) this.yylloc.range = [0,0]; - this.offset = 0; - return this; - }, -input:function () { - var ch = this._input[0]; - this.yytext += ch; - this.yyleng++; - this.offset++; - this.match += ch; - this.matched += ch; - var lines = ch.match(/(?:\r\n?|\n).*/g); - if (lines) { - this.yylineno++; - this.yylloc.last_line++; - } else { - this.yylloc.last_column++; - } - if (this.options.ranges) this.yylloc.range[1]++; - - this._input = this._input.slice(1); - return ch; - }, -unput:function (ch) { - var len = ch.length; - var lines = ch.split(/(?:\r\n?|\n)/g); - - this._input = ch + this._input; - this.yytext = this.yytext.substr(0, this.yytext.length-len-1); - //this.yyleng -= len; - this.offset -= len; - var oldLines = this.match.split(/(?:\r\n?|\n)/g); - this.match = this.match.substr(0, this.match.length-1); - this.matched = this.matched.substr(0, this.matched.length-1); - - if (lines.length-1) this.yylineno -= lines.length-1; - var r = this.yylloc.range; - - this.yylloc = {first_line: this.yylloc.first_line, - last_line: this.yylineno+1, - first_column: this.yylloc.first_column, - last_column: lines ? - (lines.length === oldLines.length ? this.yylloc.first_column : 0) + oldLines[oldLines.length - lines.length].length - lines[0].length: - this.yylloc.first_column - len - }; - - if (this.options.ranges) { - this.yylloc.range = [r[0], r[0] + this.yyleng - len]; - } - return this; - }, -more:function () { - this._more = true; - return this; - }, -less:function (n) { - this.unput(this.match.slice(n)); - }, -pastInput:function () { - var past = this.matched.substr(0, this.matched.length - this.match.length); - return (past.length > 20 ? '...':'') + past.substr(-20).replace(/\n/g, ""); - }, -upcomingInput:function () { - var next = this.match; - if (next.length < 20) { - next += this._input.substr(0, 20-next.length); - } - return (next.substr(0,20)+(next.length > 20 ? '...':'')).replace(/\n/g, ""); - }, -showPosition:function () { - var pre = this.pastInput(); - var c = new Array(pre.length + 1).join("-"); - return pre + this.upcomingInput() + "\n" + c+"^"; - }, -next:function () { - if (this.done) { - return this.EOF; - } - if (!this._input) this.done = true; - - var token, - match, - tempMatch, - index, - col, - lines; - if (!this._more) { - this.yytext = ''; - this.match = ''; - } - var rules = this._currentRules(); - for (var i=0;i < rules.length; i++) { - tempMatch = this._input.match(this.rules[rules[i]]); - if (tempMatch && (!match || tempMatch[0].length > match[0].length)) { - match = tempMatch; - index = i; - if (!this.options.flex) break; - } - } - if (match) { - lines = match[0].match(/(?:\r\n?|\n).*/g); - if (lines) this.yylineno += lines.length; - this.yylloc = {first_line: this.yylloc.last_line, - last_line: this.yylineno+1, - first_column: this.yylloc.last_column, - last_column: lines ? lines[lines.length-1].length-lines[lines.length-1].match(/\r?\n?/)[0].length : this.yylloc.last_column + match[0].length}; - this.yytext += match[0]; - this.match += match[0]; - this.matches = match; - this.yyleng = this.yytext.length; - if (this.options.ranges) { - this.yylloc.range = [this.offset, this.offset += this.yyleng]; - } - this._more = false; - this._input = this._input.slice(match[0].length); - this.matched += match[0]; - token = this.performAction.call(this, this.yy, this, rules[index],this.conditionStack[this.conditionStack.length-1]); - if (this.done && this._input) this.done = false; - if (token) return token; - else return; - } - if (this._input === "") { - return this.EOF; - } else { - return this.parseError('Lexical error on line '+(this.yylineno+1)+'. Unrecognized text.\n'+this.showPosition(), - {text: "", token: null, line: this.yylineno}); - } - }, -lex:function lex() { - var r = this.next(); - if (typeof r !== 'undefined') { - return r; - } else { - return this.lex(); - } - }, -begin:function begin(condition) { - this.conditionStack.push(condition); - }, -popState:function popState() { - return this.conditionStack.pop(); - }, -_currentRules:function _currentRules() { - return this.conditions[this.conditionStack[this.conditionStack.length-1]].rules; - }, -topState:function () { - return this.conditionStack[this.conditionStack.length-2]; - }, -pushState:function begin(condition) { - this.begin(condition); - }}); -lexer.options = {}; -lexer.performAction = function anonymous(yy,yy_,$avoiding_name_collisions,YY_START) { - -var YYSTATE=YY_START -switch($avoiding_name_collisions) { -case 0: yy_.yytext = "\\"; return 14; -break; -case 1: - if(yy_.yytext.slice(-1) !== "\\") this.begin("mu"); - if(yy_.yytext.slice(-1) === "\\") yy_.yytext = yy_.yytext.substr(0,yy_.yyleng-1), this.begin("emu"); - if(yy_.yytext) return 14; - -break; -case 2: return 14; -break; -case 3: - if(yy_.yytext.slice(-1) !== "\\") this.popState(); - if(yy_.yytext.slice(-1) === "\\") yy_.yytext = yy_.yytext.substr(0,yy_.yyleng-1); - return 14; - -break; -case 4: yy_.yytext = yy_.yytext.substr(0, yy_.yyleng-4); this.popState(); return 15; -break; -case 5: return 25; -break; -case 6: return 16; -break; -case 7: return 20; -break; -case 8: return 19; -break; -case 9: return 19; -break; -case 10: return 23; -break; -case 11: return 22; -break; -case 12: this.popState(); this.begin('com'); -break; -case 13: yy_.yytext = yy_.yytext.substr(3,yy_.yyleng-5); this.popState(); return 15; -break; -case 14: return 22; -break; -case 15: return 37; -break; -case 16: return 36; -break; -case 17: return 36; -break; -case 18: return 40; -break; -case 19: /*ignore whitespace*/ -break; -case 20: this.popState(); return 24; -break; -case 21: this.popState(); return 18; -break; -case 22: yy_.yytext = yy_.yytext.substr(1,yy_.yyleng-2).replace(/\\"/g,'"'); return 31; -break; -case 23: yy_.yytext = yy_.yytext.substr(1,yy_.yyleng-2).replace(/\\'/g,"'"); return 31; -break; -case 24: return 38; -break; -case 25: return 33; -break; -case 26: return 33; -break; -case 27: return 32; -break; -case 28: return 36; -break; -case 29: yy_.yytext = yy_.yytext.substr(1, yy_.yyleng-2); return 36; -break; -case 30: return 'INVALID'; -break; -case 31: return 5; -break; -} -}; -lexer.rules = [/^(?:\\\\(?=(\{\{)))/,/^(?:[^\x00]*?(?=(\{\{)))/,/^(?:[^\x00]+)/,/^(?:[^\x00]{2,}?(?=(\{\{|$)))/,/^(?:[\s\S]*?--\}\})/,/^(?:\{\{>)/,/^(?:\{\{#)/,/^(?:\{\{\/)/,/^(?:\{\{\^)/,/^(?:\{\{\s*else\b)/,/^(?:\{\{\{)/,/^(?:\{\{&)/,/^(?:\{\{!--)/,/^(?:\{\{![\s\S]*?\}\})/,/^(?:\{\{)/,/^(?:=)/,/^(?:\.(?=[}\/ ]))/,/^(?:\.\.)/,/^(?:[\/.])/,/^(?:\s+)/,/^(?:\}\}\})/,/^(?:\}\})/,/^(?:"(\\["]|[^"])*")/,/^(?:'(\\[']|[^'])*')/,/^(?:@)/,/^(?:true(?=[}\s]))/,/^(?:false(?=[}\s]))/,/^(?:-?[0-9]+(?=[}\s]))/,/^(?:[^\s!"#%-,\.\/;->@\[-\^`\{-~]+(?=[=}\s\/.]))/,/^(?:\[[^\]]*\])/,/^(?:.)/,/^(?:$)/]; -lexer.conditions = {"mu":{"rules":[5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31],"inclusive":false},"emu":{"rules":[3],"inclusive":false},"com":{"rules":[4],"inclusive":false},"INITIAL":{"rules":[0,1,2,31],"inclusive":true}}; -return lexer;})() -parser.lexer = lexer; -function Parser () { this.yy = {}; }Parser.prototype = parser;parser.Parser = Parser; -return new Parser; -})();; -// lib/handlebars/compiler/base.js - -Handlebars.Parser = handlebars; - -Handlebars.parse = function(input) { - - // Just return if an already-compile AST was passed in. - if(input.constructor === Handlebars.AST.ProgramNode) { return input; } - - Handlebars.Parser.yy = Handlebars.AST; - return Handlebars.Parser.parse(input); -}; -; -// lib/handlebars/compiler/ast.js -Handlebars.AST = {}; - -Handlebars.AST.ProgramNode = function(statements, inverse) { - this.type = "program"; - this.statements = statements; - if(inverse) { this.inverse = new Handlebars.AST.ProgramNode(inverse); } -}; - -Handlebars.AST.MustacheNode = function(rawParams, hash, unescaped) { - this.type = "mustache"; - this.escaped = !unescaped; - this.hash = hash; - - var id = this.id = rawParams[0]; - var params = this.params = rawParams.slice(1); - - // a mustache is an eligible helper if: - // * its id is simple (a single part, not `this` or `..`) - var eligibleHelper = this.eligibleHelper = id.isSimple; - - // a mustache is definitely a helper if: - // * it is an eligible helper, and - // * it has at least one parameter or hash segment - this.isHelper = eligibleHelper && (params.length || hash); - - // if a mustache is an eligible helper but not a definite - // helper, it is ambiguous, and will be resolved in a later - // pass or at runtime. -}; - -Handlebars.AST.PartialNode = function(partialName, context) { - this.type = "partial"; - this.partialName = partialName; - this.context = context; -}; - -Handlebars.AST.BlockNode = function(mustache, program, inverse, close) { - var verifyMatch = function(open, close) { - if(open.original !== close.original) { - throw new Handlebars.Exception(open.original + " doesn't match " + close.original); - } - }; - - verifyMatch(mustache.id, close); - this.type = "block"; - this.mustache = mustache; - this.program = program; - this.inverse = inverse; - - if (this.inverse && !this.program) { - this.isInverse = true; - } -}; - -Handlebars.AST.ContentNode = function(string) { - this.type = "content"; - this.string = string; -}; - -Handlebars.AST.HashNode = function(pairs) { - this.type = "hash"; - this.pairs = pairs; -}; - -Handlebars.AST.IdNode = function(parts) { - this.type = "ID"; - - var original = "", - dig = [], - depth = 0; - - for(var i=0,l=parts.length; i 0) { throw new Handlebars.Exception("Invalid path: " + original); } - else if (part === "..") { depth++; } - else { this.isScoped = true; } - } - else { dig.push(part); } - } - - this.original = original; - this.parts = dig; - this.string = dig.join('.'); - this.depth = depth; - - // an ID is simple if it only has one part, and that part is not - // `..` or `this`. - this.isSimple = parts.length === 1 && !this.isScoped && depth === 0; - - this.stringModeValue = this.string; -}; - -Handlebars.AST.PartialNameNode = function(name) { - this.type = "PARTIAL_NAME"; - this.name = name.original; -}; - -Handlebars.AST.DataNode = function(id) { - this.type = "DATA"; - this.id = id; -}; - -Handlebars.AST.StringNode = function(string) { - this.type = "STRING"; - this.original = - this.string = - this.stringModeValue = string; -}; - -Handlebars.AST.IntegerNode = function(integer) { - this.type = "INTEGER"; - this.original = - this.integer = integer; - this.stringModeValue = Number(integer); -}; - -Handlebars.AST.BooleanNode = function(bool) { - this.type = "BOOLEAN"; - this.bool = bool; - this.stringModeValue = bool === "true"; -}; - -Handlebars.AST.CommentNode = function(comment) { - this.type = "comment"; - this.comment = comment; -}; -; -// lib/handlebars/utils.js - -var errorProps = ['description', 'fileName', 'lineNumber', 'message', 'name', 'number', 'stack']; - -Handlebars.Exception = function(message) { - var tmp = Error.prototype.constructor.apply(this, arguments); - - // Unfortunately errors are not enumerable in Chrome (at least), so `for prop in tmp` doesn't work. - for (var idx = 0; idx < errorProps.length; idx++) { - this[errorProps[idx]] = tmp[errorProps[idx]]; - } -}; -Handlebars.Exception.prototype = new Error(); - -// Build out our basic SafeString type -Handlebars.SafeString = function(string) { - this.string = string; -}; -Handlebars.SafeString.prototype.toString = function() { - return this.string.toString(); -}; - -var escape = { - "&": "&", - "<": "<", - ">": ">", - '"': """, - "'": "'", - "`": "`" -}; - -var badChars = /[&<>"'`]/g; -var possible = /[&<>"'`]/; - -var escapeChar = function(chr) { - return escape[chr] || "&"; -}; - -Handlebars.Utils = { - extend: function(obj, value) { - for(var key in value) { - if(value.hasOwnProperty(key)) { - obj[key] = value[key]; - } - } - }, - - 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 === false) { - return ""; - } - - // Force a string conversion as this will be done by the append regardless and - // the regex test will do this transparently behind the scenes, causing issues if - // an object's to string has escaped characters in it. - string = string.toString(); - - if(!possible.test(string)) { return string; } - return string.replace(badChars, escapeChar); - }, - - isEmpty: function(value) { - if (!value && value !== 0) { - return true; - } else if(toString.call(value) === "[object Array]" && value.length === 0) { - return true; - } else { - return false; - } - } -}; -; -// lib/handlebars/compiler/compiler.js - -/*jshint eqnull:true*/ -var Compiler = Handlebars.Compiler = function() {}; -var JavaScriptCompiler = Handlebars.JavaScriptCompiler = function() {}; - -// the foundHelper register will disambiguate helper lookup from finding a -// function in a context. This is necessary for mustache compatibility, which -// requires that context functions in blocks are evaluated by blockHelperMissing, -// and then proceed as if the resulting value was provided to blockHelperMissing. - -Compiler.prototype = { - compiler: Compiler, - - disassemble: function() { - var opcodes = this.opcodes, opcode, out = [], params, param; - - for (var i=0, l=opcodes.length; i 0) { - this.source[1] = this.source[1] + ", " + locals.join(", "); - } - - // Generate minimizer alias mappings - if (!this.isChild) { - for (var alias in this.context.aliases) { - if (this.context.aliases.hasOwnProperty(alias)) { - this.source[1] = this.source[1] + ', ' + alias + '=' + this.context.aliases[alias]; - } - } - } - - if (this.source[1]) { - this.source[1] = "var " + this.source[1].substring(2) + ";"; - } - - // Merge children - if (!this.isChild) { - this.source[1] += '\n' + this.context.programs.join('\n') + '\n'; - } - - if (!this.environment.isSimple) { - this.source.push("return buffer;"); - } - - var params = this.isChild ? ["depth0", "data"] : ["Handlebars", "depth0", "helpers", "partials", "data"]; - - for(var i=0, l=this.environment.depths.list.length; i this.stackVars.length) { this.stackVars.push("stack" + this.stackSlot); } - return this.topStackName(); - }, - topStackName: function() { - return "stack" + this.stackSlot; - }, - flushInline: function() { - var inlineStack = this.inlineStack; - if (inlineStack.length) { - this.inlineStack = []; - for (var i = 0, len = inlineStack.length; i < len; i++) { - var entry = inlineStack[i]; - if (entry instanceof Literal) { - this.compileStack.push(entry); - } else { - this.pushStack(entry); - } - } - } - }, - isInline: function() { - return this.inlineStack.length; - }, - - popStack: function(wrapped) { - var inline = this.isInline(), - item = (inline ? this.inlineStack : this.compileStack).pop(); - - if (!wrapped && (item instanceof Literal)) { - return item.value; - } else { - if (!inline) { - this.stackSlot--; - } - return item; - } - }, - - topStack: function(wrapped) { - var stack = (this.isInline() ? this.inlineStack : this.compileStack), - item = stack[stack.length - 1]; - - if (!wrapped && (item instanceof Literal)) { - return item.value; - } else { - return item; - } - }, - - quotedString: function(str) { - return '"' + str - .replace(/\\/g, '\\\\') - .replace(/"/g, '\\"') - .replace(/\n/g, '\\n') - .replace(/\r/g, '\\r') - .replace(/\u2028/g, '\\u2028') // Per Ecma-262 7.3 + 7.8.4 - .replace(/\u2029/g, '\\u2029') + '"'; - }, - - setupHelper: function(paramSize, name, missingParams) { - var params = []; - this.setupParams(paramSize, params, missingParams); - var foundHelper = this.nameLookup('helpers', name, 'helper'); - - return { - params: params, - name: foundHelper, - callParams: ["depth0"].concat(params).join(", "), - helperMissingParams: missingParams && ["depth0", this.quotedString(name)].concat(params).join(", ") - }; - }, - - // the params and contexts arguments are passed in arrays - // to fill in - setupParams: function(paramSize, params, useRegister) { - var options = [], contexts = [], types = [], param, inverse, program; - - options.push("hash:" + this.popStack()); - - inverse = this.popStack(); - program = this.popStack(); - - // Avoid setting fn and inverse if neither are set. This allows - // helpers to do a check for `if (options.fn)` - if (program || inverse) { - if (!program) { - this.context.aliases.self = "this"; - program = "self.noop"; - } - - if (!inverse) { - this.context.aliases.self = "this"; - inverse = "self.noop"; - } - - options.push("inverse:" + inverse); - options.push("fn:" + program); - } - - for(var i=0; i= 1.0.0' -}; - -Handlebars.helpers = {}; -Handlebars.partials = {}; - -var toString = Object.prototype.toString, - functionType = '[object Function]', - objectType = '[object Object]'; - -Handlebars.registerHelper = function(name, fn, inverse) { - if (toString.call(name) === objectType) { - if (inverse || fn) { throw new Handlebars.Exception('Arg not supported with multiple helpers'); } - Handlebars.Utils.extend(this.helpers, name); - } else { - if (inverse) { fn.not = inverse; } - this.helpers[name] = fn; - } -}; - -Handlebars.registerPartial = function(name, str) { - if (toString.call(name) === objectType) { - Handlebars.Utils.extend(this.partials, name); - } else { - this.partials[name] = str; - } -}; - -Handlebars.registerHelper('helperMissing', function(arg) { - if(arguments.length === 2) { - return undefined; - } else { - throw new Error("Missing helper: '" + arg + "'"); - } -}); - -Handlebars.registerHelper('blockHelperMissing', function(context, options) { - var inverse = options.inverse || function() {}, fn = options.fn; - - var type = toString.call(context); - - if(type === functionType) { context = context.call(this); } - - if(context === true) { - return fn(this); - } else if(context === false || context == null) { - return inverse(this); - } else if(type === "[object Array]") { - if(context.length > 0) { - return Handlebars.helpers.each(context, options); - } else { - return inverse(this); - } - } else { - return fn(context); - } -}); - -Handlebars.K = function() {}; - -Handlebars.createFrame = Object.create || function(object) { - Handlebars.K.prototype = object; - var obj = new Handlebars.K(); - Handlebars.K.prototype = null; - return obj; -}; - -Handlebars.logger = { - DEBUG: 0, INFO: 1, WARN: 2, ERROR: 3, level: 3, - - methodMap: {0: 'debug', 1: 'info', 2: 'warn', 3: 'error'}, - - // can be overridden in the host environment - log: function(level, obj) { - if (Handlebars.logger.level <= level) { - var method = Handlebars.logger.methodMap[level]; - if (typeof console !== 'undefined' && console[method]) { - console[method].call(console, obj); - } - } - } -}; - -Handlebars.log = function(level, obj) { Handlebars.logger.log(level, obj); }; - -Handlebars.registerHelper('each', function(context, options) { - var fn = options.fn, inverse = options.inverse; - var i = 0, ret = "", data; - - var type = toString.call(context); - if(type === functionType) { context = context.call(this); } - - if (options.data) { - data = Handlebars.createFrame(options.data); - } - - if(context && typeof context === 'object') { - if(context instanceof Array){ - for(var j = context.length; i": ">", - '"': """, - "'": "'", - "`": "`" -}; - -var badChars = /[&<>"'`]/g; -var possible = /[&<>"'`]/; - -var escapeChar = function(chr) { - return escape[chr] || "&"; -}; - -Handlebars.Utils = { - extend: function(obj, value) { - for(var key in value) { - if(value.hasOwnProperty(key)) { - obj[key] = value[key]; - } - } - }, - - 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 === false) { - return ""; - } - - // Force a string conversion as this will be done by the append regardless and - // the regex test will do this transparently behind the scenes, causing issues if - // an object's to string has escaped characters in it. - string = string.toString(); - - if(!possible.test(string)) { return string; } - return string.replace(badChars, escapeChar); - }, - - isEmpty: function(value) { - if (!value && value !== 0) { - return true; - } else if(toString.call(value) === "[object Array]" && value.length === 0) { - return true; - } else { - return false; - } - } -}; -; -// lib/handlebars/runtime.js - -Handlebars.VM = { - template: function(templateSpec) { - // Just add water - var container = { - escapeExpression: Handlebars.Utils.escapeExpression, - invokePartial: Handlebars.VM.invokePartial, - programs: [], - program: function(i, fn, data) { - var programWrapper = this.programs[i]; - if(data) { - programWrapper = Handlebars.VM.program(i, fn, data); - } else if (!programWrapper) { - programWrapper = this.programs[i] = Handlebars.VM.program(i, fn); - } - return programWrapper; - }, - merge: function(param, common) { - var ret = param || common; - - if (param && common) { - ret = {}; - Handlebars.Utils.extend(ret, common); - Handlebars.Utils.extend(ret, param); - } - return ret; - }, - programWithDepth: Handlebars.VM.programWithDepth, - noop: Handlebars.VM.noop, - compilerInfo: null - }; - - return function(context, options) { - options = options || {}; - var result = templateSpec.call(container, Handlebars, context, options.helpers, options.partials, options.data); - - var compilerInfo = container.compilerInfo || [], - compilerRevision = compilerInfo[0] || 1, - currentRevision = Handlebars.COMPILER_REVISION; - - if (compilerRevision !== currentRevision) { - if (compilerRevision < currentRevision) { - var runtimeVersions = Handlebars.REVISION_CHANGES[currentRevision], - compilerVersions = Handlebars.REVISION_CHANGES[compilerRevision]; - throw "Template was precompiled with an older version of Handlebars than the current runtime. "+ - "Please update your precompiler to a newer version ("+runtimeVersions+") or downgrade your runtime to an older version ("+compilerVersions+")."; - } else { - // Use the embedded version info since the runtime doesn't know about this revision yet - throw "Template was precompiled with a newer version of Handlebars than the current runtime. "+ - "Please update your runtime to a newer version ("+compilerInfo[1]+")."; - } - } - - return result; - }; - }, - - programWithDepth: function(i, fn, data /*, $depth */) { - var args = Array.prototype.slice.call(arguments, 3); - - var program = function(context, options) { - options = options || {}; - - return fn.apply(this, [context, options.data || data].concat(args)); - }; - program.program = i; - program.depth = args.length; - return program; - }, - program: function(i, fn, data) { - var program = function(context, options) { - options = options || {}; - - return fn(context, options.data || data); - }; - program.program = i; - program.depth = 0; - return program; - }, - noop: function() { return ""; }, - invokePartial: function(partial, name, context, helpers, partials, data) { - var options = { helpers: helpers, partials: partials, data: data }; - - if(partial === undefined) { - throw new Handlebars.Exception("The partial " + name + " could not be found"); - } else if(partial instanceof Function) { - return partial(context, options); - } else if (!Handlebars.compile) { - throw new Handlebars.Exception("The partial " + name + " could not be compiled when running in runtime-only mode"); - } else { - partials[name] = Handlebars.compile(partial, {data: data !== undefined}); - return partials[name](context, options); - } - } -}; - -Handlebars.template = Handlebars.VM.template; -; -// lib/handlebars/browser-suffix.js -})(Handlebars); -; From 8275242960ee7a7a8795795ad8eebdbd0a5e955d Mon Sep 17 00:00:00 2001 From: kpdecker Date: Fri, 31 May 2013 15:24:26 -0400 Subject: [PATCH 004/200] Preserve license comment on min --- Rakefile | 2 +- dist/handlebars.js | 1 + dist/handlebars.runtime.js | 1 + 3 files changed, 3 insertions(+), 1 deletion(-) diff --git a/Rakefile b/Rakefile index 794686f1e..d2b7d924a 100644 --- a/Rakefile +++ b/Rakefile @@ -61,7 +61,7 @@ def build_for_task(task) FileUtils.rm_rf("dist/*") if File.directory?("dist") FileUtils.mkdir_p("dist") - contents = ["/*\n\n" + File.read('LICENSE') + "\n*/\n"] + contents = ["/*\n\n" + File.read('LICENSE') + "\n@license\n*/\n"] task.prerequisites.each do |filename| next if filename == "dist" diff --git a/dist/handlebars.js b/dist/handlebars.js index c70f09d1d..751f60666 100644 --- a/dist/handlebars.js +++ b/dist/handlebars.js @@ -20,6 +20,7 @@ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +@license */ // lib/handlebars/browser-prefix.js diff --git a/dist/handlebars.runtime.js b/dist/handlebars.runtime.js index b9f4e4920..543bb5857 100644 --- a/dist/handlebars.runtime.js +++ b/dist/handlebars.runtime.js @@ -20,6 +20,7 @@ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +@license */ // lib/handlebars/browser-prefix.js From 7f82f27c5f240f20f6432bd90dacf57c9ccfdded Mon Sep 17 00:00:00 2001 From: kpdecker Date: Sat, 1 Jun 2013 13:26:46 -0400 Subject: [PATCH 005/200] Add mocha bdd globals to jshint --- .jshintrc | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/.jshintrc b/.jshintrc index f37931784..f0a233ab9 100644 --- a/.jshintrc +++ b/.jshintrc @@ -21,7 +21,10 @@ "stop", "ok", "strictEqual", - "module" + "module", + + "describe", + "it" ], "node" : true, From d13ae310d36a27fcbc333570f9b7ae9c7e641392 Mon Sep 17 00:00:00 2001 From: kpdecker Date: Sat, 1 Jun 2013 16:25:45 -0500 Subject: [PATCH 006/200] Convert parser and tokenizer tests to javascript --- package.json | 2 +- spec/parser.js | 170 ++++++++++++++++ spec/parser_spec.rb | 426 ----------------------------------------- spec/tokenizer.js | 317 ++++++++++++++++++++++++++++++ spec/tokenizer_spec.rb | 322 ------------------------------- 5 files changed, 488 insertions(+), 749 deletions(-) create mode 100644 spec/parser.js delete mode 100644 spec/parser_spec.rb create mode 100644 spec/tokenizer.js delete mode 100644 spec/tokenizer_spec.rb diff --git a/package.json b/package.json index b469398f2..e64779197 100644 --- a/package.json +++ b/package.json @@ -29,7 +29,7 @@ "handlebars": "bin/handlebars" }, "scripts": { - "test": "node_modules/.bin/mocha -u qunit spec/qunit_spec.js" + "test": "node_modules/.bin/mocha spec/tokenizer.js spec/parser.js && node_modules/.bin/mocha -u qunit spec/qunit_spec.js" }, "optionalDependencies": {} } diff --git a/spec/parser.js b/spec/parser.js new file mode 100644 index 000000000..6fb8d9a37 --- /dev/null +++ b/spec/parser.js @@ -0,0 +1,170 @@ +var Handlebars = require('../lib/handlebars'); + +require('should'); + +describe('parser', function() { + function ast_for(template) { + var ast = Handlebars.parse(template); + return Handlebars.print(ast); + } + + it('parses simple mustaches', function() { + ast_for('{{foo}}').should.equal("{{ ID:foo [] }}\n"); + ast_for('{{foo?}}').should.equal("{{ ID:foo? [] }}\n"); + ast_for('{{foo_}}').should.equal("{{ ID:foo_ [] }}\n"); + ast_for('{{foo-}}').should.equal("{{ ID:foo- [] }}\n"); + ast_for('{{foo:}}').should.equal("{{ ID:foo: [] }}\n"); + }); + + it('parses simple mustaches with data', function() { + ast_for("{{@foo}}").should.equal("{{ @ID:foo [] }}\n"); + }); + + it('parses mustaches with paths', function() { + ast_for("{{foo/bar}}").should.equal("{{ PATH:foo/bar [] }}\n"); + }); + + it('parses mustaches with this/foo', function() { + ast_for("{{this/foo}}").should.equal("{{ ID:foo [] }}\n"); + }); + + it('parses mustaches with - in a path', function() { + ast_for("{{foo-bar}}").should.equal("{{ ID:foo-bar [] }}\n"); + }); + + it('parses mustaches with parameters', function() { + ast_for("{{foo bar}}").should.equal("{{ ID:foo [ID:bar] }}\n"); + }); + + it('parses mustaches with string parameters', function() { + ast_for("{{foo bar \"baz\" }}").should.equal('{{ ID:foo [ID:bar, "baz"] }}\n'); + }); + + it('parses mustaches with INTEGER parameters', function() { + ast_for("{{foo 1}}").should.equal("{{ ID:foo [INTEGER{1}] }}\n"); + }); + + it('parses mustaches with BOOLEAN parameters', function() { + ast_for("{{foo true}}").should.equal("{{ ID:foo [BOOLEAN{true}] }}\n"); + ast_for("{{foo false}}").should.equal("{{ ID:foo [BOOLEAN{false}] }}\n"); + }); + + it('parses mutaches with DATA parameters', function() { + ast_for("{{foo @bar}}").should.equal("{{ ID:foo [@ID:bar] }}\n"); + }); + + it('parses mustaches with hash arguments', function() { + ast_for("{{foo bar=baz}}").should.equal("{{ ID:foo [] HASH{bar=ID:baz} }}\n"); + ast_for("{{foo bar=1}}").should.equal("{{ ID:foo [] HASH{bar=INTEGER{1}} }}\n"); + ast_for("{{foo bar=true}}").should.equal("{{ ID:foo [] HASH{bar=BOOLEAN{true}} }}\n"); + ast_for("{{foo bar=false}}").should.equal("{{ ID:foo [] HASH{bar=BOOLEAN{false}} }}\n"); + ast_for("{{foo bar=@baz}}").should.equal("{{ ID:foo [] HASH{bar=@ID:baz} }}\n"); + + ast_for("{{foo bar=baz bat=bam}}").should.equal("{{ ID:foo [] HASH{bar=ID:baz, bat=ID:bam} }}\n"); + ast_for("{{foo bar=baz bat=\"bam\"}}").should.equal('{{ ID:foo [] HASH{bar=ID:baz, bat="bam"} }}\n'); + + ast_for("{{foo bat='bam'}}").should.equal('{{ ID:foo [] HASH{bat="bam"} }}\n'); + + ast_for("{{foo omg bar=baz bat=\"bam\"}}").should.equal('{{ ID:foo [ID:omg] HASH{bar=ID:baz, bat="bam"} }}\n'); + ast_for("{{foo omg bar=baz bat=\"bam\" baz=1}}").should.equal('{{ ID:foo [ID:omg] HASH{bar=ID:baz, bat="bam", baz=INTEGER{1}} }}\n'); + ast_for("{{foo omg bar=baz bat=\"bam\" baz=true}}").should.equal('{{ ID:foo [ID:omg] HASH{bar=ID:baz, bat="bam", baz=BOOLEAN{true}} }}\n'); + ast_for("{{foo omg bar=baz bat=\"bam\" baz=false}}").should.equal('{{ ID:foo [ID:omg] HASH{bar=ID:baz, bat="bam", baz=BOOLEAN{false}} }}\n'); + }); + + it('parses contents followed by a mustache', function() { + ast_for("foo bar {{baz}}").should.equal("CONTENT[ \'foo bar \' ]\n{{ ID:baz [] }}\n"); + }); + + it('parses a partial', function() { + ast_for("{{> foo }}").should.equal("{{> PARTIAL:foo }}\n"); + }); + + it('parses a partial with context', function() { + ast_for("{{> foo bar}}").should.equal("{{> PARTIAL:foo ID:bar }}\n"); + }); + + it('parses a partial with a complex name', function() { + ast_for("{{> shared/partial?.bar}}").should.equal("{{> PARTIAL:shared/partial?.bar }}\n"); + }); + + it('parses a comment', function() { + ast_for("{{! this is a comment }}").should.equal("{{! ' this is a comment ' }}\n"); + }); + + it('parses a multi-line comment', function() { + ast_for("{{!\nthis is a multi-line comment\n}}").should.equal("{{! \'\nthis is a multi-line comment\n\' }}\n"); + }); + + it('parses an inverse section', function() { + ast_for("{{#foo}} bar {{^}} baz {{/foo}}").should.equal("BLOCK:\n {{ ID:foo [] }}\n PROGRAM:\n CONTENT[ ' bar ' ]\n {{^}}\n CONTENT[ ' baz ' ]\n"); + }); + + it('parses an inverse (else-style) section', function() { + ast_for("{{#foo}} bar {{else}} baz {{/foo}}").should.equal("BLOCK:\n {{ ID:foo [] }}\n PROGRAM:\n CONTENT[ ' bar ' ]\n {{^}}\n CONTENT[ ' baz ' ]\n"); + }); + + it('parses empty blocks', function() { + ast_for("{{#foo}}{{/foo}}").should.equal("BLOCK:\n {{ ID:foo [] }}\n PROGRAM:\n"); + }); + + it('parses empty blocks with empty inverse section', function() { + ast_for("{{#foo}}{{^}}{{/foo}}").should.equal("BLOCK:\n {{ ID:foo [] }}\n PROGRAM:\n {{^}}\n"); + }); + + it('parses empty blocks with empty inverse (else-style) section', function() { + ast_for("{{#foo}}{{else}}{{/foo}}").should.equal("BLOCK:\n {{ ID:foo [] }}\n PROGRAM:\n {{^}}\n"); + }); + + it('parses non-empty blocks with empty inverse section', function() { + ast_for("{{#foo}} bar {{^}}{{/foo}}").should.equal("BLOCK:\n {{ ID:foo [] }}\n PROGRAM:\n CONTENT[ ' bar ' ]\n {{^}}\n"); + }); + + it('parses non-empty blocks with empty inverse (else-style) section', function() { + ast_for("{{#foo}} bar {{else}}{{/foo}}").should.equal("BLOCK:\n {{ ID:foo [] }}\n PROGRAM:\n CONTENT[ ' bar ' ]\n {{^}}\n"); + }); + + it('parses empty blocks with non-empty inverse section', function() { + ast_for("{{#foo}}{{^}} bar {{/foo}}").should.equal("BLOCK:\n {{ ID:foo [] }}\n PROGRAM:\n {{^}}\n CONTENT[ ' bar ' ]\n"); + }); + + it('parses empty blocks with non-empty inverse (else-style) section', function() { + ast_for("{{#foo}}{{else}} bar {{/foo}}").should.equal("BLOCK:\n {{ ID:foo [] }}\n PROGRAM:\n {{^}}\n CONTENT[ ' bar ' ]\n"); + }); + + it('parses a standalone inverse section', function() { + ast_for("{{^foo}}bar{{/foo}}").should.equal("BLOCK:\n {{ ID:foo [] }}\n {{^}}\n CONTENT[ 'bar' ]\n"); + }); + + it("raises if there's a Parse error", function() { + (function() { + ast_for("{{foo}"); + }).should.throw(/Parse error on line 1/); + (function() { + ast_for("{{foo &}}"); + }).should.throw(/Parse error on line 1/); + (function() { + ast_for("{{#goodbyes}}{{/hellos}}"); + }).should.throw(/goodbyes doesn't match hellos/); + }); + + it('knows how to report the correct line number in errors', function() { + (function() { + ast_for("hello\nmy\n{{foo}"); + }).should.throw(/Parse error on line 3/); + (function() { + ast_for("hello\n\nmy\n\n{{foo}"); + }).should.throw(/Parse error on line 5/); + }); + + it('knows how to report the correct line number in errors when the first character is a newline', function() { + (function() { + ast_for("\n\nhello\n\nmy\n\n{{foo}"); + }).should.throw(/Parse error on line 7/); + }); + + describe('externally compiled AST', function() { + it('can pass through an already-compiled AST', function() { + ast_for(new Handlebars.AST.ProgramNode([ new Handlebars.AST.ContentNode("Hello")])).should.equal("CONTENT[ \'Hello\' ]\n"); + }); + }); +}); diff --git a/spec/parser_spec.rb b/spec/parser_spec.rb deleted file mode 100644 index b25e88938..000000000 --- a/spec/parser_spec.rb +++ /dev/null @@ -1,426 +0,0 @@ -require "spec_helper" - -describe "Parser" do - let(:handlebars) { @context["Handlebars"] } - - before(:all) do - @compiles = true - end - - def root(&block) - ASTBuilder.build do - instance_eval(&block) - end - end - - def ast_for(string) - ast = handlebars.parse(string) - handlebars.print(ast) - end - - class ASTBuilder - def self.build(&block) - ret = new - ret.evaluate(&block) - ret.out - end - - attr_reader :out - - def initialize - @padding = 0 - @out = "" - end - - def evaluate(&block) - instance_eval(&block) - end - - def pad(string) - @out << (" " * @padding) + string + "\n" - end - - def with_padding - @padding += 1 - ret = yield - @padding -= 1 - ret - end - - def program - pad("PROGRAM:") - with_padding { yield } - end - - def inverse - pad("{{^}}") - with_padding { yield } - end - - def block - pad("BLOCK:") - with_padding { yield } - end - - def inverted_block - pad("INVERSE:") - with_padding { yield } - end - - def mustache(id, params = [], hash = nil) - hash = " #{hash}" if hash - pad("{{ #{id} [#{params.join(", ")}]#{hash} }}") - end - - def partial(id, context = nil) - content = id.dup - content << " #{context}" if context - pad("{{> #{content} }}") - end - - def comment(comment) - pad("{{! '#{comment}' }}") - end - - def multiline_comment(comment) - pad("{{! '\n#{comment}\n' }}") - end - - def content(string) - pad("CONTENT[ '#{string}' ]") - end - - def string(string) - string.inspect - end - - def integer(string) - "INTEGER{#{string}}" - end - - def boolean(string) - "BOOLEAN{#{string}}" - end - - def hash(*pairs) - "HASH{" + pairs.map {|k,v| "#{k}=#{v}" }.join(", ") + "}" - end - - def id(id) - "ID:#{id}" - end - - def data(id) - "@ID:#{id}" - end - - def partial_name(name) - "PARTIAL:#{name}" - end - - def path(*parts) - "PATH:#{parts.join("/")}" - end - end - - it "parses simple mustaches" do - ast_for("{{foo}}").should == root { mustache id("foo") } - ast_for("{{foo?}}").should == root { mustache id("foo?") } - ast_for("{{foo_}}").should == root { mustache id("foo_") } - ast_for("{{foo-}}").should == root { mustache id("foo-") } - ast_for("{{foo:}}").should == root { mustache id("foo:") } - end - - it "parses simple mustaches with data" do - ast_for("{{@foo}}").should == root { mustache data("foo") } - end - - it "parses mustaches with paths" do - ast_for("{{foo/bar}}").should == root { mustache path("foo", "bar") } - end - - it "parses mustaches with this/foo" do - ast_for("{{this/foo}}").should == root { mustache id("foo") } - end - - it "parses mustaches with - in a path" do - ast_for("{{foo-bar}}").should == root { mustache id("foo-bar") } - end - - it "parses mustaches with parameters" do - ast_for("{{foo bar}}").should == root { mustache id("foo"), [id("bar")] } - end - - it "parses mustaches with hash arguments" do - ast_for("{{foo bar=baz}}").should == root do - mustache id("foo"), [], hash(["bar", id("baz")]) - end - - ast_for("{{foo bar=1}}").should == root do - mustache id("foo"), [], hash(["bar", integer("1")]) - end - - ast_for("{{foo bar=true}}").should == root do - mustache id("foo"), [], hash(["bar", boolean("true")]) - end - - ast_for("{{foo bar=false}}").should == root do - mustache id("foo"), [], hash(["bar", boolean("false")]) - end - - ast_for("{{foo bar=@baz}}").should == root do - mustache id("foo"), [], hash(["bar", data("baz")]) - end - - ast_for("{{foo bar=baz bat=bam}}").should == root do - mustache id("foo"), [], hash(["bar", "ID:baz"], ["bat", "ID:bam"]) - end - - ast_for("{{foo bar=baz bat=\"bam\"}}").should == root do - mustache id("foo"), [], hash(["bar", "ID:baz"], ["bat", "\"bam\""]) - end - - ast_for("{{foo bat='bam'}}").should == root do - mustache id("foo"), [], hash(["bat", "\"bam\""]) - end - - ast_for("{{foo omg bar=baz bat=\"bam\"}}").should == root do - mustache id("foo"), [id("omg")], hash(["bar", id("baz")], ["bat", string("bam")]) - end - - ast_for("{{foo omg bar=baz bat=\"bam\" baz=1}}").should == root do - mustache id("foo"), [id("omg")], hash(["bar", id("baz")], ["bat", string("bam")], ["baz", integer("1")]) - end - - ast_for("{{foo omg bar=baz bat=\"bam\" baz=true}}").should == root do - mustache id("foo"), [id("omg")], hash(["bar", id("baz")], ["bat", string("bam")], ["baz", boolean("true")]) - end - - ast_for("{{foo omg bar=baz bat=\"bam\" baz=false}}").should == root do - mustache id("foo"), [id("omg")], hash(["bar", id("baz")], ["bat", string("bam")], ["baz", boolean("false")]) - end - end - - it "parses mustaches with string parameters" do - ast_for("{{foo bar \"baz\" }}").should == root { mustache id("foo"), [id("bar"), string("baz")] } - end - - it "parses mustaches with INTEGER parameters" do - ast_for("{{foo 1}}").should == root { mustache id("foo"), [integer("1")] } - end - - it "parses mustaches with BOOLEAN parameters" do - ast_for("{{foo true}}").should == root { mustache id("foo"), [boolean("true")] } - ast_for("{{foo false}}").should == root { mustache id("foo"), [boolean("false")] } - end - - it "parses mutaches with DATA parameters" do - ast_for("{{foo @bar}}").should == root { mustache id("foo"), [data("bar")] } - end - - it "parses contents followed by a mustache" do - ast_for("foo bar {{baz}}").should == root do - content "foo bar " - mustache id("baz") - end - end - - it "parses a partial" do - ast_for("{{> foo }}").should == root { partial partial_name("foo") } - end - - it "parses a partial with context" do - 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?.bar}}").should == root { partial partial_name("shared/partial?.bar") } - end - - it "parses a comment" do - ast_for("{{! this is a comment }}").should == root do - comment " this is a comment " - end - end - - it "parses a multi-line comment" do - ast_for("{{!\nthis is a multi-line comment\n}}").should == root do - multiline_comment "this is a multi-line comment" - end - end - - it "parses an inverse section" do - ast_for("{{#foo}} bar {{^}} baz {{/foo}}").should == root do - block do - mustache id("foo") - - program do - content " bar " - end - - inverse do - content " baz " - end - end - end - end - - it "parses an inverse ('else'-style) section" do - ast_for("{{#foo}} bar {{else}} baz {{/foo}}").should == root do - block do - mustache id("foo") - - program do - content " bar " - end - - inverse do - content " baz " - end - end - end - end - - it "parses empty blocks" do - ast_for("{{#foo}}{{/foo}}").should == root do - block do - mustache id("foo") - - program do - # empty program - end - end - end - end - - it "parses empty blocks with empty inverse section" do - ast_for("{{#foo}}{{^}}{{/foo}}").should == root do - block do - mustache id("foo") - - program do - # empty program - end - - inverse do - # empty inverse - end - end - end - end - - it "parses empty blocks with empty inverse ('else'-style) section" do - ast_for("{{#foo}}{{else}}{{/foo}}").should == root do - block do - mustache id("foo") - - program do - # empty program - end - - inverse do - # empty inverse - end - end - end - end - - it "parses non-empty blocks with empty inverse section" do - ast_for("{{#foo}} bar {{^}}{{/foo}}").should == root do - block do - mustache id("foo") - - program do - content " bar " - end - - inverse do - # empty inverse - end - end - end - end - - it "parses non-empty blocks with empty inverse ('else'-style) section" do - ast_for("{{#foo}} bar {{else}}{{/foo}}").should == root do - block do - mustache id("foo") - - program do - content " bar " - end - - inverse do - # empty inverse - end - end - end - end - - it "parses empty blocks with non-empty inverse section" do - ast_for("{{#foo}}{{^}} bar {{/foo}}").should == root do - block do - mustache id("foo") - - program do - # empty program - end - - inverse do - content " bar " - end - end - end - end - - it "parses empty blocks with non-empty inverse ('else'-style) section" do - ast_for("{{#foo}}{{else}} bar {{/foo}}").should == root do - block do - mustache id("foo") - - program do - # empty program - end - - inverse do - content " bar " - end - end - end - end - - it "parses a standalone inverse section" do - ast_for("{{^foo}}bar{{/foo}}").should == root do - block do - mustache id("foo") - - inverse do - content "bar" - end - end - end - end - - it "raises if there's a Parse error" do - lambda { ast_for("{{foo}") }.should raise_error(V8::JSError, /Parse error on line 1/) - lambda { ast_for("{{foo &}}")}.should raise_error(V8::JSError, /Parse error on line 1/) - lambda { ast_for("{{#goodbyes}}{{/hellos}}") }.should raise_error(V8::JSError, /goodbyes doesn't match hellos/) - end - - it "knows how to report the correct line number in errors" do - lambda { ast_for("hello\nmy\n{{foo}") }.should raise_error(V8::JSError, /Parse error on line 3/m) - lambda { ast_for("hello\n\nmy\n\n{{foo}") }.should raise_error(V8::JSError, /Parse error on line 5/m) - end - - it "knows how to report the correct line number in errors when the first character is a newline" do - lambda { ast_for("\n\nhello\n\nmy\n\n{{foo}") }.should raise_error(V8::JSError, /Parse error on line 7/m) - end - - context "externally compiled AST" do - it "can pass through an already-compiled AST" do - ast_for(@context.eval('new Handlebars.AST.ProgramNode([ new Handlebars.AST.ContentNode("Hello")]);')).should == root do - content "Hello" - end - end - end -end diff --git a/spec/tokenizer.js b/spec/tokenizer.js new file mode 100644 index 000000000..c69f406b5 --- /dev/null +++ b/spec/tokenizer.js @@ -0,0 +1,317 @@ +var Handlebars = require('../lib/handlebars'), + should = require('should'); + +should.Assertion.prototype.match_tokens = function(tokens) { + this.obj.forEach(function(value, index) { + value.name.should.equal(tokens[index]); + }); +}; +should.Assertion.prototype.be_token = function(name, text) { + this.obj.should.eql({name: name, text: text}); +}; + +describe('Tokenizer', function() { + function tokenize(template) { + var parser = Handlebars.Parser, + lexer = parser.lexer; + + lexer.setInput(template); + var out = [], + token; + + while (token = lexer.lex()) { + var result = parser.terminals_[token] || token; + if (!result || result === 'EOF' || result === 'INVALID') { + break; + } + out.push({name: result, text: lexer.yytext}); + } + + return out; + } + + it('tokenizes a simple mustache as "OPEN ID CLOSE"', function() { + var result = tokenize("{{foo}}"); + result.should.match_tokens(['OPEN', 'ID', 'CLOSE']); + result[1].should.be_token("ID", "foo"); + }); + + it('supports unescaping with &', function() { + var result = tokenize("{{&bar}}"); + result.should.match_tokens(['OPEN', 'ID', 'CLOSE']); + + result[0].should.be_token("OPEN", "{{&"); + result[1].should.be_token("ID", "bar"); + }); + + it('supports unescaping with {{{', function() { + var result = tokenize("{{{bar}}}"); + result.should.match_tokens(['OPEN_UNESCAPED', 'ID', 'CLOSE_UNESCAPED']); + + result[1].should.be_token("ID", "bar"); + }); + + it('supports escaping delimiters', function() { + var result = tokenize("{{foo}} \\{{bar}} {{baz}}"); + result.should.match_tokens(['OPEN', 'ID', 'CLOSE', 'CONTENT', 'CONTENT', 'OPEN', 'ID', 'CLOSE']); + + result[4].should.be_token("CONTENT", "{{bar}} "); + }); + + it('supports escaping multiple delimiters', function() { + var result = tokenize("{{foo}} \\{{bar}} \\{{baz}}"); + result.should.match_tokens(['OPEN', 'ID', 'CLOSE', 'CONTENT', 'CONTENT', 'CONTENT']); + + result[3].should.be_token("CONTENT", " "); + result[4].should.be_token("CONTENT", "{{bar}} "); + result[5].should.be_token("CONTENT", "{{baz}}"); + }); + + it('supports escaping a triple stash', function() { + var result = tokenize("{{foo}} \\{{{bar}}} {{baz}}"); + result.should.match_tokens(['OPEN', 'ID', 'CLOSE', 'CONTENT', 'CONTENT', 'OPEN', 'ID', 'CLOSE']); + + result[4].should.be_token("CONTENT", "{{{bar}}} "); + }); + + it('tokenizes a simple path', function() { + var result = tokenize("{{foo/bar}}"); + result.should.match_tokens(['OPEN', 'ID', 'SEP', 'ID', 'CLOSE']); + }); + + it('allows dot notation', function() { + var result = tokenize("{{foo.bar}}"); + result.should.match_tokens(['OPEN', 'ID', 'SEP', 'ID', 'CLOSE']); + + tokenize("{{foo.bar.baz}}").should.match_tokens(['OPEN', 'ID', 'SEP', 'ID', 'SEP', 'ID', 'CLOSE']); + }); + + it('allows path literals with []', function() { + var result = tokenize("{{foo.[bar]}}"); + result.should.match_tokens(['OPEN', 'ID', 'SEP', 'ID', 'CLOSE']); + }); + + it('allows multiple path literals on a line with []', function() { + var result = tokenize("{{foo.[bar]}}{{foo.[baz]}}"); + result.should.match_tokens(['OPEN', 'ID', 'SEP', 'ID', 'CLOSE', 'OPEN', 'ID', 'SEP', 'ID', 'CLOSE']); + }); + + it('tokenizes {{.}} as OPEN ID CLOSE', function() { + var result = tokenize("{{.}}"); + result.should.match_tokens(['OPEN', 'ID', 'CLOSE']); + }); + + it('tokenizes a path as "OPEN (ID SEP)* ID CLOSE"', function() { + var result = tokenize("{{../foo/bar}}"); + result.should.match_tokens(['OPEN', 'ID', 'SEP', 'ID', 'SEP', 'ID', 'CLOSE']); + result[1].should.be_token("ID", ".."); + }); + + it('tokenizes a path with .. as a parent path', function() { + var result = tokenize("{{../foo.bar}}"); + result.should.match_tokens(['OPEN', 'ID', 'SEP', 'ID', 'SEP', 'ID', 'CLOSE']); + result[1].should.be_token("ID", ".."); + }); + + it('tokenizes a path with this/foo as OPEN ID SEP ID CLOSE', function() { + var result = tokenize("{{this/foo}}"); + result.should.match_tokens(['OPEN', 'ID', 'SEP', 'ID', 'CLOSE']); + result[1].should.be_token("ID", "this"); + result[3].should.be_token("ID", "foo"); + }); + + it('tokenizes a simple mustache with spaces as "OPEN ID CLOSE"', function() { + var result = tokenize("{{ foo }}"); + result.should.match_tokens(['OPEN', 'ID', 'CLOSE']); + result[1].should.be_token("ID", "foo"); + }); + + it('tokenizes a simple mustache with line breaks as "OPEN ID ID CLOSE"', function() { + var result = tokenize("{{ foo \n bar }}"); + result.should.match_tokens(['OPEN', 'ID', 'ID', 'CLOSE']); + result[1].should.be_token("ID", "foo"); + }); + + it('tokenizes raw content as "CONTENT"', function() { + var result = tokenize("foo {{ bar }} baz"); + result.should.match_tokens(['CONTENT', 'OPEN', 'ID', 'CLOSE', 'CONTENT']); + result[0].should.be_token("CONTENT", "foo "); + result[4].should.be_token("CONTENT", " baz"); + }); + + it('tokenizes a partial as "OPEN_PARTIAL ID CLOSE"', function() { + var result = tokenize("{{> foo}}"); + result.should.match_tokens(['OPEN_PARTIAL', 'ID', 'CLOSE']); + }); + + it('tokenizes a partial with context as "OPEN_PARTIAL ID ID CLOSE"', function() { + var result = tokenize("{{> foo bar }}"); + result.should.match_tokens(['OPEN_PARTIAL', 'ID', 'ID', 'CLOSE']); + }); + + it('tokenizes a partial without spaces as "OPEN_PARTIAL ID CLOSE"', function() { + var result = tokenize("{{>foo}}"); + result.should.match_tokens(['OPEN_PARTIAL', 'ID', 'CLOSE']); + }); + + it('tokenizes a partial space at the }); as "OPEN_PARTIAL ID CLOSE"', function() { + var result = tokenize("{{>foo }}"); + result.should.match_tokens(['OPEN_PARTIAL', 'ID', 'CLOSE']); + }); + + it('tokenizes a partial space at the }); as "OPEN_PARTIAL ID CLOSE"', function() { + var result = tokenize("{{>foo/bar.baz }}"); + result.should.match_tokens(['OPEN_PARTIAL', 'ID', 'SEP', 'ID', 'SEP', 'ID', 'CLOSE']); + }); + + it('tokenizes a comment as "COMMENT"', function() { + var result = tokenize("foo {{! this is a comment }} bar {{ baz }}"); + result.should.match_tokens(['CONTENT', 'COMMENT', 'CONTENT', 'OPEN', 'ID', 'CLOSE']); + result[1].should.be_token("COMMENT", " this is a comment "); + }); + + it('tokenizes a block comment as "COMMENT"', function() { + var result = tokenize("foo {{!-- this is a {{comment}} --}} bar {{ baz }}"); + result.should.match_tokens(['CONTENT', 'COMMENT', 'CONTENT', 'OPEN', 'ID', 'CLOSE']); + result[1].should.be_token("COMMENT", " this is a {{comment}} "); + }); + + it('tokenizes a block comment with whitespace as "COMMENT"', function() { + var result = tokenize("foo {{!-- this is a\n{{comment}}\n--}} bar {{ baz }}"); + result.should.match_tokens(['CONTENT', 'COMMENT', 'CONTENT', 'OPEN', 'ID', 'CLOSE']); + result[1].should.be_token("COMMENT", " this is a\n{{comment}}\n"); + }); + + it('tokenizes open and closing blocks as OPEN_BLOCK, ID, CLOSE ..., OPEN_ENDBLOCK ID CLOSE', function() { + var result = tokenize("{{#foo}}content{{/foo}}"); + result.should.match_tokens(['OPEN_BLOCK', 'ID', 'CLOSE', 'CONTENT', 'OPEN_ENDBLOCK', 'ID', 'CLOSE']); + }); + + it('tokenizes inverse sections as "OPEN_INVERSE CLOSE"', function() { + tokenize("{{^}}").should.match_tokens(['OPEN_INVERSE', 'CLOSE']); + tokenize("{{else}}").should.match_tokens(['OPEN_INVERSE', 'CLOSE']); + tokenize("{{ else }}").should.match_tokens(['OPEN_INVERSE', 'CLOSE']); + }); + + it('tokenizes inverse sections with ID as "OPEN_INVERSE ID CLOSE"', function() { + var result = tokenize("{{^foo}}"); + result.should.match_tokens(['OPEN_INVERSE', 'ID', 'CLOSE']); + result[1].should.be_token("ID", "foo"); + }); + + it('tokenizes inverse sections with ID and spaces as "OPEN_INVERSE ID CLOSE"', function() { + var result = tokenize("{{^ foo }}"); + result.should.match_tokens(['OPEN_INVERSE', 'ID', 'CLOSE']); + result[1].should.be_token("ID", "foo"); + }); + + it('tokenizes mustaches with params as "OPEN ID ID ID CLOSE"', function() { + var result = tokenize("{{ foo bar baz }}"); + result.should.match_tokens(['OPEN', 'ID', 'ID', 'ID', 'CLOSE']); + result[1].should.be_token("ID", "foo"); + result[2].should.be_token("ID", "bar"); + result[3].should.be_token("ID", "baz"); + }); + + it('tokenizes mustaches with String params as "OPEN ID ID STRING CLOSE"', function() { + var result = tokenize("{{ foo bar \"baz\" }}"); + result.should.match_tokens(['OPEN', 'ID', 'ID', 'STRING', 'CLOSE']); + result[3].should.be_token("STRING", "baz"); + }); + + it('tokenizes mustaches with String params using single quotes as "OPEN ID ID STRING CLOSE"', function() { + var result = tokenize("{{ foo bar \'baz\' }}"); + result.should.match_tokens(['OPEN', 'ID', 'ID', 'STRING', 'CLOSE']); + result[3].should.be_token("STRING", "baz"); + }); + + it('tokenizes String params with spaces inside as "STRING"', function() { + var result = tokenize("{{ foo bar \"baz bat\" }}"); + result.should.match_tokens(['OPEN', 'ID', 'ID', 'STRING', 'CLOSE']); + result[3].should.be_token("STRING", "baz bat"); + }); + + it('tokenizes String params with escapes quotes as STRING', function() { + var result = tokenize('{{ foo "bar\\"baz" }}'); + result.should.match_tokens(['OPEN', 'ID', 'STRING', 'CLOSE']); + result[2].should.be_token("STRING", 'bar"baz'); + }); + + it('tokenizes String params using single quotes with escapes quotes as STRING', function() { + var result = tokenize("{{ foo 'bar\\'baz' }}"); + result.should.match_tokens(['OPEN', 'ID', 'STRING', 'CLOSE']); + result[2].should.be_token("STRING", "bar'baz"); + }); + + it('tokenizes numbers', function() { + var result = tokenize('{{ foo 1 }}'); + result.should.match_tokens(['OPEN', 'ID', 'INTEGER', 'CLOSE']); + result[2].should.be_token("INTEGER", "1"); + + result = tokenize('{{ foo -1 }}'); + result.should.match_tokens(['OPEN', 'ID', 'INTEGER', 'CLOSE']); + result[2].should.be_token("INTEGER", "-1"); + }); + + it('tokenizes booleans', function() { + var result = tokenize('{{ foo true }}'); + result.should.match_tokens(['OPEN', 'ID', 'BOOLEAN', 'CLOSE']); + result[2].should.be_token("BOOLEAN", "true"); + + result = tokenize('{{ foo false }}'); + result.should.match_tokens(['OPEN', 'ID', 'BOOLEAN', 'CLOSE']); + result[2].should.be_token("BOOLEAN", "false"); + }); + + it('tokenizes hash arguments', function() { + var result = tokenize("{{ foo bar=baz }}"); + result.should.match_tokens(['OPEN', 'ID', 'ID', 'EQUALS', 'ID', 'CLOSE']); + + result = tokenize("{{ foo bar baz=bat }}"); + result.should.match_tokens(['OPEN', 'ID', 'ID', 'ID', 'EQUALS', 'ID', 'CLOSE']); + + result = tokenize("{{ foo bar baz=1 }}"); + result.should.match_tokens(['OPEN', 'ID', 'ID', 'ID', 'EQUALS', 'INTEGER', 'CLOSE']); + + result = tokenize("{{ foo bar baz=true }}"); + result.should.match_tokens(['OPEN', 'ID', 'ID', 'ID', 'EQUALS', 'BOOLEAN', 'CLOSE']); + + result = tokenize("{{ foo bar baz=false }}"); + result.should.match_tokens(['OPEN', 'ID', 'ID', 'ID', 'EQUALS', 'BOOLEAN', 'CLOSE']); + + result = tokenize("{{ foo bar\n baz=bat }}"); + result.should.match_tokens(['OPEN', 'ID', 'ID', 'ID', 'EQUALS', 'ID', 'CLOSE']); + + result = tokenize("{{ foo bar baz=\"bat\" }}"); + result.should.match_tokens(['OPEN', 'ID', 'ID', 'ID', 'EQUALS', 'STRING', 'CLOSE']); + + result = tokenize("{{ foo bar baz=\"bat\" bam=wot }}"); + result.should.match_tokens(['OPEN', 'ID', 'ID', 'ID', 'EQUALS', 'STRING', 'ID', 'EQUALS', 'ID', 'CLOSE']); + + result = tokenize("{{foo omg bar=baz bat=\"bam\"}}"); + result.should.match_tokens(['OPEN', 'ID', 'ID', 'ID', 'EQUALS', 'ID', 'ID', 'EQUALS', 'STRING', 'CLOSE']); + result[2].should.be_token("ID", "omg"); + }); + + it('tokenizes special @ identifiers', function() { + var result = tokenize("{{ @foo }}"); + result.should.match_tokens(['OPEN', 'DATA', 'ID', 'CLOSE']); + result[2].should.be_token("ID", "foo"); + + result = tokenize("{{ foo @bar }}"); + result.should.match_tokens(['OPEN', 'ID', 'DATA', 'ID', 'CLOSE']); + result[3].should.be_token("ID", "bar"); + + result = tokenize("{{ foo bar=@baz }}"); + result.should.match_tokens(['OPEN', 'ID', 'ID', 'EQUALS', 'DATA', 'ID', 'CLOSE']); + result[5].should.be_token("ID", "baz"); + }); + + it('does not time out in a mustache with a single } followed by EOF', function() { + tokenize("{{foo}").should.match_tokens(['OPEN', 'ID']); + }); + + it('does not time out in a mustache when invalid ID characters are used', function() { + tokenize("{{foo & }}").should.match_tokens(['OPEN', 'ID']); + }); +}); diff --git a/spec/tokenizer_spec.rb b/spec/tokenizer_spec.rb deleted file mode 100644 index 0a7c3f9db..000000000 --- a/spec/tokenizer_spec.rb +++ /dev/null @@ -1,322 +0,0 @@ -require "spec_helper" -require "timeout" - -describe "Tokenizer" do - let(:parser) { Handlebars::Spec::PARSER_CONTEXT["handlebars"] } - let(:lexer) { Handlebars::Spec::PARSER_CONTEXT["handlebars"]["lexer"] } - - Token = Struct.new(:name, :text) - - def tokenize(string) - lexer.setInput(string) - out = [] - - while token = lexer.lex - # p token - result = parser.terminals_[token] || token - # p result - break if !result || result == "EOF" || result == "INVALID" - out << Token.new(result, lexer.yytext) - end - - out - end - - RSpec::Matchers.define :match_tokens do |tokens| - match do |result| - result.map(&:name).should == tokens - end - end - - RSpec::Matchers.define :be_token do |name, string| - match do |token| - token.name.should == name - token.text.should == string - end - end - - it "tokenizes a simple mustache as 'OPEN ID CLOSE'" do - result = tokenize("{{foo}}") - result.should match_tokens(%w(OPEN ID CLOSE)) - result[1].should be_token("ID", "foo") - end - - it "supports unescaping with &" do - result = tokenize("{{&bar}}") - result.should match_tokens(%w(OPEN ID CLOSE)) - - result[0].should be_token("OPEN", "{{&") - result[1].should be_token("ID", "bar") - end - - it "supports unescaping with {{{" do - result = tokenize("{{{bar}}}") - result.should match_tokens(%w(OPEN_UNESCAPED ID CLOSE_UNESCAPED)) - - result[1].should be_token("ID", "bar") - end - - it "supports escaping delimiters" do - result = tokenize("{{foo}} \\{{bar}} {{baz}}") - result.should match_tokens(%w(OPEN ID CLOSE CONTENT CONTENT OPEN ID CLOSE)) - - result[4].should be_token("CONTENT", "{{bar}} ") - end - - it "supports escaping multiple delimiters" do - result = tokenize("{{foo}} \\{{bar}} \\{{baz}}") - result.should match_tokens(%w(OPEN ID CLOSE CONTENT CONTENT CONTENT)) - - result[3].should be_token("CONTENT", " ") - result[4].should be_token("CONTENT", "{{bar}} ") - result[5].should be_token("CONTENT", "{{baz}}") - end - - it "supports escaping a triple stash" do - result = tokenize("{{foo}} \\{{{bar}}} {{baz}}") - result.should match_tokens(%w(OPEN ID CLOSE CONTENT CONTENT OPEN ID CLOSE)) - - result[4].should be_token("CONTENT", "{{{bar}}} ") - end - - it "tokenizes a simple path" do - result = tokenize("{{foo/bar}}") - result.should match_tokens(%w(OPEN ID SEP ID CLOSE)) - end - - it "allows dot notation" do - result = tokenize("{{foo.bar}}") - result.should match_tokens(%w(OPEN ID SEP ID CLOSE)) - - tokenize("{{foo.bar.baz}}").should match_tokens(%w(OPEN ID SEP ID SEP ID CLOSE)) - end - - it "allows path literals with []" do - result = tokenize("{{foo.[bar]}}") - result.should match_tokens(%w(OPEN ID SEP ID CLOSE)) - end - - it "allows multiple path literals on a line with []" do - result = tokenize("{{foo.[bar]}}{{foo.[baz]}}") - result.should match_tokens(%w(OPEN ID SEP ID CLOSE OPEN ID SEP ID CLOSE)) - end - - it "tokenizes {{.}} as OPEN ID CLOSE" do - result = tokenize("{{.}}") - result.should match_tokens(%w(OPEN ID CLOSE)) - end - - 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 .. as a parent path" 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 mustache with spaces as 'OPEN ID CLOSE'" do - result = tokenize("{{ foo }}") - result.should match_tokens(%w(OPEN ID CLOSE)) - result[1].should be_token("ID", "foo") - end - - it "tokenizes a simple mustache with line breaks as 'OPEN ID ID CLOSE'" do - result = tokenize("{{ foo \n bar }}") - result.should match_tokens(%w(OPEN ID ID CLOSE)) - result[1].should be_token("ID", "foo") - end - - it "tokenizes raw content as 'CONTENT'" do - result = tokenize("foo {{ bar }} baz") - result.should match_tokens(%w(CONTENT OPEN ID CLOSE CONTENT)) - result[0].should be_token("CONTENT", "foo ") - result[4].should be_token("CONTENT", " baz") - end - - it "tokenizes a partial as 'OPEN_PARTIAL ID CLOSE'" do - result = tokenize("{{> foo}}") - 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)) - end - - it "tokenizes a partial space at the end as 'OPEN_PARTIAL ID CLOSE'" do - result = tokenize("{{>foo }}") - result.should match_tokens(%w(OPEN_PARTIAL ID CLOSE)) - end - - it "tokenizes a partial space at the end as 'OPEN_PARTIAL ID CLOSE'" do - result = tokenize("{{>foo/bar.baz }}") - result.should match_tokens(%w(OPEN_PARTIAL ID SEP ID SEP ID CLOSE)) - end - - it "tokenizes a comment as 'COMMENT'" do - result = tokenize("foo {{! this is a comment }} bar {{ baz }}") - result.should match_tokens(%w(CONTENT COMMENT CONTENT OPEN ID CLOSE)) - result[1].should be_token("COMMENT", " this is a comment ") - end - - it "tokenizes a block comment as 'COMMENT'" do - result = tokenize("foo {{!-- this is a {{comment}} --}} bar {{ baz }}") - result.should match_tokens(%w(CONTENT COMMENT CONTENT OPEN ID CLOSE)) - result[1].should be_token("COMMENT", " this is a {{comment}} ") - end - - it "tokenizes a block comment with whitespace as 'COMMENT'" do - result = tokenize("foo {{!-- this is a\n{{comment}}\n--}} bar {{ baz }}") - result.should match_tokens(%w(CONTENT COMMENT CONTENT OPEN ID CLOSE)) - result[1].should be_token("COMMENT", " this is a\n{{comment}}\n") - end - - it "tokenizes open and closing blocks as 'OPEN_BLOCK ID CLOSE ... OPEN_ENDBLOCK ID CLOSE'" do - result = tokenize("{{#foo}}content{{/foo}}") - result.should match_tokens(%w(OPEN_BLOCK ID CLOSE CONTENT OPEN_ENDBLOCK ID CLOSE)) - end - - it "tokenizes inverse sections as 'OPEN_INVERSE CLOSE'" do - tokenize("{{^}}").should match_tokens(%w(OPEN_INVERSE CLOSE)) - tokenize("{{else}}").should match_tokens(%w(OPEN_INVERSE CLOSE)) - tokenize("{{ else }}").should match_tokens(%w(OPEN_INVERSE CLOSE)) - end - - it "tokenizes inverse sections with ID as 'OPEN_INVERSE ID CLOSE'" do - result = tokenize("{{^foo}}") - result.should match_tokens(%w(OPEN_INVERSE ID CLOSE)) - result[1].should be_token("ID", "foo") - end - - it "tokenizes inverse sections with ID and spaces as 'OPEN_INVERSE ID CLOSE'" do - result = tokenize("{{^ foo }}") - result.should match_tokens(%w(OPEN_INVERSE ID CLOSE)) - result[1].should be_token("ID", "foo") - end - - it "tokenizes mustaches with params as 'OPEN ID ID ID CLOSE'" do - result = tokenize("{{ foo bar baz }}") - result.should match_tokens(%w(OPEN ID ID ID CLOSE)) - result[1].should be_token("ID", "foo") - result[2].should be_token("ID", "bar") - result[3].should be_token("ID", "baz") - end - - it "tokenizes mustaches with String params as 'OPEN ID ID STRING CLOSE'" do - result = tokenize("{{ foo bar \"baz\" }}") - result.should match_tokens(%w(OPEN ID ID STRING CLOSE)) - result[3].should be_token("STRING", "baz") - end - - it "tokenizes mustaches with String params using single quotes as 'OPEN ID ID STRING CLOSE'" do - result = tokenize("{{ foo bar \'baz\' }}") - result.should match_tokens(%w(OPEN ID ID STRING CLOSE)) - result[3].should be_token("STRING", "baz") - end - - it "tokenizes String params with spaces inside as 'STRING'" do - result = tokenize("{{ foo bar \"baz bat\" }}") - result.should match_tokens(%w(OPEN ID ID STRING CLOSE)) - result[3].should be_token("STRING", "baz bat") - end - - it "tokenizes String params with escapes quotes as 'STRING'" do - result = tokenize(%|{{ foo "bar\\"baz" }}|) - result.should match_tokens(%w(OPEN ID STRING CLOSE)) - result[2].should be_token("STRING", %{bar"baz}) - end - - it "tokenizes String params using single quotes with escapes quotes as 'STRING'" do - result = tokenize(%|{{ foo 'bar\\'baz' }}|) - result.should match_tokens(%w(OPEN ID STRING CLOSE)) - result[2].should be_token("STRING", %{bar'baz}) - end - - it "tokenizes numbers" do - result = tokenize(%|{{ foo 1 }}|) - result.should match_tokens(%w(OPEN ID INTEGER CLOSE)) - result[2].should be_token("INTEGER", "1") - - result = tokenize(%|{{ foo -1 }}|) - result.should match_tokens(%w(OPEN ID INTEGER CLOSE)) - result[2].should be_token("INTEGER", "-1") - end - - it "tokenizes booleans" do - result = tokenize(%|{{ foo true }}|) - result.should match_tokens(%w(OPEN ID BOOLEAN CLOSE)) - result[2].should be_token("BOOLEAN", "true") - - result = tokenize(%|{{ foo false }}|) - result.should match_tokens(%w(OPEN ID BOOLEAN CLOSE)) - result[2].should be_token("BOOLEAN", "false") - end - - it "tokenizes hash arguments" do - result = tokenize("{{ foo bar=baz }}") - result.should match_tokens %w(OPEN ID ID EQUALS ID CLOSE) - - result = tokenize("{{ foo bar baz=bat }}") - result.should match_tokens %w(OPEN ID ID ID EQUALS ID CLOSE) - - result = tokenize("{{ foo bar baz=1 }}") - result.should match_tokens %w(OPEN ID ID ID EQUALS INTEGER CLOSE) - - result = tokenize("{{ foo bar baz=true }}") - result.should match_tokens %w(OPEN ID ID ID EQUALS BOOLEAN CLOSE) - - result = tokenize("{{ foo bar baz=false }}") - result.should match_tokens %w(OPEN ID ID ID EQUALS BOOLEAN CLOSE) - - result = tokenize("{{ foo bar\n baz=bat }}") - result.should match_tokens %w(OPEN ID ID ID EQUALS ID CLOSE) - - result = tokenize("{{ foo bar baz=\"bat\" }}") - result.should match_tokens %w(OPEN ID ID ID EQUALS STRING CLOSE) - - result = tokenize("{{ foo bar baz=\"bat\" bam=wot }}") - result.should match_tokens %w(OPEN ID ID ID EQUALS STRING ID EQUALS ID CLOSE) - - result = tokenize("{{foo omg bar=baz bat=\"bam\"}}") - result.should match_tokens %w(OPEN ID ID ID EQUALS ID ID EQUALS STRING CLOSE) - result[2].should be_token("ID", "omg") - end - - it "tokenizes special @ identifiers" do - result = tokenize("{{ @foo }}") - result.should match_tokens %w( OPEN DATA ID CLOSE ) - result[2].should be_token("ID", "foo") - - result = tokenize("{{ foo @bar }}") - result.should match_tokens %w( OPEN ID DATA ID CLOSE ) - result[3].should be_token("ID", "bar") - - result = tokenize("{{ foo bar=@baz }}") - result.should match_tokens %w( OPEN ID ID EQUALS DATA ID CLOSE ) - result[5].should be_token("ID", "baz") - end - - it "does not time out in a mustache with a single } followed by EOF" do - Timeout.timeout(1) { tokenize("{{foo}").should match_tokens(%w(OPEN ID)) } - end - - it "does not time out in a mustache when invalid ID characters are used" do - Timeout.timeout(1) { tokenize("{{foo & }}").should match_tokens(%w(OPEN ID)) } - end -end From adda0569e0ec3fc363af9efbb23f5304c6d80fe2 Mon Sep 17 00:00:00 2001 From: kpdecker Date: Sat, 1 Jun 2013 23:45:43 -0500 Subject: [PATCH 007/200] Refactor qunit unit tests Allows for testing node, browser, and precompiled modes in the node tests. Also reorganizes the qunit spec file to provide better organization. --- .jshintrc | 5 +- Rakefile | 8 +- package.json | 2 +- spec/acceptance_spec.rb | 101 -- spec/{ => artifacts}/example_1.handlebars | 0 spec/{ => artifacts}/example_2.hbs | 0 spec/basic.js | 162 ++ spec/blocks.js | 86 ++ spec/builtins.js | 125 ++ spec/data.js | 240 +++ spec/env/browser.js | 13 + spec/env/common.js | 28 + spec/env/node.js | 13 + spec/env/runner.js | 40 + spec/env/runtime.js | 15 + spec/helpers.js | 532 +++++++ spec/parser.js | 8 +- spec/partials.js | 119 ++ spec/qunit_spec.js | 1657 --------------------- spec/regressions.js | 119 ++ spec/require.js | 23 + spec/source-map.js | 18 + spec/spec_helper.rb | 132 -- spec/string-params.js | 145 ++ spec/tokenizer.js | 7 +- spec/utils.js | 56 + 26 files changed, 1748 insertions(+), 1906 deletions(-) delete mode 100644 spec/acceptance_spec.rb rename spec/{ => artifacts}/example_1.handlebars (100%) rename spec/{ => artifacts}/example_2.hbs (100%) create mode 100644 spec/basic.js create mode 100644 spec/blocks.js create mode 100644 spec/builtins.js create mode 100644 spec/data.js create mode 100644 spec/env/browser.js create mode 100644 spec/env/common.js create mode 100644 spec/env/node.js create mode 100644 spec/env/runner.js create mode 100644 spec/env/runtime.js create mode 100644 spec/helpers.js create mode 100644 spec/partials.js delete mode 100644 spec/qunit_spec.js create mode 100644 spec/regressions.js create mode 100644 spec/require.js create mode 100644 spec/source-map.js delete mode 100644 spec/spec_helper.rb create mode 100644 spec/string-params.js create mode 100644 spec/utils.js diff --git a/.jshintrc b/.jshintrc index f0a233ab9..d6bd57bba 100644 --- a/.jshintrc +++ b/.jshintrc @@ -24,7 +24,8 @@ "module", "describe", - "it" + "it", + "afterEach" ], "node" : true, @@ -35,7 +36,7 @@ "curly": false, "debug": false, "devel": false, - "eqeqeq": true, + "eqeqeq": false, "evil": true, "forin": false, "immed": false, diff --git a/Rakefile b/Rakefile index d2b7d924a..a11142234 100644 --- a/Rakefile +++ b/Rakefile @@ -28,17 +28,11 @@ task :compile => "lib/handlebars/compiler/parser.js" desc "run the spec suite" task :spec => [:release] do - rc = system "rspec -cfs spec" - fail "rspec spec failed with exit code #{$?.exitstatus}" if (rc.nil? || ! rc || $?.exitstatus != 0) -end - -desc "run the npm test suite" -task :npm_test => [:release] do rc = system "npm test" fail "npm test failed with exit code #{$?.exitstatus}" if (rc.nil? || ! rc || $?.exitstatus != 0) end -task :default => [:compile, :spec, :npm_test] +task :default => [:compile, :spec] def remove_exports(string) match = string.match(%r{^// BEGIN\(BROWSER\)\n(.*)\n^// END\(BROWSER\)}m) diff --git a/package.json b/package.json index e64779197..3f517c1b1 100644 --- a/package.json +++ b/package.json @@ -29,7 +29,7 @@ "handlebars": "bin/handlebars" }, "scripts": { - "test": "node_modules/.bin/mocha spec/tokenizer.js spec/parser.js && node_modules/.bin/mocha -u qunit spec/qunit_spec.js" + "test": "node ./spec/env/runner" }, "optionalDependencies": {} } diff --git a/spec/acceptance_spec.rb b/spec/acceptance_spec.rb deleted file mode 100644 index 03b23c5de..000000000 --- a/spec/acceptance_spec.rb +++ /dev/null @@ -1,101 +0,0 @@ -require "spec_helper" - -class TestContext - class TestModule - attr_reader :name, :tests - - def initialize(name) - @name = name - @tests = [] - end - end - - attr_reader :modules - - def initialize - @modules = [] - end - - def module(name) - @modules << TestModule.new(name) - end - - def test(name, function) - @modules.last.tests << [name, function] - end -end - -test_context = TestContext.new -js_context = Handlebars::Spec::CONTEXT - -Module.new do - extend Test::Unit::Assertions - - def self.js_backtrace(context) - begin - context.eval("throw") - rescue V8::JSError => e - return e.backtrace(:javascript) - end - end - - js_context["p"] = proc do |this, str| - p str - end - - js_context["ok"] = proc do |this, ok, message| - js_context["$$RSPEC1$$"] = ok - - result = js_context.eval("!!$$RSPEC1$$") - - message ||= "#{ok} was not truthy" - - unless result - backtrace = js_backtrace(js_context) - message << "\n#{backtrace.join("\n")}" - end - - assert result, message - end - - js_context["equals"] = proc do |this, first, second, message| - js_context["$$RSPEC1$$"] = first - js_context["$$RSPEC2$$"] = second - - result = js_context.eval("$$RSPEC1$$ == $$RSPEC2$$") - - additional_message = "#{first.inspect} did not == #{second.inspect}" - message = message ? "#{message} (#{additional_message})" : additional_message - - unless result - backtrace = js_backtrace(js_context) - message << "\n#{backtrace.join("\n")}" - end - - assert result, message - end - - js_context["equal"] = js_context["equals"] - - js_context["suite"] = proc do |this, name| - test_context.module(name) - end - - js_context["test"] = proc do |this, name, function| - test_context.test(name, function) - end - - local = Regexp.escape(File.expand_path(Dir.pwd)) - qunit_spec = File.expand_path("../qunit_spec.js", __FILE__) - js_context.load(qunit_spec.sub(/^#{local}\//, '')) -end - -test_context.modules.each do |mod| - describe mod.name do - mod.tests.each do |name, function| - it name do - function.call - end - end - end -end diff --git a/spec/example_1.handlebars b/spec/artifacts/example_1.handlebars similarity index 100% rename from spec/example_1.handlebars rename to spec/artifacts/example_1.handlebars diff --git a/spec/example_2.hbs b/spec/artifacts/example_2.hbs similarity index 100% rename from spec/example_2.hbs rename to spec/artifacts/example_2.hbs diff --git a/spec/basic.js b/spec/basic.js new file mode 100644 index 000000000..a46636cfd --- /dev/null +++ b/spec/basic.js @@ -0,0 +1,162 @@ +describe("basic context", function() { + it("most basic", function() { + shouldCompileTo("{{foo}}", { foo: "foo" }, "foo"); + }); + + it("escaping", function() { + shouldCompileTo("\\{{foo}}", { foo: "food" }, "{{foo}}"); + shouldCompileTo("\\\\{{foo}}", { foo: "food" }, "\\food"); + shouldCompileTo("\\\\ {{foo}}", { foo: "food" }, "\\\\ food"); + }); + + it("compiling with a basic context", function() { + shouldCompileTo("Goodbye\n{{cruel}}\n{{world}}!", {cruel: "cruel", world: "world"}, "Goodbye\ncruel\nworld!", + "It works if all the required keys are provided"); + }); + + it("comments", function() { + shouldCompileTo("{{! Goodbye}}Goodbye\n{{cruel}}\n{{world}}!", + {cruel: "cruel", world: "world"}, "Goodbye\ncruel\nworld!", + "comments are ignored"); + }); + + it("boolean", function() { + var string = "{{#goodbye}}GOODBYE {{/goodbye}}cruel {{world}}!"; + shouldCompileTo(string, {goodbye: true, world: "world"}, "GOODBYE cruel world!", + "booleans show the contents when true"); + + shouldCompileTo(string, {goodbye: false, world: "world"}, "cruel world!", + "booleans do not show the contents when false"); + }); + + it("zeros", function() { + shouldCompileTo("num1: {{num1}}, num2: {{num2}}", {num1: 42, num2: 0}, + "num1: 42, num2: 0"); + shouldCompileTo("num: {{.}}", 0, "num: 0"); + shouldCompileTo("num: {{num1/num2}}", {num1: {num2: 0}}, "num: 0"); + }); + + it("newlines", function() { + shouldCompileTo("Alan's\nTest", {}, "Alan's\nTest"); + shouldCompileTo("Alan's\rTest", {}, "Alan's\rTest"); + }); + + it("escaping text", function() { + shouldCompileTo("Awesome's", {}, "Awesome's", "text is escaped so that it doesn't get caught on single quotes"); + shouldCompileTo("Awesome\\", {}, "Awesome\\", "text is escaped so that the closing quote can't be ignored"); + shouldCompileTo("Awesome\\\\ foo", {}, "Awesome\\\\ foo", "text is escaped so that it doesn't mess up backslashes"); + shouldCompileTo("Awesome {{foo}}", {foo: '\\'}, "Awesome \\", "text is escaped so that it doesn't mess up backslashes"); + shouldCompileTo(' " " ', {}, ' " " ', "double quotes never produce invalid javascript"); + }); + + it("escaping expressions", function() { + shouldCompileTo("{{{awesome}}}", {awesome: "&\"\\<>"}, '&\"\\<>', + "expressions with 3 handlebars aren't escaped"); + + shouldCompileTo("{{&awesome}}", {awesome: "&\"\\<>"}, '&\"\\<>', + "expressions with {{& handlebars aren't escaped"); + + shouldCompileTo("{{awesome}}", {awesome: "&\"'`\\<>"}, '&"'`\\<>', + "by default expressions should be escaped"); + + shouldCompileTo("{{awesome}}", {awesome: "Escaped, looks like: <b>"}, 'Escaped, <b> looks like: &lt;b&gt;', + "escaping should properly handle amperstands"); + }); + + it("functions returning safestrings shouldn't be escaped", function() { + var hash = {awesome: function() { return new Handlebars.SafeString("&\"\\<>"); }}; + shouldCompileTo("{{awesome}}", hash, '&\"\\<>', + "functions returning safestrings aren't escaped"); + }); + + it("functions", function() { + shouldCompileTo("{{awesome}}", {awesome: function() { return "Awesome"; }}, "Awesome", + "functions are called and render their output"); + shouldCompileTo("{{awesome}}", {awesome: function() { return this.more; }, more: "More awesome"}, "More awesome", + "functions are bound to the context"); + }); + + it("functions with context argument", function() { + shouldCompileTo("{{awesome frank}}", + {awesome: function(context) { return context; }, + frank: "Frank"}, + "Frank", "functions are called with context arguments"); + }); + + + it("paths with hyphens", function() { + shouldCompileTo("{{foo-bar}}", {"foo-bar": "baz"}, "baz", "Paths can contain hyphens (-)"); + shouldCompileTo("{{foo.foo-bar}}", {foo: {"foo-bar": "baz"}}, "baz", "Paths can contain hyphens (-)"); + shouldCompileTo("{{foo/foo-bar}}", {foo: {"foo-bar": "baz"}}, "baz", "Paths can contain hyphens (-)"); + }); + + it("nested paths", function() { + shouldCompileTo("Goodbye {{alan/expression}} world!", {alan: {expression: "beautiful"}}, + "Goodbye beautiful world!", "Nested paths access nested objects"); + }); + + it("nested paths with empty string value", function() { + shouldCompileTo("Goodbye {{alan/expression}} world!", {alan: {expression: ""}}, + "Goodbye world!", "Nested paths access nested objects with empty string"); + }); + + it("literal paths", function() { + shouldCompileTo("Goodbye {{[@alan]/expression}} world!", {"@alan": {expression: "beautiful"}}, + "Goodbye beautiful world!", "Literal paths can be used"); + shouldCompileTo("Goodbye {{[foo bar]/expression}} world!", {"foo bar": {expression: "beautiful"}}, + "Goodbye beautiful world!", "Literal paths can be used"); + }); + + it('literal references', function() { + shouldCompileTo("Goodbye {{[foo bar]}} world!", {"foo bar": "beautiful"}, + "Goodbye beautiful world!", "Literal paths can be used"); + }); + + it("that current context path ({{.}}) doesn't hit helpers", function() { + shouldCompileTo("test: {{.}}", [null, {helper: "awesome"}], "test: "); + }); + + it("complex but empty paths", function() { + shouldCompileTo("{{person/name}}", {person: {name: null}}, ""); + shouldCompileTo("{{person/name}}", {person: {}}, ""); + }); + + it("this keyword in paths", function() { + var string = "{{#goodbyes}}{{this}}{{/goodbyes}}"; + var hash = {goodbyes: ["goodbye", "Goodbye", "GOODBYE"]}; + shouldCompileTo(string, hash, "goodbyeGoodbyeGOODBYE", + "This keyword in paths evaluates to current context"); + + string = "{{#hellos}}{{this/text}}{{/hellos}}"; + hash = {hellos: [{text: "hello"}, {text: "Hello"}, {text: "HELLO"}]}; + shouldCompileTo(string, hash, "helloHelloHELLO", "This keyword evaluates in more complex paths"); + }); + + it("this keyword nested inside path", function() { + var string = "{{#hellos}}{{text/this/foo}}{{/hellos}}"; + (function() { + CompilerContext.compile(string); + }).should.throw(Error); + }); + + it("this keyword in helpers", function() { + var helpers = {foo: function(value) { + return 'bar ' + value; + }}; + var string = "{{#goodbyes}}{{foo this}}{{/goodbyes}}"; + var hash = {goodbyes: ["goodbye", "Goodbye", "GOODBYE"]}; + shouldCompileTo(string, [hash, helpers], "bar goodbyebar Goodbyebar GOODBYE", + "This keyword in paths evaluates to current context"); + + string = "{{#hellos}}{{foo this/text}}{{/hellos}}"; + hash = {hellos: [{text: "hello"}, {text: "Hello"}, {text: "HELLO"}]}; + shouldCompileTo(string, [hash, helpers], "bar hellobar Hellobar HELLO", "This keyword evaluates in more complex paths"); + }); + + it("this keyword nested inside helpers param", function() { + var string = "{{#hellos}}{{foo text/this/foo}}{{/hellos}}"; + (function() { + CompilerContext.compile(string); + }).should.throw(Error); + }); +}); diff --git a/spec/blocks.js b/spec/blocks.js new file mode 100644 index 000000000..1880eb584 --- /dev/null +++ b/spec/blocks.js @@ -0,0 +1,86 @@ +/*global CompilerContext, shouldCompileTo */ +describe('blocks', function() { + it("array", function() { + var string = "{{#goodbyes}}{{text}}! {{/goodbyes}}cruel {{world}}!"; + var hash = {goodbyes: [{text: "goodbye"}, {text: "Goodbye"}, {text: "GOODBYE"}], world: "world"}; + shouldCompileTo(string, hash, "goodbye! Goodbye! GOODBYE! cruel world!", + "Arrays iterate over the contents when not empty"); + + shouldCompileTo(string, {goodbyes: [], world: "world"}, "cruel world!", + "Arrays ignore the contents when empty"); + + }); + + it("array with @index", function() { + var string = "{{#goodbyes}}{{@index}}. {{text}}! {{/goodbyes}}cruel {{world}}!"; + var hash = {goodbyes: [{text: "goodbye"}, {text: "Goodbye"}, {text: "GOODBYE"}], world: "world"}; + + var template = CompilerContext.compile(string); + var result = template(hash); + + equal(result, "0. goodbye! 1. Goodbye! 2. GOODBYE! cruel world!", "The @index variable is used"); + }); + + it("empty block", function() { + var string = "{{#goodbyes}}{{/goodbyes}}cruel {{world}}!"; + var hash = {goodbyes: [{text: "goodbye"}, {text: "Goodbye"}, {text: "GOODBYE"}], world: "world"}; + shouldCompileTo(string, hash, "cruel world!", + "Arrays iterate over the contents when not empty"); + + shouldCompileTo(string, {goodbyes: [], world: "world"}, "cruel world!", + "Arrays ignore the contents when empty"); + }); + + it("block with complex lookup", function() { + var string = "{{#goodbyes}}{{text}} cruel {{../name}}! {{/goodbyes}}"; + var hash = {name: "Alan", goodbyes: [{text: "goodbye"}, {text: "Goodbye"}, {text: "GOODBYE"}]}; + + shouldCompileTo(string, hash, "goodbye cruel Alan! Goodbye cruel Alan! GOODBYE cruel Alan! ", + "Templates can access variables in contexts up the stack with relative path syntax"); + }); + + it("block with complex lookup using nested context", function() { + var string = "{{#goodbyes}}{{text}} cruel {{foo/../name}}! {{/goodbyes}}"; + + (function() { + CompilerContext.compile(string); + }).should.throw(Error); + }); + + it("block with deep nested complex lookup", function() { + var string = "{{#outer}}Goodbye {{#inner}}cruel {{../../omg}}{{/inner}}{{/outer}}"; + var hash = {omg: "OMG!", outer: [{ inner: [{ text: "goodbye" }] }] }; + + shouldCompileTo(string, hash, "Goodbye cruel OMG!"); + }); + + describe('inverted sections', function() { + it("inverted sections with unset value", function() { + var string = "{{#goodbyes}}{{this}}{{/goodbyes}}{{^goodbyes}}Right On!{{/goodbyes}}"; + var hash = {}; + shouldCompileTo(string, hash, "Right On!", "Inverted section rendered when value isn't set."); + }); + + it("inverted section with false value", function() { + var string = "{{#goodbyes}}{{this}}{{/goodbyes}}{{^goodbyes}}Right On!{{/goodbyes}}"; + var hash = {goodbyes: false}; + shouldCompileTo(string, hash, "Right On!", "Inverted section rendered when value is false."); + }); + + it("inverted section with empty set", function() { + var string = "{{#goodbyes}}{{this}}{{/goodbyes}}{{^goodbyes}}Right On!{{/goodbyes}}"; + var hash = {goodbyes: []}; + shouldCompileTo(string, hash, "Right On!", "Inverted section rendered when value is empty set."); + }); + + it("block inverted sections", function() { + shouldCompileTo("{{#people}}{{name}}{{^}}{{none}}{{/people}}", {none: "No people"}, + "No people"); + }); + + it("block inverted sections with empty arrays", function() { + shouldCompileTo("{{#people}}{{name}}{{^}}{{none}}{{/people}}", {none: "No people", people: []}, + "No people"); + }); + }); +}); diff --git a/spec/builtins.js b/spec/builtins.js new file mode 100644 index 000000000..4379725b3 --- /dev/null +++ b/spec/builtins.js @@ -0,0 +1,125 @@ +/*global CompilerContext, shouldCompileTo, compileWithPartials */ +describe('builtin helpers', function() { + var originalLog = Handlebars.log; + afterEach(function() { + Handlebars.log = originalLog; + }); + + describe('#if', function() { + it("if", function() { + var string = "{{#if goodbye}}GOODBYE {{/if}}cruel {{world}}!"; + shouldCompileTo(string, {goodbye: true, world: "world"}, "GOODBYE cruel world!", + "if with boolean argument shows the contents when true"); + shouldCompileTo(string, {goodbye: "dummy", world: "world"}, "GOODBYE cruel world!", + "if with string argument shows the contents"); + shouldCompileTo(string, {goodbye: false, world: "world"}, "cruel world!", + "if with boolean argument does not show the contents when false"); + shouldCompileTo(string, {world: "world"}, "cruel world!", + "if with undefined does not show the contents"); + shouldCompileTo(string, {goodbye: ['foo'], world: "world"}, "GOODBYE cruel world!", + "if with non-empty array shows the contents"); + shouldCompileTo(string, {goodbye: [], world: "world"}, "cruel world!", + "if with empty array does not show the contents"); + }); + + it("if with function argument", function() { + var string = "{{#if goodbye}}GOODBYE {{/if}}cruel {{world}}!"; + shouldCompileTo(string, {goodbye: function() {return true;}, world: "world"}, "GOODBYE cruel world!", + "if with function shows the contents when function returns true"); + shouldCompileTo(string, {goodbye: function() {return this.world;}, world: "world"}, "GOODBYE cruel world!", + "if with function shows the contents when function returns string"); + shouldCompileTo(string, {goodbye: function() {return false;}, world: "world"}, "cruel world!", + "if with function does not show the contents when returns false"); + shouldCompileTo(string, {goodbye: function() {return this.foo;}, world: "world"}, "cruel world!", + "if with function does not show the contents when returns undefined"); + }); + }); + + describe('#with', function() { + it("with", function() { + var string = "{{#with person}}{{first}} {{last}}{{/with}}"; + shouldCompileTo(string, {person: {first: "Alan", last: "Johnson"}}, "Alan Johnson"); + }); + it("with with function argument", function() { + var string = "{{#with person}}{{first}} {{last}}{{/with}}"; + shouldCompileTo(string, {person: function() { return {first: "Alan", last: "Johnson"};}}, "Alan Johnson"); + }); + }); + + describe('#each', function() { + it("each", function() { + var string = "{{#each goodbyes}}{{text}}! {{/each}}cruel {{world}}!"; + var hash = {goodbyes: [{text: "goodbye"}, {text: "Goodbye"}, {text: "GOODBYE"}], world: "world"}; + shouldCompileTo(string, hash, "goodbye! Goodbye! GOODBYE! cruel world!", + "each with array argument iterates over the contents when not empty"); + shouldCompileTo(string, {goodbyes: [], world: "world"}, "cruel world!", + "each with array argument ignores the contents when empty"); + }); + + it("each with an object and @key", function() { + var string = "{{#each goodbyes}}{{@key}}. {{text}}! {{/each}}cruel {{world}}!"; + var hash = {goodbyes: {"#1": {text: "goodbye"}, 2: {text: "GOODBYE"}}, world: "world"}; + + // Object property iteration order is undefined according to ECMA spec, + // so we need to check both possible orders + // @see http://stackoverflow.com/questions/280713/elements-order-in-a-for-in-loop + var actual = compileWithPartials(string, hash); + var expected1 = "<b>#1</b>. goodbye! 2. GOODBYE! cruel world!"; + var expected2 = "2. GOODBYE! <b>#1</b>. goodbye! cruel world!"; + + (actual === expected1 || actual === expected2).should.equal(true, "each with object argument iterates over the contents when not empty"); + shouldCompileTo(string, {goodbyes: [], world: "world"}, "cruel world!", + "each with object argument ignores the contents when empty"); + }); + + it("each with @index", function() { + var string = "{{#each goodbyes}}{{@index}}. {{text}}! {{/each}}cruel {{world}}!"; + var hash = {goodbyes: [{text: "goodbye"}, {text: "Goodbye"}, {text: "GOODBYE"}], world: "world"}; + + var template = CompilerContext.compile(string); + var result = template(hash); + + equal(result, "0. goodbye! 1. Goodbye! 2. GOODBYE! cruel world!", "The @index variable is used"); + }); + + it("each with function argument", function() { + var string = "{{#each goodbyes}}{{text}}! {{/each}}cruel {{world}}!"; + var hash = {goodbyes: function () { return [{text: "goodbye"}, {text: "Goodbye"}, {text: "GOODBYE"}];}, world: "world"}; + shouldCompileTo(string, hash, "goodbye! Goodbye! GOODBYE! cruel world!", + "each with array function argument iterates over the contents when not empty"); + shouldCompileTo(string, {goodbyes: [], world: "world"}, "cruel world!", + "each with array function argument ignores the contents when empty"); + }); + + it("data passed to helpers", function() { + var string = "{{#each letters}}{{this}}{{detectDataInsideEach}}{{/each}}"; + var hash = {letters: ['a', 'b', 'c']}; + + var template = CompilerContext.compile(string); + var result = template(hash, { + data: { + exclaim: '!' + } + }); + equal(result, 'a!b!c!', 'should output data'); + }); + + Handlebars.registerHelper('detectDataInsideEach', function(options) { + return options.data && options.data.exclaim; + }); + }); + + it("#log", function() { + + var string = "{{log blah}}"; + var hash = { blah: "whee" }; + + var levelArg, logArg; + Handlebars.log = function(level, arg){ levelArg = level, logArg = arg; }; + + shouldCompileTo(string, hash, "", "log should not display"); + equals(1, levelArg, "should call log with 1"); + equals("whee", logArg, "should call log with 'whee'"); + }); + +}); diff --git a/spec/data.js b/spec/data.js new file mode 100644 index 000000000..5af35b7eb --- /dev/null +++ b/spec/data.js @@ -0,0 +1,240 @@ +/*global CompilerContext */ +describe('data', function() { + it("passing in data to a compiled function that expects data - works with helpers", function() { + var template = CompilerContext.compile("{{hello}}", {data: true}); + + var helpers = { + hello: function(options) { + return options.data.adjective + " " + this.noun; + } + }; + + var result = template({noun: "cat"}, {helpers: helpers, data: {adjective: "happy"}}); + equals("happy cat", result, "Data output by helper"); + }); + + it("data can be looked up via @foo", function() { + var template = CompilerContext.compile("{{@hello}}"); + var result = template({}, { data: { hello: "hello" } }); + equals("hello", result, "@foo retrieves template data"); + }); + + var objectCreate = Handlebars.createFrame; + + it("deep @foo triggers automatic top-level data", function() { + var template = CompilerContext.compile('{{#let world="world"}}{{#if foo}}{{#if foo}}Hello {{@world}}{{/if}}{{/if}}{{/let}}'); + + var helpers = objectCreate(Handlebars.helpers); + + helpers.let = function(options) { + var frame = Handlebars.createFrame(options.data); + + for (var prop in options.hash) { + frame[prop] = options.hash[prop]; + } + return options.fn(this, { data: frame }); + }; + + var result = template({ foo: true }, { helpers: helpers }); + equals("Hello world", result, "Automatic data was triggered"); + }); + + it("parameter data can be looked up via @foo", function() { + var template = CompilerContext.compile("{{hello @world}}"); + var helpers = { + hello: function(noun) { + return "Hello " + noun; + } + }; + + var result = template({}, { helpers: helpers, data: { world: "world" } }); + equals("Hello world", result, "@foo as a parameter retrieves template data"); + }); + + it("hash values can be looked up via @foo", function() { + var template = CompilerContext.compile("{{hello noun=@world}}"); + var helpers = { + hello: function(options) { + return "Hello " + options.hash.noun; + } + }; + + var result = template({}, { helpers: helpers, data: { world: "world" } }); + equals("Hello world", result, "@foo as a parameter retrieves template data"); + }); + + it("nested parameter data can be looked up via @foo.bar", function() { + var template = CompilerContext.compile("{{hello @world.bar}}"); + var helpers = { + hello: function(noun) { + return "Hello " + noun; + } + }; + + var result = template({}, { helpers: helpers, data: { world: {bar: "world" } } }); + equals("Hello world", result, "@foo as a parameter retrieves template data"); + }); + + it("nested parameter data does not fail with @world.bar", function() { + var template = CompilerContext.compile("{{hello @world.bar}}"); + var helpers = { + hello: function(noun) { + return "Hello " + noun; + } + }; + + var result = template({}, { helpers: helpers, data: { foo: {bar: "world" } } }); + equals("Hello undefined", result, "@foo as a parameter retrieves template data"); + }); + + it("parameter data throws when using this scope references", function() { + var string = "{{#goodbyes}}{{text}} cruel {{@./name}}! {{/goodbyes}}"; + + (function() { + CompilerContext.compile(string); + }).should.throw(Error); + }); + + it("parameter data throws when using parent scope references", function() { + var string = "{{#goodbyes}}{{text}} cruel {{@../name}}! {{/goodbyes}}"; + + (function() { + CompilerContext.compile(string); + }).should.throw(Error); + }); + + it("parameter data throws when using complex scope references", function() { + var string = "{{#goodbyes}}{{text}} cruel {{@foo/../name}}! {{/goodbyes}}"; + + (function() { + CompilerContext.compile(string); + }).should.throw(Error); + }); + + it("data is inherited downstream", function() { + var template = CompilerContext.compile("{{#let foo=bar.baz}}{{@foo}}{{/let}}", { data: true }); + var helpers = { + let: function(options) { + for (var prop in options.hash) { + options.data[prop] = options.hash[prop]; + } + return options.fn(this); + } + }; + + var result = template({ bar: { baz: "hello world" } }, { helpers: helpers, data: {} }); + equals("hello world", result, "data variables are inherited downstream"); + }); + + it("passing in data to a compiled function that expects data - works with helpers in partials", function() { + var template = CompilerContext.compile("{{>my_partial}}", {data: true}); + + var partials = { + my_partial: CompilerContext.compile("{{hello}}", {data: true}) + }; + + var helpers = { + hello: function(options) { + return options.data.adjective + " " + this.noun; + } + }; + + var result = template({noun: "cat"}, {helpers: helpers, partials: partials, data: {adjective: "happy"}}); + equals("happy cat", result, "Data output by helper inside partial"); + }); + + it("passing in data to a compiled function that expects data - works with helpers and parameters", function() { + var template = CompilerContext.compile("{{hello world}}", {data: true}); + + var helpers = { + hello: function(noun, options) { + return options.data.adjective + " " + noun + (this.exclaim ? "!" : ""); + } + }; + + var result = template({exclaim: true, world: "world"}, {helpers: helpers, data: {adjective: "happy"}}); + equals("happy world!", result, "Data output by helper"); + }); + + it("passing in data to a compiled function that expects data - works with block helpers", function() { + var template = CompilerContext.compile("{{#hello}}{{world}}{{/hello}}", {data: true}); + + var helpers = { + hello: function(options) { + return options.fn(this); + }, + world: function(options) { + return options.data.adjective + " world" + (this.exclaim ? "!" : ""); + } + }; + + var result = template({exclaim: true}, {helpers: helpers, data: {adjective: "happy"}}); + equals("happy world!", result, "Data output by helper"); + }); + + it("passing in data to a compiled function that expects data - works with block helpers that use ..", function() { + var template = CompilerContext.compile("{{#hello}}{{world ../zomg}}{{/hello}}", {data: true}); + + var helpers = { + hello: function(options) { + return options.fn({exclaim: "?"}); + }, + world: function(thing, options) { + return options.data.adjective + " " + thing + (this.exclaim || ""); + } + }; + + var result = template({exclaim: true, zomg: "world"}, {helpers: helpers, data: {adjective: "happy"}}); + equals("happy world?", result, "Data output by helper"); + }); + + it("passing in data to a compiled function that expects data - data is passed to with block helpers where children use ..", function() { + var template = CompilerContext.compile("{{#hello}}{{world ../zomg}}{{/hello}}", {data: true}); + + var helpers = { + hello: function(options) { + return options.data.accessData + " " + options.fn({exclaim: "?"}); + }, + world: function(thing, options) { + return options.data.adjective + " " + thing + (this.exclaim || ""); + } + }; + + var result = template({exclaim: true, zomg: "world"}, {helpers: helpers, data: {adjective: "happy", accessData: "#win"}}); + equals("#win happy world?", result, "Data output by helper"); + }); + + it("you can override inherited data when invoking a helper", function() { + var template = CompilerContext.compile("{{#hello}}{{world zomg}}{{/hello}}", {data: true}); + + var helpers = { + hello: function(options) { + return options.fn({exclaim: "?", zomg: "world"}, { data: {adjective: "sad"} }); + }, + world: function(thing, options) { + return options.data.adjective + " " + thing + (this.exclaim || ""); + } + }; + + var result = template({exclaim: true, zomg: "planet"}, {helpers: helpers, data: {adjective: "happy"}}); + equals("sad world?", result, "Overriden data output by helper"); + }); + + + it("you can override inherited data when invoking a helper with depth", function() { + var template = CompilerContext.compile("{{#hello}}{{world ../zomg}}{{/hello}}", {data: true}); + + var helpers = { + hello: function(options) { + return options.fn({exclaim: "?"}, { data: {adjective: "sad"} }); + }, + world: function(thing, options) { + return options.data.adjective + " " + thing + (this.exclaim || ""); + } + }; + + var result = template({exclaim: true, zomg: "world"}, {helpers: helpers, data: {adjective: "happy"}}); + equals("sad world?", result, "Overriden data output by helper"); + }); + +}); diff --git a/spec/env/browser.js b/spec/env/browser.js new file mode 100644 index 000000000..a17aa6618 --- /dev/null +++ b/spec/env/browser.js @@ -0,0 +1,13 @@ +require('./common'); + +global.Handlebars = require('../../dist/handlebars'); + +global.CompilerContext = { + compile: function(template, options) { + var templateSpec = Handlebars.precompile(template, options); + return Handlebars.template(eval('(' + templateSpec + ')')); + }, + compileWithPartial: function(template, options) { + return Handlebars.compile(template, options); + } +}; diff --git a/spec/env/common.js b/spec/env/common.js new file mode 100644 index 000000000..53ddd61ae --- /dev/null +++ b/spec/env/common.js @@ -0,0 +1,28 @@ +global.should = require('should'); + +global.shouldCompileTo = function(string, hashOrArray, expected, message) { + shouldCompileToWithPartials(string, hashOrArray, false, expected, message); +}; + +global.shouldCompileToWithPartials = function(string, hashOrArray, partials, expected, message) { + var result = compileWithPartials(string, hashOrArray, partials); + result.should.equal(expected, "'" + expected + "' should === '" + result + "': " + message); +}; + +global.compileWithPartials = function(string, hashOrArray, partials) { + var template = CompilerContext[partials ? 'compileWithPartial' : 'compile'](string), ary; + if(Object.prototype.toString.call(hashOrArray) === "[object Array]") { + ary = []; + ary.push(hashOrArray[0]); + ary.push({ helpers: hashOrArray[1], partials: hashOrArray[2] }); + } else { + ary = [hashOrArray]; + } + + return template.apply(this, ary); +}; + + +global.equals = global.equal = function(a, b, msg) { + a.should.equal(b, msg || ''); +}; diff --git a/spec/env/node.js b/spec/env/node.js new file mode 100644 index 000000000..fe34f9467 --- /dev/null +++ b/spec/env/node.js @@ -0,0 +1,13 @@ +require('./common'); + +global.Handlebars = require('../../lib/handlebars'); + +global.CompilerContext = { + compile: function(template, options) { + var templateSpec = Handlebars.precompile(template, options); + return Handlebars.template(eval('(' + templateSpec + ')')); + }, + compileWithPartial: function(template, options) { + return Handlebars.compile(template, options); + } +}; diff --git a/spec/env/runner.js b/spec/env/runner.js new file mode 100644 index 000000000..919f0aeef --- /dev/null +++ b/spec/env/runner.js @@ -0,0 +1,40 @@ +var fs = require('fs'), + Mocha = require('mocha'), + path = require('path'); + +var errors = 0, + testDir = path.dirname(__dirname), + grep = process.argv[2]; + +var files = fs.readdirSync(testDir) + .filter(function(name) { return (/.*\.js$/).test(name); }) + .map(function(name) { return testDir + '/' + name; }); + +run('./node', function() { + run('./browser', function() { + run('./runtime', function() { + process.exit(errors); + }); + }); +}); + + +function run(env, callback) { + var mocha = new Mocha(); + mocha.ui('bdd'); + mocha.files = files.slice(); + if (grep) { + mocha.grep(grep); + } + + files.forEach(function(name) { + delete require.cache[name]; + }); + + console.log('Running env: ' + env); + require(env); + mocha.run(function(errorCount) { + errors += errorCount; + callback(); + }); +} diff --git a/spec/env/runtime.js b/spec/env/runtime.js new file mode 100644 index 000000000..fb4b342b3 --- /dev/null +++ b/spec/env/runtime.js @@ -0,0 +1,15 @@ +require('./common'); + +global.Handlebars = require('../../dist/handlebars.runtime'); + +var compiler = require('../../lib/handlebars'); + +global.CompilerContext = { + compile: function(template, options) { + var templateSpec = compiler.precompile(template, options); + return Handlebars.template(eval('(' + templateSpec + ')')); + }, + compileWithPartial: function(template, options) { + return compiler.compile(template, options); + } +}; diff --git a/spec/helpers.js b/spec/helpers.js new file mode 100644 index 000000000..9fb32e6c6 --- /dev/null +++ b/spec/helpers.js @@ -0,0 +1,532 @@ +/*global CompilerContext, shouldCompileTo, shouldCompileToWithPartials */ +describe('helpers', function() { + it("helper with complex lookup$", function() { + var string = "{{#goodbyes}}{{{link ../prefix}}}{{/goodbyes}}"; + var hash = {prefix: "/root", goodbyes: [{text: "Goodbye", url: "goodbye"}]}; + var helpers = {link: function(prefix) { + return "" + this.text + ""; + }}; + shouldCompileTo(string, [hash, helpers], "Goodbye"); + }); + + it("helper block with complex lookup expression", function() { + var string = "{{#goodbyes}}{{../name}}{{/goodbyes}}"; + var hash = {name: "Alan"}; + var helpers = {goodbyes: function(options) { + var out = ""; + var byes = ["Goodbye", "goodbye", "GOODBYE"]; + for (var i = 0,j = byes.length; i < j; i++) { + out += byes[i] + " " + options.fn(this) + "! "; + } + return out; + }}; + shouldCompileTo(string, [hash, helpers], "Goodbye Alan! goodbye Alan! GOODBYE Alan! "); + }); + + it("helper with complex lookup and nested template", function() { + var string = "{{#goodbyes}}{{#link ../prefix}}{{text}}{{/link}}{{/goodbyes}}"; + var hash = {prefix: '/root', goodbyes: [{text: "Goodbye", url: "goodbye"}]}; + var helpers = {link: function (prefix, options) { + return "" + options.fn(this) + ""; + }}; + shouldCompileToWithPartials(string, [hash, helpers], false, "Goodbye"); + }); + + it("helper with complex lookup and nested template in VM+Compiler", function() { + var string = "{{#goodbyes}}{{#link ../prefix}}{{text}}{{/link}}{{/goodbyes}}"; + var hash = {prefix: '/root', goodbyes: [{text: "Goodbye", url: "goodbye"}]}; + var helpers = {link: function (prefix, options) { + return "" + options.fn(this) + ""; + }}; + shouldCompileToWithPartials(string, [hash, helpers], true, "Goodbye"); + }); + + it("block helper", function() { + var string = "{{#goodbyes}}{{text}}! {{/goodbyes}}cruel {{world}}!"; + var template = CompilerContext.compile(string); + + var result = template({world: "world"}, { helpers: {goodbyes: function(options) { return options.fn({text: "GOODBYE"}); }}}); + equal(result, "GOODBYE! cruel world!", "Block helper executed"); + }); + + it("block helper staying in the same context", function() { + var string = "{{#form}}

{{name}}

{{/form}}"; + var template = CompilerContext.compile(string); + + var result = template({name: "Yehuda"}, {helpers: {form: function(options) { return "
" + options.fn(this) + "
"; } }}); + equal(result, "

Yehuda

", "Block helper executed with current context"); + }); + + it("block helper should have context in this", function() { + var source = "
    {{#people}}
  • {{#link}}{{name}}{{/link}}
  • {{/people}}
"; + var link = function(options) { + return '' + options.fn(this) + ''; + }; + var data = { "people": [ + { "name": "Alan", "id": 1 }, + { "name": "Yehuda", "id": 2 } + ]}; + + shouldCompileTo(source, [data, {link: link}], ""); + }); + + it("block helper for undefined value", function() { + shouldCompileTo("{{#empty}}shouldn't render{{/empty}}", {}, ""); + }); + + it("block helper passing a new context", function() { + var string = "{{#form yehuda}}

{{name}}

{{/form}}"; + var template = CompilerContext.compile(string); + + var result = template({yehuda: {name: "Yehuda"}}, { helpers: {form: function(context, options) { return "
" + options.fn(context) + "
"; }}}); + equal(result, "

Yehuda

", "Context variable resolved"); + }); + + it("block helper passing a complex path context", function() { + var string = "{{#form yehuda/cat}}

{{name}}

{{/form}}"; + var template = CompilerContext.compile(string); + + var result = template({yehuda: {name: "Yehuda", cat: {name: "Harold"}}}, { helpers: {form: function(context, options) { return "
" + options.fn(context) + "
"; }}}); + equal(result, "

Harold

", "Complex path variable resolved"); + }); + + it("nested block helpers", function() { + var string = "{{#form yehuda}}

{{name}}

{{#link}}Hello{{/link}}{{/form}}"; + var template = CompilerContext.compile(string); + + var result = template({ + yehuda: {name: "Yehuda" } + }, { + helpers: { + link: function(options) { return "" + options.fn(this) + ""; }, + form: function(context, options) { return "
" + options.fn(context) + "
"; } + } + }); + equal(result, "

Yehuda

Hello
", "Both blocks executed"); + }); + + it("block helper inverted sections", function() { + var string = "{{#list people}}{{name}}{{^}}Nobody's here{{/list}}"; + var list = function(context, options) { + if (context.length > 0) { + var out = "
    "; + for(var i = 0,j=context.length; i < j; i++) { + out += "
  • "; + out += options.fn(context[i]); + out += "
  • "; + } + out += "
"; + return out; + } else { + return "

" + options.inverse(this) + "

"; + } + }; + + var hash = {people: [{name: "Alan"}, {name: "Yehuda"}]}; + var empty = {people: []}; + var rootMessage = { + people: [], + message: "Nobody's here" + }; + + var messageString = "{{#list people}}Hello{{^}}{{message}}{{/list}}"; + + // the meaning here may be kind of hard to catch, but list.not is always called, + // so we should see the output of both + shouldCompileTo(string, [hash, { list: list }], "
  • Alan
  • Yehuda
", "an inverse wrapper is passed in as a new context"); + shouldCompileTo(string, [empty, { list: list }], "

Nobody's here

", "an inverse wrapper can be optionally called"); + shouldCompileTo(messageString, [rootMessage, { list: list }], "

Nobody's here

", "the context of an inverse is the parent of the block"); + }); + + describe("helpers hash", function() { + it("providing a helpers hash", function() { + shouldCompileTo("Goodbye {{cruel}} {{world}}!", [{cruel: "cruel"}, {world: function() { return "world"; }}], "Goodbye cruel world!", + "helpers hash is available"); + + shouldCompileTo("Goodbye {{#iter}}{{cruel}} {{world}}{{/iter}}!", [{iter: [{cruel: "cruel"}]}, {world: function() { return "world"; }}], + "Goodbye cruel world!", "helpers hash is available inside other blocks"); + }); + + it("in cases of conflict, helpers win", function() { + shouldCompileTo("{{{lookup}}}", [{lookup: 'Explicit'}, {lookup: function() { return 'helpers'; }}], "helpers", + "helpers hash has precedence escaped expansion"); + shouldCompileTo("{{lookup}}", [{lookup: 'Explicit'}, {lookup: function() { return 'helpers'; }}], "helpers", + "helpers hash has precedence simple expansion"); + }); + + it("the helpers hash is available is nested contexts", function() { + shouldCompileTo("{{#outer}}{{#inner}}{{helper}}{{/inner}}{{/outer}}", + [{'outer': {'inner': {'unused':[]}}}, {'helper': function() { return 'helper'; }}], "helper", + "helpers hash is available in nested contexts."); + }); + + it("the helper hash should augment the global hash", function() { + Handlebars.registerHelper('test_helper', function() { return 'found it!'; }); + + shouldCompileTo( + "{{test_helper}} {{#if cruel}}Goodbye {{cruel}} {{world}}!{{/if}}", [ + {cruel: "cruel"}, + {world: function() { return "world!"; }} + ], + "found it! Goodbye cruel world!!"); + }); + }); + + it("Multiple global helper registration", function() { + var helpers = Handlebars.helpers; + try { + Handlebars.helpers = {}; + Handlebars.registerHelper({ + 'if': helpers['if'], + world: function() { return "world!"; }, + test_helper: function() { return 'found it!'; } + }); + + shouldCompileTo( + "{{test_helper}} {{#if cruel}}Goodbye {{cruel}} {{world}}!{{/if}}", + [{cruel: "cruel"}], + "found it! Goodbye cruel world!!"); + } finally { + if (helpers) { + Handlebars.helpers = helpers; + } + } + }); + it("negative number literals work", function() { + var string = 'Message: {{hello -12}}'; + var hash = {}; + var helpers = {hello: function(times) { + if(typeof times !== 'number') { times = "NaN"; } + return "Hello " + times + " times"; + }}; + shouldCompileTo(string, [hash, helpers], "Message: Hello -12 times", "template with a negative integer literal"); + }); + + describe("String literal parameters", function() { + it("simple literals work", function() { + var string = 'Message: {{hello "world" 12 true false}}'; + var hash = {}; + var helpers = {hello: function(param, times, bool1, bool2) { + if(typeof times !== 'number') { times = "NaN"; } + if(typeof bool1 !== 'boolean') { bool1 = "NaB"; } + if(typeof bool2 !== 'boolean') { bool2 = "NaB"; } + return "Hello " + param + " " + times + " times: " + bool1 + " " + bool2; + }}; + shouldCompileTo(string, [hash, helpers], "Message: Hello world 12 times: true false", "template with a simple String literal"); + }); + + it("using a quote in the middle of a parameter raises an error", function() { + (function() { + var string = 'Message: {{hello wo"rld"}}'; + CompilerContext.compile(string); + }).should.throw(Error); + }); + + it("escaping a String is possible", function(){ + var string = 'Message: {{{hello "\\"world\\""}}}'; + var hash = {}; + var helpers = {hello: function(param) { return "Hello " + param; }}; + shouldCompileTo(string, [hash, helpers], "Message: Hello \"world\"", "template with an escaped String literal"); + }); + + it("it works with ' marks", function() { + var string = 'Message: {{{hello "Alan\'s world"}}}'; + var hash = {}; + var helpers = {hello: function(param) { return "Hello " + param; }}; + shouldCompileTo(string, [hash, helpers], "Message: Hello Alan's world", "template with a ' mark"); + }); + }); + + it("negative number literals work", function() { + var string = 'Message: {{hello -12}}'; + var hash = {}; + var helpers = {hello: function(times) { + if(typeof times !== 'number') { times = "NaN"; } + return "Hello " + times + " times"; + }}; + shouldCompileTo(string, [hash, helpers], "Message: Hello -12 times", "template with a negative integer literal"); + }); + + describe("multiple parameters", function() { + it("simple multi-params work", function() { + var string = 'Message: {{goodbye cruel world}}'; + var hash = {cruel: "cruel", world: "world"}; + var helpers = {goodbye: function(cruel, world) { return "Goodbye " + cruel + " " + world; }}; + shouldCompileTo(string, [hash, helpers], "Message: Goodbye cruel world", "regular helpers with multiple params"); + }); + + it("block multi-params work", function() { + var string = 'Message: {{#goodbye cruel world}}{{greeting}} {{adj}} {{noun}}{{/goodbye}}'; + var hash = {cruel: "cruel", world: "world"}; + var helpers = {goodbye: function(cruel, world, options) { + return options.fn({greeting: "Goodbye", adj: cruel, noun: world}); + }}; + shouldCompileTo(string, [hash, helpers], "Message: Goodbye cruel world", "block helpers with multiple params"); + }); + }); + describe('hash', function() { + it("helpers can take an optional hash", function() { + var template = CompilerContext.compile('{{goodbye cruel="CRUEL" world="WORLD" times=12}}'); + + var helpers = { + goodbye: function(options) { + return "GOODBYE " + options.hash.cruel + " " + options.hash.world + " " + options.hash.times + " TIMES"; + } + }; + + var context = {}; + + var result = template(context, {helpers: helpers}); + equals(result, "GOODBYE CRUEL WORLD 12 TIMES", "Helper output hash"); + }); + + it("helpers can take an optional hash with booleans", function() { + var helpers = { + goodbye: function(options) { + if (options.hash.print === true) { + return "GOODBYE " + options.hash.cruel + " " + options.hash.world; + } else if (options.hash.print === false) { + return "NOT PRINTING"; + } else { + return "THIS SHOULD NOT HAPPEN"; + } + } + }; + + var context = {}; + + var template = CompilerContext.compile('{{goodbye cruel="CRUEL" world="WORLD" print=true}}'); + var result = template(context, {helpers: helpers}); + equals(result, "GOODBYE CRUEL WORLD", "Helper output hash"); + + template = CompilerContext.compile('{{goodbye cruel="CRUEL" world="WORLD" print=false}}'); + result = template(context, {helpers: helpers}); + equals(result, "NOT PRINTING", "Boolean helper parameter honored"); + }); + + it("block helpers can take an optional hash", function() { + var template = CompilerContext.compile('{{#goodbye cruel="CRUEL" times=12}}world{{/goodbye}}'); + + var helpers = { + goodbye: function(options) { + return "GOODBYE " + options.hash.cruel + " " + options.fn(this) + " " + options.hash.times + " TIMES"; + } + }; + + var result = template({}, {helpers: helpers}); + equals(result, "GOODBYE CRUEL world 12 TIMES", "Hash parameters output"); + }); + + it("block helpers can take an optional hash with single quoted stings", function() { + var template = CompilerContext.compile("{{#goodbye cruel='CRUEL' times=12}}world{{/goodbye}}"); + + var helpers = { + goodbye: function(options) { + return "GOODBYE " + options.hash.cruel + " " + options.fn(this) + " " + options.hash.times + " TIMES"; + } + }; + + var result = template({}, {helpers: helpers}); + equals(result, "GOODBYE CRUEL world 12 TIMES", "Hash parameters output"); + }); + + it("block helpers can take an optional hash with booleans", function() { + var helpers = { + goodbye: function(options) { + if (options.hash.print === true) { + return "GOODBYE " + options.hash.cruel + " " + options.fn(this); + } else if (options.hash.print === false) { + return "NOT PRINTING"; + } else { + return "THIS SHOULD NOT HAPPEN"; + } + } + }; + + var template = CompilerContext.compile('{{#goodbye cruel="CRUEL" print=true}}world{{/goodbye}}'); + var result = template({}, {helpers: helpers}); + equals(result, "GOODBYE CRUEL world", "Boolean hash parameter honored"); + + template = CompilerContext.compile('{{#goodbye cruel="CRUEL" print=false}}world{{/goodbye}}'); + result = template({}, {helpers: helpers}); + equals(result, "NOT PRINTING", "Boolean hash parameter honored"); + }); + }); + + describe("helperMissing", function() { + it("if a context is not found, helperMissing is used", function() { + (function() { + var template = CompilerContext.compile("{{hello}} {{link_to world}}"); + template({}); + }).should.throw(/Missing helper: 'link_to'/); + }); + + it("if a context is not found, custom helperMissing is used", function() { + var string = "{{hello}} {{link_to world}}"; + var context = { hello: "Hello", world: "world" }; + + var helpers = { + helperMissing: function(helper, context) { + if(helper === "link_to") { + return new Handlebars.SafeString("" + context + ""); + } + } + }; + + shouldCompileTo(string, [context, helpers], "Hello world"); + }); + }); + + describe("knownHelpers", function() { + it("Known helper should render helper", function() { + var template = CompilerContext.compile("{{hello}}", {knownHelpers: {"hello" : true}}); + + var result = template({}, {helpers: {hello: function() { return "foo"; }}}); + equal(result, "foo", "'foo' should === '" + result); + }); + + it("Unknown helper in knownHelpers only mode should be passed as undefined", function() { + var template = CompilerContext.compile("{{typeof hello}}", {knownHelpers: {'typeof': true}, knownHelpersOnly: true}); + + var result = template({}, {helpers: {'typeof': function(arg) { return typeof arg; }, hello: function() { return "foo"; }}}); + equal(result, "undefined", "'undefined' should === '" + result); + }); + it("Builtin helpers available in knownHelpers only mode", function() { + var template = CompilerContext.compile("{{#unless foo}}bar{{/unless}}", {knownHelpersOnly: true}); + + var result = template({}); + equal(result, "bar", "'bar' should === '" + result); + }); + it("Field lookup works in knownHelpers only mode", function() { + var template = CompilerContext.compile("{{foo}}", {knownHelpersOnly: true}); + + var result = template({foo: 'bar'}); + equal(result, "bar", "'bar' should === '" + result); + }); + it("Conditional blocks work in knownHelpers only mode", function() { + var template = CompilerContext.compile("{{#foo}}bar{{/foo}}", {knownHelpersOnly: true}); + + var result = template({foo: 'baz'}); + equal(result, "bar", "'bar' should === '" + result); + }); + it("Invert blocks work in knownHelpers only mode", function() { + var template = CompilerContext.compile("{{^foo}}bar{{/foo}}", {knownHelpersOnly: true}); + + var result = template({foo: false}); + equal(result, "bar", "'bar' should === '" + result); + }); + it("Functions are bound to the context in knownHelpers only mode", function() { + var template = CompilerContext.compile("{{foo}}", {knownHelpersOnly: true}); + var result = template({foo: function() { return this.bar; }, bar: 'bar'}); + equal(result, "bar", "'bar' should === '" + result); + }); + it("Unknown helper call in knownHelpers only mode should throw", function() { + (function() { + CompilerContext.compile("{{typeof hello}}", {knownHelpersOnly: true}); + }).should.throw(Error); + }); + }); + + describe("blockHelperMissing", function() { + it("lambdas are resolved by blockHelperMissing, not handlebars proper", function() { + var string = "{{#truthy}}yep{{/truthy}}"; + var data = { truthy: function() { return true; } }; + shouldCompileTo(string, data, "yep"); + }); + it("lambdas resolved by blockHelperMissing are bound to the context", function() { + var string = "{{#truthy}}yep{{/truthy}}"; + var boundData = { truthy: function() { return this.truthiness(); }, truthiness: function() { return false; } }; + shouldCompileTo(string, boundData, ""); + }); + }); + + describe('name conflicts', function() { + it("helpers take precedence over same-named context properties", function() { + var template = CompilerContext.compile("{{goodbye}} {{cruel world}}"); + + var helpers = { + goodbye: function() { + return this.goodbye.toUpperCase(); + }, + + cruel: function(world) { + return "cruel " + world.toUpperCase(); + } + }; + + var context = { + goodbye: "goodbye", + world: "world" + }; + + var result = template(context, {helpers: helpers}); + equals(result, "GOODBYE cruel WORLD", "Helper executed"); + }); + + it("helpers take precedence over same-named context properties$", function() { + var template = CompilerContext.compile("{{#goodbye}} {{cruel world}}{{/goodbye}}"); + + var helpers = { + goodbye: function(options) { + return this.goodbye.toUpperCase() + options.fn(this); + }, + + cruel: function(world) { + return "cruel " + world.toUpperCase(); + } + }; + + var context = { + goodbye: "goodbye", + world: "world" + }; + + var result = template(context, {helpers: helpers}); + equals(result, "GOODBYE cruel WORLD", "Helper executed"); + }); + + it("Scoped names take precedence over helpers", function() { + var template = CompilerContext.compile("{{this.goodbye}} {{cruel world}} {{cruel this.goodbye}}"); + + var helpers = { + goodbye: function() { + return this.goodbye.toUpperCase(); + }, + + cruel: function(world) { + return "cruel " + world.toUpperCase(); + }, + }; + + var context = { + goodbye: "goodbye", + world: "world" + }; + + var result = template(context, {helpers: helpers}); + equals(result, "goodbye cruel WORLD cruel GOODBYE", "Helper not executed"); + }); + + it("Scoped names take precedence over block helpers", function() { + var template = CompilerContext.compile("{{#goodbye}} {{cruel world}}{{/goodbye}} {{this.goodbye}}"); + + var helpers = { + goodbye: function(options) { + return this.goodbye.toUpperCase() + options.fn(this); + }, + + cruel: function(world) { + return "cruel " + world.toUpperCase(); + }, + }; + + var context = { + goodbye: "goodbye", + world: "world" + }; + + var result = template(context, {helpers: helpers}); + equals(result, "GOODBYE cruel WORLD goodbye", "Helper executed"); + }); + }); +}); diff --git a/spec/parser.js b/spec/parser.js index 6fb8d9a37..3f2a0127f 100644 --- a/spec/parser.js +++ b/spec/parser.js @@ -1,8 +1,8 @@ -var Handlebars = require('../lib/handlebars'); - -require('should'); - describe('parser', function() { + if (!Handlebars.print) { + return; + } + function ast_for(template) { var ast = Handlebars.parse(template); return Handlebars.print(ast); diff --git a/spec/partials.js b/spec/partials.js new file mode 100644 index 000000000..d919eede1 --- /dev/null +++ b/spec/partials.js @@ -0,0 +1,119 @@ +/*global CompilerContext, shouldCompileTo, shouldCompileToWithPartials */ +describe('partials', function() { + it("basic partials", function() { + var string = "Dudes: {{#dudes}}{{> dude}}{{/dudes}}"; + var partial = "{{name}} ({{url}}) "; + var hash = {dudes: [{name: "Yehuda", url: "http://yehuda"}, {name: "Alan", url: "http://alan"}]}; + shouldCompileToWithPartials(string, [hash, {}, {dude: partial}], true, "Dudes: Yehuda (http://yehuda) Alan (http://alan) ", + "Basic partials output based on current context."); + }); + + it("partials with context", function() { + var string = "Dudes: {{>dude dudes}}"; + var partial = "{{#this}}{{name}} ({{url}}) {{/this}}"; + var hash = {dudes: [{name: "Yehuda", url: "http://yehuda"}, {name: "Alan", url: "http://alan"}]}; + shouldCompileToWithPartials(string, [hash, {}, {dude: partial}], true, "Dudes: Yehuda (http://yehuda) Alan (http://alan) ", + "Partials can be passed a context"); + }); + + it("partial in a partial", function() { + var string = "Dudes: {{#dudes}}{{>dude}}{{/dudes}}"; + var dude = "{{name}} {{> url}} "; + var url = "{{url}}"; + var hash = {dudes: [{name: "Yehuda", url: "http://yehuda"}, {name: "Alan", url: "http://alan"}]}; + shouldCompileToWithPartials(string, [hash, {}, {dude: dude, url: url}], true, "Dudes: Yehuda http://yehuda Alan http://alan ", "Partials are rendered inside of other partials"); + }); + + it("rendering undefined partial throws an exception", function() { + (function() { + var template = CompilerContext.compile("{{> whatever}}"); + template(); + }).should.throw(Handlebars.Exception, 'The partial whatever could not be found'); + }); + + it("rendering template partial in vm mode throws an exception", function() { + (function() { + var template = CompilerContext.compile("{{> whatever}}"); + template(); + }).should.throw(Handlebars.Exception, 'The partial whatever could not be found'); + }); + + it("rendering function partial in vm mode", function() { + var string = "Dudes: {{#dudes}}{{> dude}}{{/dudes}}"; + var partial = function(context) { + return context.name + ' (' + context.url + ') '; + }; + var hash = {dudes: [{name: "Yehuda", url: "http://yehuda"}, {name: "Alan", url: "http://alan"}]}; + shouldCompileTo(string, [hash, {}, {dude: partial}], "Dudes: Yehuda (http://yehuda) Alan (http://alan) ", + "Function partials output based in VM."); + }); + + it("GH-14: a partial preceding a selector", function() { + var string = "Dudes: {{>dude}} {{another_dude}}"; + var dude = "{{name}}"; + var hash = {name:"Jeepers", another_dude:"Creepers"}; + shouldCompileToWithPartials(string, [hash, {}, {dude:dude}], true, "Dudes: Jeepers Creepers", "Regular selectors can follow a partial"); + }); + + it("Partials with slash paths", function() { + var string = "Dudes: {{> shared/dude}}"; + var dude = "{{name}}"; + var hash = {name:"Jeepers", another_dude:"Creepers"}; + shouldCompileToWithPartials(string, [hash, {}, {'shared/dude':dude}], true, "Dudes: Jeepers", "Partials can use literal paths"); + }); + + it("Partials with slash and point paths", function() { + var string = "Dudes: {{> shared/dude.thing}}"; + var dude = "{{name}}"; + var hash = {name:"Jeepers", another_dude:"Creepers"}; + shouldCompileToWithPartials(string, [hash, {}, {'shared/dude.thing':dude}], true, "Dudes: Jeepers", "Partials can use literal with points in paths"); + }); + + it("Global Partials", function() { + Handlebars.registerPartial('global_test', '{{another_dude}}'); + + var string = "Dudes: {{> shared/dude}} {{> global_test}}"; + var dude = "{{name}}"; + var hash = {name:"Jeepers", another_dude:"Creepers"}; + shouldCompileToWithPartials(string, [hash, {}, {'shared/dude':dude}], true, "Dudes: Jeepers Creepers", "Partials can use globals or passed"); + }); + + it("Multiple partial registration", function() { + Handlebars.registerPartial({ + 'shared/dude': '{{name}}', + global_test: '{{another_dude}}' + }); + + var string = "Dudes: {{> shared/dude}} {{> global_test}}"; + var hash = {name:"Jeepers", another_dude:"Creepers"}; + shouldCompileToWithPartials(string, [hash], true, "Dudes: Jeepers Creepers", "Partials can use globals or passed"); + }); + + it("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"); + }); + + it("Partials with complex path", function() { + var string = "Dudes: {{> 404/asdf?.bar}}"; + var dude = "{{name}}"; + var hash = {name:"Jeepers", another_dude:"Creepers"}; + shouldCompileToWithPartials(string, [hash, {}, {'404/asdf?.bar':dude}], true, "Dudes: Jeepers", "Partials can use literal paths"); + }); + + it("Partials with escaped", function() { + var string = "Dudes: {{> [+404/asdf?.bar]}}"; + var dude = "{{name}}"; + var hash = {name:"Jeepers", another_dude:"Creepers"}; + shouldCompileToWithPartials(string, [hash, {}, {'+404/asdf?.bar':dude}], true, "Dudes: Jeepers", "Partials can use literal paths"); + }); + + it("Partials with string", function() { + var string = "Dudes: {{> \"+404/asdf?.bar\"}}"; + var dude = "{{name}}"; + var hash = {name:"Jeepers", another_dude:"Creepers"}; + shouldCompileToWithPartials(string, [hash, {}, {'+404/asdf?.bar':dude}], true, "Dudes: Jeepers", "Partials can use literal paths"); + }); +}); diff --git a/spec/qunit_spec.js b/spec/qunit_spec.js deleted file mode 100644 index 5d52e444b..000000000 --- a/spec/qunit_spec.js +++ /dev/null @@ -1,1657 +0,0 @@ -var Handlebars; -if (!Handlebars) { - // Setup for Node package testing - Handlebars = require('../lib/handlebars'); - - var assert = require("assert"), - - equal = assert.equal, - equals = assert.equal, - ok = assert.ok; - - // Note that this doesn't have the same context separation as the rspec test. - // Both should be run for full acceptance of the two libary modes. - var CompilerContext = { - compile: function(template, options) { - var templateSpec = Handlebars.precompile(template, options); - return Handlebars.template(eval('(' + templateSpec + ')')); - }, - compileWithPartial: function(template, options) { - return Handlebars.compile(template, options); - } - }; -} else { - var _equal = equal; - equals = equal = function(a, b, msg) { - // Allow exec with missing message params - _equal(a, b, msg || ''); - }; -} - -suite("basic context"); - -function shouldCompileTo(string, hashOrArray, expected, message) { - shouldCompileToWithPartials(string, hashOrArray, false, expected, message); -} - -function shouldCompileToWithPartials(string, hashOrArray, partials, expected, message) { - var result = compileWithPartials(string, hashOrArray, partials); - equal(result, expected, "'" + expected + "' should === '" + result + "': " + message); -} - -function compileWithPartials(string, hashOrArray, partials) { - var template = CompilerContext[partials ? 'compileWithPartial' : 'compile'](string), ary; - if(Object.prototype.toString.call(hashOrArray) === "[object Array]") { - ary = []; - ary.push(hashOrArray[0]); - ary.push({ helpers: hashOrArray[1], partials: hashOrArray[2] }); - } else { - ary = [hashOrArray]; - } - - return template.apply(this, ary); -} - -function shouldThrow(fn, exception, message) { - var caught = false, - exType, exMessage; - - if (exception instanceof Array) { - exType = exception[0]; - exMessage = exception[1]; - } else if (typeof exception === 'string') { - exType = Error; - exMessage = exception; - } else { - exType = exception; - } - - try { - fn(); - } - catch (e) { - if (e instanceof exType) { - if (!exMessage || e.message === exMessage) { - caught = true; - } - } - } - - ok(caught, message || null); -} - -test("most basic", function() { - shouldCompileTo("{{foo}}", { foo: "foo" }, "foo"); -}); - -test("escaping", function() { - shouldCompileTo("\\{{foo}}", { foo: "food" }, "{{foo}}"); - shouldCompileTo("\\\\{{foo}}", { foo: "food" }, "\\food"); - shouldCompileTo("\\\\ {{foo}}", { foo: "food" }, "\\\\ food"); -}); - -test("compiling with a basic context", function() { - shouldCompileTo("Goodbye\n{{cruel}}\n{{world}}!", {cruel: "cruel", world: "world"}, "Goodbye\ncruel\nworld!", - "It works if all the required keys are provided"); -}); - -test("comments", function() { - shouldCompileTo("{{! Goodbye}}Goodbye\n{{cruel}}\n{{world}}!", - {cruel: "cruel", world: "world"}, "Goodbye\ncruel\nworld!", - "comments are ignored"); -}); - -test("boolean", function() { - var string = "{{#goodbye}}GOODBYE {{/goodbye}}cruel {{world}}!"; - shouldCompileTo(string, {goodbye: true, world: "world"}, "GOODBYE cruel world!", - "booleans show the contents when true"); - - shouldCompileTo(string, {goodbye: false, world: "world"}, "cruel world!", - "booleans do not show the contents when false"); -}); - -test("zeros", function() { - shouldCompileTo("num1: {{num1}}, num2: {{num2}}", {num1: 42, num2: 0}, - "num1: 42, num2: 0"); - shouldCompileTo("num: {{.}}", 0, "num: 0"); - shouldCompileTo("num: {{num1/num2}}", {num1: {num2: 0}}, "num: 0"); -}); - -test("newlines", function() { - shouldCompileTo("Alan's\nTest", {}, "Alan's\nTest"); - shouldCompileTo("Alan's\rTest", {}, "Alan's\rTest"); -}); - -test("escaping text", function() { - shouldCompileTo("Awesome's", {}, "Awesome's", "text is escaped so that it doesn't get caught on single quotes"); - shouldCompileTo("Awesome\\", {}, "Awesome\\", "text is escaped so that the closing quote can't be ignored"); - shouldCompileTo("Awesome\\\\ foo", {}, "Awesome\\\\ foo", "text is escaped so that it doesn't mess up backslashes"); - shouldCompileTo("Awesome {{foo}}", {foo: '\\'}, "Awesome \\", "text is escaped so that it doesn't mess up backslashes"); - shouldCompileTo(' " " ', {}, ' " " ', "double quotes never produce invalid javascript"); -}); - -test("escaping expressions", function() { - shouldCompileTo("{{{awesome}}}", {awesome: "&\"\\<>"}, '&\"\\<>', - "expressions with 3 handlebars aren't escaped"); - - shouldCompileTo("{{&awesome}}", {awesome: "&\"\\<>"}, '&\"\\<>', - "expressions with {{& handlebars aren't escaped"); - - shouldCompileTo("{{awesome}}", {awesome: "&\"'`\\<>"}, '&"'`\\<>', - "by default expressions should be escaped"); - - shouldCompileTo("{{awesome}}", {awesome: "Escaped, looks like: <b>"}, 'Escaped, <b> looks like: &lt;b&gt;', - "escaping should properly handle amperstands"); -}); - -test("functions returning safestrings shouldn't be escaped", function() { - var hash = {awesome: function() { return new Handlebars.SafeString("&\"\\<>"); }}; - shouldCompileTo("{{awesome}}", hash, '&\"\\<>', - "functions returning safestrings aren't escaped"); -}); - -test("functions", function() { - shouldCompileTo("{{awesome}}", {awesome: function() { return "Awesome"; }}, "Awesome", - "functions are called and render their output"); - shouldCompileTo("{{awesome}}", {awesome: function() { return this.more; }, more: "More awesome"}, "More awesome", - "functions are bound to the context"); -}); - -test("functions with context argument", function() { - shouldCompileTo("{{awesome frank}}", - {awesome: function(context) { return context; }, - frank: "Frank"}, - "Frank", "functions are called with context arguments"); -}); - - -test("paths with hyphens", function() { - shouldCompileTo("{{foo-bar}}", {"foo-bar": "baz"}, "baz", "Paths can contain hyphens (-)"); - shouldCompileTo("{{foo.foo-bar}}", {foo: {"foo-bar": "baz"}}, "baz", "Paths can contain hyphens (-)"); - shouldCompileTo("{{foo/foo-bar}}", {foo: {"foo-bar": "baz"}}, "baz", "Paths can contain hyphens (-)"); -}); - -test("nested paths", function() { - shouldCompileTo("Goodbye {{alan/expression}} world!", {alan: {expression: "beautiful"}}, - "Goodbye beautiful world!", "Nested paths access nested objects"); -}); - -test("nested paths with empty string value", function() { - shouldCompileTo("Goodbye {{alan/expression}} world!", {alan: {expression: ""}}, - "Goodbye world!", "Nested paths access nested objects with empty string"); -}); - -test("literal paths", function() { - shouldCompileTo("Goodbye {{[@alan]/expression}} world!", {"@alan": {expression: "beautiful"}}, - "Goodbye beautiful world!", "Literal paths can be used"); - shouldCompileTo("Goodbye {{[foo bar]/expression}} world!", {"foo bar": {expression: "beautiful"}}, - "Goodbye beautiful world!", "Literal paths can be used"); -}); - -test('literal references', function() { - shouldCompileTo("Goodbye {{[foo bar]}} world!", {"foo bar": "beautiful"}, - "Goodbye beautiful world!", "Literal paths can be used"); -}); - -test("that current context path ({{.}}) doesn't hit helpers", function() { - shouldCompileTo("test: {{.}}", [null, {helper: "awesome"}], "test: "); -}); - -test("complex but empty paths", function() { - shouldCompileTo("{{person/name}}", {person: {name: null}}, ""); - shouldCompileTo("{{person/name}}", {person: {}}, ""); -}); - -test("this keyword in paths", function() { - var string = "{{#goodbyes}}{{this}}{{/goodbyes}}"; - var hash = {goodbyes: ["goodbye", "Goodbye", "GOODBYE"]}; - shouldCompileTo(string, hash, "goodbyeGoodbyeGOODBYE", - "This keyword in paths evaluates to current context"); - - string = "{{#hellos}}{{this/text}}{{/hellos}}"; - hash = {hellos: [{text: "hello"}, {text: "Hello"}, {text: "HELLO"}]}; - shouldCompileTo(string, hash, "helloHelloHELLO", "This keyword evaluates in more complex paths"); -}); - -test("this keyword nested inside path", function() { - var string = "{{#hellos}}{{text/this/foo}}{{/hellos}}"; - shouldThrow(function() { - CompilerContext.compile(string); - }, Error, "Should throw exception"); -}); - -test("this keyword in helpers", function() { - var helpers = {foo: function(value) { - return 'bar ' + value; - }}; - var string = "{{#goodbyes}}{{foo this}}{{/goodbyes}}"; - var hash = {goodbyes: ["goodbye", "Goodbye", "GOODBYE"]}; - shouldCompileTo(string, [hash, helpers], "bar goodbyebar Goodbyebar GOODBYE", - "This keyword in paths evaluates to current context"); - - string = "{{#hellos}}{{foo this/text}}{{/hellos}}"; - hash = {hellos: [{text: "hello"}, {text: "Hello"}, {text: "HELLO"}]}; - shouldCompileTo(string, [hash, helpers], "bar hellobar Hellobar HELLO", "This keyword evaluates in more complex paths"); -}); - -test("this keyword nested inside helpers param", function() { - var string = "{{#hellos}}{{foo text/this/foo}}{{/hellos}}"; - shouldThrow(function() { - CompilerContext.compile(string); - }, Error, "Should throw exception"); -}); - -suite("inverted sections"); - -test("inverted sections with unset value", function() { - var string = "{{#goodbyes}}{{this}}{{/goodbyes}}{{^goodbyes}}Right On!{{/goodbyes}}"; - var hash = {}; - shouldCompileTo(string, hash, "Right On!", "Inverted section rendered when value isn't set."); -}); - -test("inverted section with false value", function() { - var string = "{{#goodbyes}}{{this}}{{/goodbyes}}{{^goodbyes}}Right On!{{/goodbyes}}"; - var hash = {goodbyes: false}; - shouldCompileTo(string, hash, "Right On!", "Inverted section rendered when value is false."); -}); - -test("inverted section with empty set", function() { - var string = "{{#goodbyes}}{{this}}{{/goodbyes}}{{^goodbyes}}Right On!{{/goodbyes}}"; - var hash = {goodbyes: []}; - shouldCompileTo(string, hash, "Right On!", "Inverted section rendered when value is empty set."); -}); - -suite("blocks"); - -test("array", function() { - var string = "{{#goodbyes}}{{text}}! {{/goodbyes}}cruel {{world}}!"; - var hash = {goodbyes: [{text: "goodbye"}, {text: "Goodbye"}, {text: "GOODBYE"}], world: "world"}; - shouldCompileTo(string, hash, "goodbye! Goodbye! GOODBYE! cruel world!", - "Arrays iterate over the contents when not empty"); - - shouldCompileTo(string, {goodbyes: [], world: "world"}, "cruel world!", - "Arrays ignore the contents when empty"); - -}); - -test("array with @index", function() { - var string = "{{#goodbyes}}{{@index}}. {{text}}! {{/goodbyes}}cruel {{world}}!"; - var hash = {goodbyes: [{text: "goodbye"}, {text: "Goodbye"}, {text: "GOODBYE"}], world: "world"}; - - var template = CompilerContext.compile(string); - var result = template(hash); - - equal(result, "0. goodbye! 1. Goodbye! 2. GOODBYE! cruel world!", "The @index variable is used"); -}); - -test("empty block", function() { - var string = "{{#goodbyes}}{{/goodbyes}}cruel {{world}}!"; - var hash = {goodbyes: [{text: "goodbye"}, {text: "Goodbye"}, {text: "GOODBYE"}], world: "world"}; - shouldCompileTo(string, hash, "cruel world!", - "Arrays iterate over the contents when not empty"); - - shouldCompileTo(string, {goodbyes: [], world: "world"}, "cruel world!", - "Arrays ignore the contents when empty"); -}); - -test("nested iteration", function() { - -}); - -test("block with complex lookup", function() { - var string = "{{#goodbyes}}{{text}} cruel {{../name}}! {{/goodbyes}}"; - var hash = {name: "Alan", goodbyes: [{text: "goodbye"}, {text: "Goodbye"}, {text: "GOODBYE"}]}; - - shouldCompileTo(string, hash, "goodbye cruel Alan! Goodbye cruel Alan! GOODBYE cruel Alan! ", - "Templates can access variables in contexts up the stack with relative path syntax"); -}); - -test("block with complex lookup using nested context", function() { - var string = "{{#goodbyes}}{{text}} cruel {{foo/../name}}! {{/goodbyes}}"; - - shouldThrow(function() { - CompilerContext.compile(string); - }, Error, "Should throw exception"); -}); - -test("helper with complex lookup$", function() { - var string = "{{#goodbyes}}{{{link ../prefix}}}{{/goodbyes}}"; - var hash = {prefix: "/root", goodbyes: [{text: "Goodbye", url: "goodbye"}]}; - var helpers = {link: function(prefix) { - return "" + this.text + ""; - }}; - shouldCompileTo(string, [hash, helpers], "Goodbye"); -}); - -test("helper block with complex lookup expression", function() { - var string = "{{#goodbyes}}{{../name}}{{/goodbyes}}"; - var hash = {name: "Alan"}; - var helpers = {goodbyes: function(options) { - var out = ""; - var byes = ["Goodbye", "goodbye", "GOODBYE"]; - for (var i = 0,j = byes.length; i < j; i++) { - out += byes[i] + " " + options.fn(this) + "! "; - } - return out; - }}; - shouldCompileTo(string, [hash, helpers], "Goodbye Alan! goodbye Alan! GOODBYE Alan! "); -}); - -test("helper with complex lookup and nested template", function() { - var string = "{{#goodbyes}}{{#link ../prefix}}{{text}}{{/link}}{{/goodbyes}}"; - var hash = {prefix: '/root', goodbyes: [{text: "Goodbye", url: "goodbye"}]}; - var helpers = {link: function (prefix, options) { - return "" + options.fn(this) + ""; - }}; - shouldCompileToWithPartials(string, [hash, helpers], false, "Goodbye"); -}); - -test("helper with complex lookup and nested template in VM+Compiler", function() { - var string = "{{#goodbyes}}{{#link ../prefix}}{{text}}{{/link}}{{/goodbyes}}"; - var hash = {prefix: '/root', goodbyes: [{text: "Goodbye", url: "goodbye"}]}; - var helpers = {link: function (prefix, options) { - return "" + options.fn(this) + ""; - }}; - shouldCompileToWithPartials(string, [hash, helpers], true, "Goodbye"); -}); - -test("block with deep nested complex lookup", function() { - var string = "{{#outer}}Goodbye {{#inner}}cruel {{../../omg}}{{/inner}}{{/outer}}"; - var hash = {omg: "OMG!", outer: [{ inner: [{ text: "goodbye" }] }] }; - - shouldCompileTo(string, hash, "Goodbye cruel OMG!"); -}); - -test("block helper", function() { - var string = "{{#goodbyes}}{{text}}! {{/goodbyes}}cruel {{world}}!"; - var template = CompilerContext.compile(string); - - var result = template({world: "world"}, { helpers: {goodbyes: function(options) { return options.fn({text: "GOODBYE"}); }}}); - equal(result, "GOODBYE! cruel world!", "Block helper executed"); -}); - -test("block helper staying in the same context", function() { - var string = "{{#form}}

{{name}}

{{/form}}"; - var template = CompilerContext.compile(string); - - var result = template({name: "Yehuda"}, {helpers: {form: function(options) { return "
" + options.fn(this) + "
"; } }}); - equal(result, "

Yehuda

", "Block helper executed with current context"); -}); - -test("block helper should have context in this", function() { - var source = "
    {{#people}}
  • {{#link}}{{name}}{{/link}}
  • {{/people}}
"; - var link = function(options) { - return '' + options.fn(this) + ''; - }; - var data = { "people": [ - { "name": "Alan", "id": 1 }, - { "name": "Yehuda", "id": 2 } - ]}; - - shouldCompileTo(source, [data, {link: link}], ""); -}); - -test("block helper for undefined value", function() { - shouldCompileTo("{{#empty}}shouldn't render{{/empty}}", {}, ""); -}); - -test("block helper passing a new context", function() { - var string = "{{#form yehuda}}

{{name}}

{{/form}}"; - var template = CompilerContext.compile(string); - - var result = template({yehuda: {name: "Yehuda"}}, { helpers: {form: function(context, options) { return "
" + options.fn(context) + "
"; }}}); - equal(result, "

Yehuda

", "Context variable resolved"); -}); - -test("block helper passing a complex path context", function() { - var string = "{{#form yehuda/cat}}

{{name}}

{{/form}}"; - var template = CompilerContext.compile(string); - - var result = template({yehuda: {name: "Yehuda", cat: {name: "Harold"}}}, { helpers: {form: function(context, options) { return "
" + options.fn(context) + "
"; }}}); - equal(result, "

Harold

", "Complex path variable resolved"); -}); - -test("nested block helpers", function() { - var string = "{{#form yehuda}}

{{name}}

{{#link}}Hello{{/link}}{{/form}}"; - var template = CompilerContext.compile(string); - - var result = template({ - yehuda: {name: "Yehuda" } - }, { - helpers: { - link: function(options) { return "" + options.fn(this) + ""; }, - form: function(context, options) { return "
" + options.fn(context) + "
"; } - } - }); - equal(result, "

Yehuda

Hello
", "Both blocks executed"); -}); - -test("block inverted sections", function() { - shouldCompileTo("{{#people}}{{name}}{{^}}{{none}}{{/people}}", {none: "No people"}, - "No people"); -}); - -test("block inverted sections with empty arrays", function() { - shouldCompileTo("{{#people}}{{name}}{{^}}{{none}}{{/people}}", {none: "No people", people: []}, - "No people"); -}); - -test("block helper inverted sections", function() { - var string = "{{#list people}}{{name}}{{^}}Nobody's here{{/list}}"; - var list = function(context, options) { - if (context.length > 0) { - var out = "
    "; - for(var i = 0,j=context.length; i < j; i++) { - out += "
  • "; - out += options.fn(context[i]); - out += "
  • "; - } - out += "
"; - return out; - } else { - return "

" + options.inverse(this) + "

"; - } - }; - - var hash = {people: [{name: "Alan"}, {name: "Yehuda"}]}; - var empty = {people: []}; - var rootMessage = { - people: [], - message: "Nobody's here" - }; - - var messageString = "{{#list people}}Hello{{^}}{{message}}{{/list}}"; - - // the meaning here may be kind of hard to catch, but list.not is always called, - // so we should see the output of both - shouldCompileTo(string, [hash, { list: list }], "
  • Alan
  • Yehuda
", "an inverse wrapper is passed in as a new context"); - shouldCompileTo(string, [empty, { list: list }], "

Nobody's here

", "an inverse wrapper can be optionally called"); - shouldCompileTo(messageString, [rootMessage, { list: list }], "

Nobody's here

", "the context of an inverse is the parent of the block"); -}); - -suite("helpers hash"); - -test("providing a helpers hash", function() { - shouldCompileTo("Goodbye {{cruel}} {{world}}!", [{cruel: "cruel"}, {world: function() { return "world"; }}], "Goodbye cruel world!", - "helpers hash is available"); - - shouldCompileTo("Goodbye {{#iter}}{{cruel}} {{world}}{{/iter}}!", [{iter: [{cruel: "cruel"}]}, {world: function() { return "world"; }}], - "Goodbye cruel world!", "helpers hash is available inside other blocks"); -}); - -test("in cases of conflict, helpers win", function() { - shouldCompileTo("{{{lookup}}}", [{lookup: 'Explicit'}, {lookup: function() { return 'helpers'; }}], "helpers", - "helpers hash has precedence escaped expansion"); - shouldCompileTo("{{lookup}}", [{lookup: 'Explicit'}, {lookup: function() { return 'helpers'; }}], "helpers", - "helpers hash has precedence simple expansion"); -}); - -test("the helpers hash is available is nested contexts", function() { - shouldCompileTo("{{#outer}}{{#inner}}{{helper}}{{/inner}}{{/outer}}", - [{'outer': {'inner': {'unused':[]}}}, {'helper': function() { return 'helper'; }}], "helper", - "helpers hash is available in nested contexts."); -}); - -test("the helper hash should augment the global hash", function() { - Handlebars.registerHelper('test_helper', function() { return 'found it!'; }); - - shouldCompileTo( - "{{test_helper}} {{#if cruel}}Goodbye {{cruel}} {{world}}!{{/if}}", [ - {cruel: "cruel"}, - {world: function() { return "world!"; }} - ], - "found it! Goodbye cruel world!!"); -}); - -test("Multiple global helper registration", function() { - var helpers = Handlebars.helpers; - try { - Handlebars.helpers = {}; - Handlebars.registerHelper({ - 'if': helpers['if'], - world: function() { return "world!"; }, - test_helper: function() { return 'found it!'; } - }); - - shouldCompileTo( - "{{test_helper}} {{#if cruel}}Goodbye {{cruel}} {{world}}!{{/if}}", - [{cruel: "cruel"}], - "found it! Goodbye cruel world!!"); - } finally { - if (helpers) { - Handlebars.helpers = helpers; - } - } -}); - -suite("partials"); - -test("basic partials", function() { - var string = "Dudes: {{#dudes}}{{> dude}}{{/dudes}}"; - var partial = "{{name}} ({{url}}) "; - var hash = {dudes: [{name: "Yehuda", url: "http://yehuda"}, {name: "Alan", url: "http://alan"}]}; - shouldCompileToWithPartials(string, [hash, {}, {dude: partial}], true, "Dudes: Yehuda (http://yehuda) Alan (http://alan) ", - "Basic partials output based on current context."); -}); - -test("partials with context", function() { - var string = "Dudes: {{>dude dudes}}"; - var partial = "{{#this}}{{name}} ({{url}}) {{/this}}"; - var hash = {dudes: [{name: "Yehuda", url: "http://yehuda"}, {name: "Alan", url: "http://alan"}]}; - shouldCompileToWithPartials(string, [hash, {}, {dude: partial}], true, "Dudes: Yehuda (http://yehuda) Alan (http://alan) ", - "Partials can be passed a context"); -}); - -test("partial in a partial", function() { - var string = "Dudes: {{#dudes}}{{>dude}}{{/dudes}}"; - var dude = "{{name}} {{> url}} "; - var url = "{{url}}"; - var hash = {dudes: [{name: "Yehuda", url: "http://yehuda"}, {name: "Alan", url: "http://alan"}]}; - shouldCompileToWithPartials(string, [hash, {}, {dude: dude, url: url}], true, "Dudes: Yehuda http://yehuda Alan http://alan ", "Partials are rendered inside of other partials"); -}); - -test("rendering undefined partial throws an exception", function() { - shouldThrow(function() { - var template = CompilerContext.compile("{{> whatever}}"); - template(); - }, [Handlebars.Exception, 'The partial whatever could not be found'], "Should throw exception"); -}); - -test("rendering template partial in vm mode throws an exception", function() { - shouldThrow(function() { - var template = CompilerContext.compile("{{> whatever}}"); - template(); - }, [Handlebars.Exception, 'The partial whatever could not be found'], "Should throw exception"); -}); - -test("rendering function partial in vm mode", function() { - var string = "Dudes: {{#dudes}}{{> dude}}{{/dudes}}"; - var partial = function(context) { - return context.name + ' (' + context.url + ') '; - }; - var hash = {dudes: [{name: "Yehuda", url: "http://yehuda"}, {name: "Alan", url: "http://alan"}]}; - shouldCompileTo(string, [hash, {}, {dude: partial}], "Dudes: Yehuda (http://yehuda) Alan (http://alan) ", - "Function partials output based in VM."); -}); - -test("GH-14: a partial preceding a selector", function() { - var string = "Dudes: {{>dude}} {{another_dude}}"; - var dude = "{{name}}"; - var hash = {name:"Jeepers", another_dude:"Creepers"}; - shouldCompileToWithPartials(string, [hash, {}, {dude:dude}], true, "Dudes: Jeepers Creepers", "Regular selectors can follow a partial"); -}); - -test("Partials with slash paths", function() { - var string = "Dudes: {{> shared/dude}}"; - var dude = "{{name}}"; - var hash = {name:"Jeepers", another_dude:"Creepers"}; - shouldCompileToWithPartials(string, [hash, {}, {'shared/dude':dude}], true, "Dudes: Jeepers", "Partials can use literal paths"); -}); - -test("Partials with slash and point paths", function() { - var string = "Dudes: {{> shared/dude.thing}}"; - var dude = "{{name}}"; - var hash = {name:"Jeepers", another_dude:"Creepers"}; - shouldCompileToWithPartials(string, [hash, {}, {'shared/dude.thing':dude}], true, "Dudes: Jeepers", "Partials can use literal with points in paths"); -}); - -test("Global Partials", function() { - Handlebars.registerPartial('global_test', '{{another_dude}}'); - - var string = "Dudes: {{> shared/dude}} {{> global_test}}"; - var dude = "{{name}}"; - var hash = {name:"Jeepers", another_dude:"Creepers"}; - shouldCompileToWithPartials(string, [hash, {}, {'shared/dude':dude}], true, "Dudes: Jeepers Creepers", "Partials can use globals or passed"); -}); - -test("Multiple partial registration", function() { - Handlebars.registerPartial({ - 'shared/dude': '{{name}}', - global_test: '{{another_dude}}' - }); - - var string = "Dudes: {{> shared/dude}} {{> global_test}}"; - var hash = {name:"Jeepers", another_dude:"Creepers"}; - shouldCompileToWithPartials(string, [hash], true, "Dudes: Jeepers Creepers", "Partials can use globals or passed"); -}); - -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"); -}); - -test("Partials with complex path", function() { - var string = "Dudes: {{> 404/asdf?.bar}}"; - var dude = "{{name}}"; - var hash = {name:"Jeepers", another_dude:"Creepers"}; - shouldCompileToWithPartials(string, [hash, {}, {'404/asdf?.bar':dude}], true, "Dudes: Jeepers", "Partials can use literal paths"); -}); - -test("Partials with escaped", function() { - var string = "Dudes: {{> [+404/asdf?.bar]}}"; - var dude = "{{name}}"; - var hash = {name:"Jeepers", another_dude:"Creepers"}; - shouldCompileToWithPartials(string, [hash, {}, {'+404/asdf?.bar':dude}], true, "Dudes: Jeepers", "Partials can use literal paths"); -}); - -test("Partials with string", function() { - var string = "Dudes: {{> \"+404/asdf?.bar\"}}"; - var dude = "{{name}}"; - var hash = {name:"Jeepers", another_dude:"Creepers"}; - shouldCompileToWithPartials(string, [hash, {}, {'+404/asdf?.bar':dude}], true, "Dudes: Jeepers", "Partials can use literal paths"); -}); - -suite("String literal parameters"); - -test("simple literals work", function() { - var string = 'Message: {{hello "world" 12 true false}}'; - var hash = {}; - var helpers = {hello: function(param, times, bool1, bool2) { - if(typeof times !== 'number') { times = "NaN"; } - if(typeof bool1 !== 'boolean') { bool1 = "NaB"; } - if(typeof bool2 !== 'boolean') { bool2 = "NaB"; } - return "Hello " + param + " " + times + " times: " + bool1 + " " + bool2; - }}; - shouldCompileTo(string, [hash, helpers], "Message: Hello world 12 times: true false", "template with a simple String literal"); -}); -test("negative number literals work", function() { - var string = 'Message: {{hello -12}}'; - var hash = {}; - var helpers = {hello: function(times) { - if(typeof times !== 'number') { times = "NaN"; } - return "Hello " + times + " times"; - }}; - shouldCompileTo(string, [hash, helpers], "Message: Hello -12 times", "template with a negative integer literal"); -}); - - -test("using a quote in the middle of a parameter raises an error", function() { - shouldThrow(function() { - var string = 'Message: {{hello wo"rld"}}'; - CompilerContext.compile(string); - }, Error, "should throw exception"); -}); - -test("escaping a String is possible", function(){ - var string = 'Message: {{{hello "\\"world\\""}}}'; - var hash = {}; - var helpers = {hello: function(param) { return "Hello " + param; }}; - shouldCompileTo(string, [hash, helpers], "Message: Hello \"world\"", "template with an escaped String literal"); -}); - -test("it works with ' marks", function() { - var string = 'Message: {{{hello "Alan\'s world"}}}'; - var hash = {}; - var helpers = {hello: function(param) { return "Hello " + param; }}; - shouldCompileTo(string, [hash, helpers], "Message: Hello Alan's world", "template with a ' mark"); -}); - -suite("multiple parameters"); - -test("simple multi-params work", function() { - var string = 'Message: {{goodbye cruel world}}'; - var hash = {cruel: "cruel", world: "world"}; - var helpers = {goodbye: function(cruel, world) { return "Goodbye " + cruel + " " + world; }}; - shouldCompileTo(string, [hash, helpers], "Message: Goodbye cruel world", "regular helpers with multiple params"); -}); - -test("block multi-params work", function() { - var string = 'Message: {{#goodbye cruel world}}{{greeting}} {{adj}} {{noun}}{{/goodbye}}'; - var hash = {cruel: "cruel", world: "world"}; - var helpers = {goodbye: function(cruel, world, options) { - return options.fn({greeting: "Goodbye", adj: cruel, noun: world}); - }}; - shouldCompileTo(string, [hash, helpers], "Message: Goodbye cruel world", "block helpers with multiple params"); -}); - -suite("safestring"); - -test("constructing a safestring from a string and checking its type", function() { - var safe = new Handlebars.SafeString("testing 1, 2, 3"); - ok(safe instanceof Handlebars.SafeString, "SafeString is an instance of Handlebars.SafeString"); - equal(safe, "testing 1, 2, 3", "SafeString is equivalent to its underlying string"); -}); - -test("it should not escape SafeString properties", function() { - var name = new Handlebars.SafeString("Sean O'Malley"); - - shouldCompileTo('{{name}}', [{ name: name }], "Sean O'Malley"); -}); - -suite("helperMissing"); - -test("if a context is not found, helperMissing is used", function() { - shouldThrow(function() { - var template = CompilerContext.compile("{{hello}} {{link_to world}}"); - template({}); - }, [Error, "Missing helper: 'link_to'"], "Should throw exception"); -}); - -test("if a context is not found, custom helperMissing is used", function() { - var string = "{{hello}} {{link_to world}}"; - var context = { hello: "Hello", world: "world" }; - - var helpers = { - helperMissing: function(helper, context) { - if(helper === "link_to") { - return new Handlebars.SafeString("" + context + ""); - } - } - }; - - shouldCompileTo(string, [context, helpers], "Hello world"); -}); - -suite("knownHelpers"); - -test("Known helper should render helper", function() { - var template = CompilerContext.compile("{{hello}}", {knownHelpers: {"hello" : true}}); - - var result = template({}, {helpers: {hello: function() { return "foo"; }}}); - equal(result, "foo", "'foo' should === '" + result); -}); - -test("Unknown helper in knownHelpers only mode should be passed as undefined", function() { - var template = CompilerContext.compile("{{typeof hello}}", {knownHelpers: {'typeof': true}, knownHelpersOnly: true}); - - var result = template({}, {helpers: {'typeof': function(arg) { return typeof arg; }, hello: function() { return "foo"; }}}); - equal(result, "undefined", "'undefined' should === '" + result); -}); -test("Builtin helpers available in knownHelpers only mode", function() { - var template = CompilerContext.compile("{{#unless foo}}bar{{/unless}}", {knownHelpersOnly: true}); - - var result = template({}); - equal(result, "bar", "'bar' should === '" + result); -}); -test("Field lookup works in knownHelpers only mode", function() { - var template = CompilerContext.compile("{{foo}}", {knownHelpersOnly: true}); - - var result = template({foo: 'bar'}); - equal(result, "bar", "'bar' should === '" + result); -}); -test("Conditional blocks work in knownHelpers only mode", function() { - var template = CompilerContext.compile("{{#foo}}bar{{/foo}}", {knownHelpersOnly: true}); - - var result = template({foo: 'baz'}); - equal(result, "bar", "'bar' should === '" + result); -}); -test("Invert blocks work in knownHelpers only mode", function() { - var template = CompilerContext.compile("{{^foo}}bar{{/foo}}", {knownHelpersOnly: true}); - - var result = template({foo: false}); - equal(result, "bar", "'bar' should === '" + result); -}); -test("Functions are bound to the context in knownHelpers only mode", function() { - var template = CompilerContext.compile("{{foo}}", {knownHelpersOnly: true}); - var result = template({foo: function() { return this.bar; }, bar: 'bar'}); - equal(result, "bar", "'bar' should === '" + result); -}); -test("Unknown helper call in knownHelpers only mode should throw", function() { - shouldThrow(function() { - CompilerContext.compile("{{typeof hello}}", {knownHelpersOnly: true}); - }, Error, 'specified knownHelpersOnly'); -}); - -suite("blockHelperMissing"); - -test("lambdas are resolved by blockHelperMissing, not handlebars proper", function() { - var string = "{{#truthy}}yep{{/truthy}}"; - var data = { truthy: function() { return true; } }; - shouldCompileTo(string, data, "yep"); -}); -test("lambdas resolved by blockHelperMissing are bound to the context", function() { - var string = "{{#truthy}}yep{{/truthy}}"; - var boundData = { truthy: function() { return this.truthiness(); }, truthiness: function() { return false; } }; - shouldCompileTo(string, boundData, ""); -}); - -var teardown; -suite("built-in helpers", { - setup: function(){ teardown = null; }, - teardown: function(){ if (teardown) { teardown(); } } -}); - -test("with", function() { - var string = "{{#with person}}{{first}} {{last}}{{/with}}"; - shouldCompileTo(string, {person: {first: "Alan", last: "Johnson"}}, "Alan Johnson"); -}); -test("with with function argument", function() { - var string = "{{#with person}}{{first}} {{last}}{{/with}}"; - shouldCompileTo(string, {person: function() { return {first: "Alan", last: "Johnson"};}}, "Alan Johnson"); -}); - -test("if", function() { - var string = "{{#if goodbye}}GOODBYE {{/if}}cruel {{world}}!"; - shouldCompileTo(string, {goodbye: true, world: "world"}, "GOODBYE cruel world!", - "if with boolean argument shows the contents when true"); - shouldCompileTo(string, {goodbye: "dummy", world: "world"}, "GOODBYE cruel world!", - "if with string argument shows the contents"); - shouldCompileTo(string, {goodbye: false, world: "world"}, "cruel world!", - "if with boolean argument does not show the contents when false"); - shouldCompileTo(string, {world: "world"}, "cruel world!", - "if with undefined does not show the contents"); - shouldCompileTo(string, {goodbye: ['foo'], world: "world"}, "GOODBYE cruel world!", - "if with non-empty array shows the contents"); - shouldCompileTo(string, {goodbye: [], world: "world"}, "cruel world!", - "if with empty array does not show the contents"); -}); - -test("if with function argument", function() { - var string = "{{#if goodbye}}GOODBYE {{/if}}cruel {{world}}!"; - shouldCompileTo(string, {goodbye: function() {return true;}, world: "world"}, "GOODBYE cruel world!", - "if with function shows the contents when function returns true"); - shouldCompileTo(string, {goodbye: function() {return this.world;}, world: "world"}, "GOODBYE cruel world!", - "if with function shows the contents when function returns string"); - shouldCompileTo(string, {goodbye: function() {return false;}, world: "world"}, "cruel world!", - "if with function does not show the contents when returns false"); - shouldCompileTo(string, {goodbye: function() {return this.foo;}, world: "world"}, "cruel world!", - "if with function does not show the contents when returns undefined"); -}); - -test("each", function() { - var string = "{{#each goodbyes}}{{text}}! {{/each}}cruel {{world}}!"; - var hash = {goodbyes: [{text: "goodbye"}, {text: "Goodbye"}, {text: "GOODBYE"}], world: "world"}; - shouldCompileTo(string, hash, "goodbye! Goodbye! GOODBYE! cruel world!", - "each with array argument iterates over the contents when not empty"); - shouldCompileTo(string, {goodbyes: [], world: "world"}, "cruel world!", - "each with array argument ignores the contents when empty"); -}); - -test("each with an object and @key", function() { - var string = "{{#each goodbyes}}{{@key}}. {{text}}! {{/each}}cruel {{world}}!"; - var hash = {goodbyes: {"#1": {text: "goodbye"}, 2: {text: "GOODBYE"}}, world: "world"}; - - // Object property iteration order is undefined according to ECMA spec, - // so we need to check both possible orders - // @see http://stackoverflow.com/questions/280713/elements-order-in-a-for-in-loop - var actual = compileWithPartials(string, hash); - var expected1 = "<b>#1</b>. goodbye! 2. GOODBYE! cruel world!"; - var expected2 = "2. GOODBYE! <b>#1</b>. goodbye! cruel world!"; - - ok(actual === expected1 || actual === expected2, "each with object argument iterates over the contents when not empty"); - shouldCompileTo(string, {goodbyes: [], world: "world"}, "cruel world!", - "each with object argument ignores the contents when empty"); -}); - -test("each with @index", function() { - var string = "{{#each goodbyes}}{{@index}}. {{text}}! {{/each}}cruel {{world}}!"; - var hash = {goodbyes: [{text: "goodbye"}, {text: "Goodbye"}, {text: "GOODBYE"}], world: "world"}; - - var template = CompilerContext.compile(string); - var result = template(hash); - - equal(result, "0. goodbye! 1. Goodbye! 2. GOODBYE! cruel world!", "The @index variable is used"); -}); - -test("each with function argument", function() { - var string = "{{#each goodbyes}}{{text}}! {{/each}}cruel {{world}}!"; - var hash = {goodbyes: function () { return [{text: "goodbye"}, {text: "Goodbye"}, {text: "GOODBYE"}];}, world: "world"}; - shouldCompileTo(string, hash, "goodbye! Goodbye! GOODBYE! cruel world!", - "each with array function argument iterates over the contents when not empty"); - shouldCompileTo(string, {goodbyes: [], world: "world"}, "cruel world!", - "each with array function argument ignores the contents when empty"); -}); - -test("data passed to helpers", function() { - var string = "{{#each letters}}{{this}}{{detectDataInsideEach}}{{/each}}"; - var hash = {letters: ['a', 'b', 'c']}; - - var template = CompilerContext.compile(string); - var result = template(hash, { - data: { - exclaim: '!' - } - }); - equal(result, 'a!b!c!', 'should output data'); -}); - -Handlebars.registerHelper('detectDataInsideEach', function(options) { - return options.data && options.data.exclaim; -}); - -test("log", function() { - var string = "{{log blah}}"; - var hash = { blah: "whee" }; - - var levelArg, logArg; - var originalLog = Handlebars.log; - Handlebars.log = function(level, arg){ levelArg = level, logArg = arg; }; - teardown = function(){ Handlebars.log = originalLog; }; - - shouldCompileTo(string, hash, "", "log should not display"); - equals(1, levelArg, "should call log with 1"); - equals("whee", logArg, "should call log with 'whee'"); -}); - -test("overriding property lookup", function() { - -}); - - -test("passing in data to a compiled function that expects data - works with helpers", function() { - var template = CompilerContext.compile("{{hello}}", {data: true}); - - var helpers = { - hello: function(options) { - return options.data.adjective + " " + this.noun; - } - }; - - var result = template({noun: "cat"}, {helpers: helpers, data: {adjective: "happy"}}); - equals("happy cat", result, "Data output by helper"); -}); - -test("data can be looked up via @foo", function() { - var template = CompilerContext.compile("{{@hello}}"); - var result = template({}, { data: { hello: "hello" } }); - equals("hello", result, "@foo retrieves template data"); -}); - -var objectCreate = Handlebars.createFrame; - -test("deep @foo triggers automatic top-level data", function() { - var template = CompilerContext.compile('{{#let world="world"}}{{#if foo}}{{#if foo}}Hello {{@world}}{{/if}}{{/if}}{{/let}}'); - - var helpers = objectCreate(Handlebars.helpers); - - helpers.let = function(options) { - var frame = Handlebars.createFrame(options.data); - - for (var prop in options.hash) { - frame[prop] = options.hash[prop]; - } - return options.fn(this, { data: frame }); - }; - - var result = template({ foo: true }, { helpers: helpers }); - equals("Hello world", result, "Automatic data was triggered"); -}); - -test("parameter data can be looked up via @foo", function() { - var template = CompilerContext.compile("{{hello @world}}"); - var helpers = { - hello: function(noun) { - return "Hello " + noun; - } - }; - - var result = template({}, { helpers: helpers, data: { world: "world" } }); - equals("Hello world", result, "@foo as a parameter retrieves template data"); -}); - -test("hash values can be looked up via @foo", function() { - var template = CompilerContext.compile("{{hello noun=@world}}"); - var helpers = { - hello: function(options) { - return "Hello " + options.hash.noun; - } - }; - - var result = template({}, { helpers: helpers, data: { world: "world" } }); - equals("Hello world", result, "@foo as a parameter retrieves template data"); -}); - -test("nested parameter data can be looked up via @foo.bar", function() { - var template = CompilerContext.compile("{{hello @world.bar}}"); - var helpers = { - hello: function(noun) { - return "Hello " + noun; - } - }; - - var result = template({}, { helpers: helpers, data: { world: {bar: "world" } } }); - equals("Hello world", result, "@foo as a parameter retrieves template data"); -}); - -test("nested parameter data does not fail with @world.bar", function() { - var template = CompilerContext.compile("{{hello @world.bar}}"); - var helpers = { - hello: function(noun) { - return "Hello " + noun; - } - }; - - var result = template({}, { helpers: helpers, data: { foo: {bar: "world" } } }); - equals("Hello undefined", result, "@foo as a parameter retrieves template data"); -}); - -test("parameter data throws when using this scope references", function() { - var string = "{{#goodbyes}}{{text}} cruel {{@./name}}! {{/goodbyes}}"; - - shouldThrow(function() { - CompilerContext.compile(string); - }, Error, "Should throw exception"); -}); - -test("parameter data throws when using parent scope references", function() { - var string = "{{#goodbyes}}{{text}} cruel {{@../name}}! {{/goodbyes}}"; - - shouldThrow(function() { - CompilerContext.compile(string); - }, Error, "Should throw exception"); -}); - -test("parameter data throws when using complex scope references", function() { - var string = "{{#goodbyes}}{{text}} cruel {{@foo/../name}}! {{/goodbyes}}"; - - shouldThrow(function() { - CompilerContext.compile(string); - }, Error, "Should throw exception"); -}); - -test("data is inherited downstream", function() { - var template = CompilerContext.compile("{{#let foo=bar.baz}}{{@foo}}{{/let}}", { data: true }); - var helpers = { - let: function(options) { - for (var prop in options.hash) { - options.data[prop] = options.hash[prop]; - } - return options.fn(this); - } - }; - - var result = template({ bar: { baz: "hello world" } }, { helpers: helpers, data: {} }); - equals("hello world", result, "data variables are inherited downstream"); -}); - -test("passing in data to a compiled function that expects data - works with helpers in partials", function() { - var template = CompilerContext.compile("{{>my_partial}}", {data: true}); - - var partials = { - my_partial: CompilerContext.compile("{{hello}}", {data: true}) - }; - - var helpers = { - hello: function(options) { - return options.data.adjective + " " + this.noun; - } - }; - - var result = template({noun: "cat"}, {helpers: helpers, partials: partials, data: {adjective: "happy"}}); - equals("happy cat", result, "Data output by helper inside partial"); -}); - -test("passing in data to a compiled function that expects data - works with helpers and parameters", function() { - var template = CompilerContext.compile("{{hello world}}", {data: true}); - - var helpers = { - hello: function(noun, options) { - return options.data.adjective + " " + noun + (this.exclaim ? "!" : ""); - } - }; - - var result = template({exclaim: true, world: "world"}, {helpers: helpers, data: {adjective: "happy"}}); - equals("happy world!", result, "Data output by helper"); -}); - -test("passing in data to a compiled function that expects data - works with block helpers", function() { - var template = CompilerContext.compile("{{#hello}}{{world}}{{/hello}}", {data: true}); - - var helpers = { - hello: function(options) { - return options.fn(this); - }, - world: function(options) { - return options.data.adjective + " world" + (this.exclaim ? "!" : ""); - } - }; - - var result = template({exclaim: true}, {helpers: helpers, data: {adjective: "happy"}}); - equals("happy world!", result, "Data output by helper"); -}); - -test("passing in data to a compiled function that expects data - works with block helpers that use ..", function() { - var template = CompilerContext.compile("{{#hello}}{{world ../zomg}}{{/hello}}", {data: true}); - - var helpers = { - hello: function(options) { - return options.fn({exclaim: "?"}); - }, - world: function(thing, options) { - return options.data.adjective + " " + thing + (this.exclaim || ""); - } - }; - - var result = template({exclaim: true, zomg: "world"}, {helpers: helpers, data: {adjective: "happy"}}); - equals("happy world?", result, "Data output by helper"); -}); - -test("passing in data to a compiled function that expects data - data is passed to with block helpers where children use ..", function() { - var template = CompilerContext.compile("{{#hello}}{{world ../zomg}}{{/hello}}", {data: true}); - - var helpers = { - hello: function(options) { - return options.data.accessData + " " + options.fn({exclaim: "?"}); - }, - world: function(thing, options) { - return options.data.adjective + " " + thing + (this.exclaim || ""); - } - }; - - var result = template({exclaim: true, zomg: "world"}, {helpers: helpers, data: {adjective: "happy", accessData: "#win"}}); - equals("#win happy world?", result, "Data output by helper"); -}); - -test("you can override inherited data when invoking a helper", function() { - var template = CompilerContext.compile("{{#hello}}{{world zomg}}{{/hello}}", {data: true}); - - var helpers = { - hello: function(options) { - return options.fn({exclaim: "?", zomg: "world"}, { data: {adjective: "sad"} }); - }, - world: function(thing, options) { - return options.data.adjective + " " + thing + (this.exclaim || ""); - } - }; - - var result = template({exclaim: true, zomg: "planet"}, {helpers: helpers, data: {adjective: "happy"}}); - equals("sad world?", result, "Overriden data output by helper"); -}); - - -test("you can override inherited data when invoking a helper with depth", function() { - var template = CompilerContext.compile("{{#hello}}{{world ../zomg}}{{/hello}}", {data: true}); - - var helpers = { - hello: function(options) { - return options.fn({exclaim: "?"}, { data: {adjective: "sad"} }); - }, - world: function(thing, options) { - return options.data.adjective + " " + thing + (this.exclaim || ""); - } - }; - - var result = template({exclaim: true, zomg: "world"}, {helpers: helpers, data: {adjective: "happy"}}); - equals("sad world?", result, "Overriden data output by helper"); -}); - -test("helpers take precedence over same-named context properties", function() { - var template = CompilerContext.compile("{{goodbye}} {{cruel world}}"); - - var helpers = { - goodbye: function() { - return this.goodbye.toUpperCase(); - }, - - cruel: function(world) { - return "cruel " + world.toUpperCase(); - } - }; - - var context = { - goodbye: "goodbye", - world: "world" - }; - - var result = template(context, {helpers: helpers}); - equals(result, "GOODBYE cruel WORLD", "Helper executed"); -}); - -test("helpers take precedence over same-named context properties$", function() { - var template = CompilerContext.compile("{{#goodbye}} {{cruel world}}{{/goodbye}}"); - - var helpers = { - goodbye: function(options) { - return this.goodbye.toUpperCase() + options.fn(this); - }, - - cruel: function(world) { - return "cruel " + world.toUpperCase(); - } - }; - - var context = { - goodbye: "goodbye", - world: "world" - }; - - var result = template(context, {helpers: helpers}); - equals(result, "GOODBYE cruel WORLD", "Helper executed"); -}); - -test("Scoped names take precedence over helpers", function() { - var template = CompilerContext.compile("{{this.goodbye}} {{cruel world}} {{cruel this.goodbye}}"); - - var helpers = { - goodbye: function() { - return this.goodbye.toUpperCase(); - }, - - cruel: function(world) { - return "cruel " + world.toUpperCase(); - }, - }; - - var context = { - goodbye: "goodbye", - world: "world" - }; - - var result = template(context, {helpers: helpers}); - equals(result, "goodbye cruel WORLD cruel GOODBYE", "Helper not executed"); -}); - -test("Scoped names take precedence over block helpers", function() { - var template = CompilerContext.compile("{{#goodbye}} {{cruel world}}{{/goodbye}} {{this.goodbye}}"); - - var helpers = { - goodbye: function(options) { - return this.goodbye.toUpperCase() + options.fn(this); - }, - - cruel: function(world) { - return "cruel " + world.toUpperCase(); - }, - }; - - var context = { - goodbye: "goodbye", - world: "world" - }; - - var result = template(context, {helpers: helpers}); - equals(result, "GOODBYE cruel WORLD goodbye", "Helper executed"); -}); - -test("helpers can take an optional hash", function() { - var template = CompilerContext.compile('{{goodbye cruel="CRUEL" world="WORLD" times=12}}'); - - var helpers = { - goodbye: function(options) { - return "GOODBYE " + options.hash.cruel + " " + options.hash.world + " " + options.hash.times + " TIMES"; - } - }; - - var context = {}; - - var result = template(context, {helpers: helpers}); - equals(result, "GOODBYE CRUEL WORLD 12 TIMES", "Helper output hash"); -}); - -test("helpers can take an optional hash with booleans", function() { - var helpers = { - goodbye: function(options) { - if (options.hash.print === true) { - return "GOODBYE " + options.hash.cruel + " " + options.hash.world; - } else if (options.hash.print === false) { - return "NOT PRINTING"; - } else { - return "THIS SHOULD NOT HAPPEN"; - } - } - }; - - var context = {}; - - var template = CompilerContext.compile('{{goodbye cruel="CRUEL" world="WORLD" print=true}}'); - var result = template(context, {helpers: helpers}); - equals(result, "GOODBYE CRUEL WORLD", "Helper output hash"); - - template = CompilerContext.compile('{{goodbye cruel="CRUEL" world="WORLD" print=false}}'); - result = template(context, {helpers: helpers}); - equals(result, "NOT PRINTING", "Boolean helper parameter honored"); -}); - -test("block helpers can take an optional hash", function() { - var template = CompilerContext.compile('{{#goodbye cruel="CRUEL" times=12}}world{{/goodbye}}'); - - var helpers = { - goodbye: function(options) { - return "GOODBYE " + options.hash.cruel + " " + options.fn(this) + " " + options.hash.times + " TIMES"; - } - }; - - var result = template({}, {helpers: helpers}); - equals(result, "GOODBYE CRUEL world 12 TIMES", "Hash parameters output"); -}); - -test("block helpers can take an optional hash with single quoted stings", function() { - var template = CompilerContext.compile("{{#goodbye cruel='CRUEL' times=12}}world{{/goodbye}}"); - - var helpers = { - goodbye: function(options) { - return "GOODBYE " + options.hash.cruel + " " + options.fn(this) + " " + options.hash.times + " TIMES"; - } - }; - - var result = template({}, {helpers: helpers}); - equals(result, "GOODBYE CRUEL world 12 TIMES", "Hash parameters output"); -}); - -test("block helpers can take an optional hash with booleans", function() { - var helpers = { - goodbye: function(options) { - if (options.hash.print === true) { - return "GOODBYE " + options.hash.cruel + " " + options.fn(this); - } else if (options.hash.print === false) { - return "NOT PRINTING"; - } else { - return "THIS SHOULD NOT HAPPEN"; - } - } - }; - - var template = CompilerContext.compile('{{#goodbye cruel="CRUEL" print=true}}world{{/goodbye}}'); - var result = template({}, {helpers: helpers}); - equals(result, "GOODBYE CRUEL world", "Boolean hash parameter honored"); - - template = CompilerContext.compile('{{#goodbye cruel="CRUEL" print=false}}world{{/goodbye}}'); - result = template({}, {helpers: helpers}); - equals(result, "NOT PRINTING", "Boolean hash parameter honored"); -}); - - -test("arguments to helpers can be retrieved from options hash in string form", function() { - var template = CompilerContext.compile('{{wycats is.a slave.driver}}', {stringParams: true}); - - var helpers = { - wycats: function(passiveVoice, noun) { - return "HELP ME MY BOSS " + passiveVoice + ' ' + noun; - } - }; - - var result = template({}, {helpers: helpers}); - - equals(result, "HELP ME MY BOSS is.a slave.driver", "String parameters output"); -}); - -test("when using block form, arguments to helpers can be retrieved from options hash in string form", function() { - var template = CompilerContext.compile('{{#wycats is.a slave.driver}}help :({{/wycats}}', {stringParams: true}); - - var helpers = { - wycats: function(passiveVoice, noun, options) { - return "HELP ME MY BOSS " + passiveVoice + ' ' + - noun + ': ' + options.fn(this); - } - }; - - var result = template({}, {helpers: helpers}); - - equals(result, "HELP ME MY BOSS is.a slave.driver: help :(", "String parameters output"); -}); - -test("when inside a block in String mode, .. passes the appropriate context in the options hash", function() { - var template = CompilerContext.compile('{{#with dale}}{{tomdale ../need dad.joke}}{{/with}}', {stringParams: true}); - - var helpers = { - tomdale: function(desire, noun, options) { - return "STOP ME FROM READING HACKER NEWS I " + - options.contexts[0][desire] + " " + noun; - }, - - "with": function(context, options) { - return options.fn(options.contexts[0][context]); - } - }; - - var result = template({ - dale: {}, - - need: 'need-a' - }, {helpers: helpers}); - - equals(result, "STOP ME FROM READING HACKER NEWS I need-a dad.joke", "Proper context variable output"); -}); - -test("in string mode, information about the types is passed along", function() { - var template = CompilerContext.compile('{{tomdale "need" dad.joke true false}}', { stringParams: true }); - - var helpers = { - tomdale: function(desire, noun, trueBool, falseBool, options) { - equal(options.types[0], 'STRING', "the string type is passed"); - equal(options.types[1], 'ID', "the expression type is passed"); - equal(options.types[2], 'BOOLEAN', "the expression type is passed"); - equal(desire, "need", "the string form is passed for strings"); - equal(noun, "dad.joke", "the string form is passed for expressions"); - equal(trueBool, true, "raw booleans are passed through"); - equal(falseBool, false, "raw booleans are passed through"); - return "Helper called"; - } - }; - - var result = template({}, { helpers: helpers }); - equal(result, "Helper called"); -}); - -test("in string mode, hash parameters get type information", function() { - var template = CompilerContext.compile('{{tomdale he.says desire="need" noun=dad.joke bool=true}}', { stringParams: true }); - - var helpers = { - tomdale: function(exclamation, options) { - equal(exclamation, "he.says"); - equal(options.types[0], "ID"); - - equal(options.hashTypes.desire, "STRING"); - equal(options.hashTypes.noun, "ID"); - equal(options.hashTypes.bool, "BOOLEAN"); - equal(options.hash.desire, "need"); - equal(options.hash.noun, "dad.joke"); - equal(options.hash.bool, true); - return "Helper called"; - } - }; - - var result = template({}, { helpers: helpers }); - equal(result, "Helper called"); -}); - -test("in string mode, hash parameters get context information", function() { - var template = CompilerContext.compile('{{#with dale}}{{tomdale he.says desire="need" noun=../dad/joke bool=true}}{{/with}}', { stringParams: true }); - - var context = {dale: {}}; - - var helpers = { - tomdale: function(exclamation, options) { - equal(exclamation, "he.says"); - equal(options.types[0], "ID"); - - equal(options.contexts.length, 1); - equal(options.hashContexts.noun, context); - equal(options.hash.desire, "need"); - equal(options.hash.noun, "dad.joke"); - equal(options.hash.bool, true); - return "Helper called"; - }, - "with": function(context, options) { - return options.fn(options.contexts[0][context]); - } - }; - - var result = template(context, { helpers: helpers }); - equal(result, "Helper called"); -}); - -test("when inside a block in String mode, .. passes the appropriate context in the options hash to a block helper", function() { - var template = CompilerContext.compile('{{#with dale}}{{#tomdale ../need dad.joke}}wot{{/tomdale}}{{/with}}', {stringParams: true}); - - var helpers = { - tomdale: function(desire, noun, options) { - return "STOP ME FROM READING HACKER NEWS I " + - options.contexts[0][desire] + " " + noun + " " + - options.fn(this); - }, - - "with": function(context, options) { - return options.fn(options.contexts[0][context]); - } - }; - - var result = template({ - dale: {}, - - need: 'need-a' - }, {helpers: helpers}); - - equals(result, "STOP ME FROM READING HACKER NEWS I need-a dad.joke wot", "Proper context variable output"); -}); - -suite("Regressions"); - -test("GH-94: Cannot read property of undefined", function() { - var data = {"books":[{"title":"The origin of species","author":{"name":"Charles Darwin"}},{"title":"Lazarillo de Tormes"}]}; - var string = "{{#books}}{{title}}{{author.name}}{{/books}}"; - shouldCompileTo(string, data, "The origin of speciesCharles DarwinLazarillo de Tormes", - "Renders without an undefined property error"); -}); - -test("GH-150: Inverted sections print when they shouldn't", function() { - var string = "{{^set}}not set{{/set}} :: {{#set}}set{{/set}}"; - - shouldCompileTo(string, {}, "not set :: ", "inverted sections run when property isn't present in context"); - shouldCompileTo(string, {set: undefined}, "not set :: ", "inverted sections run when property is undefined"); - shouldCompileTo(string, {set: false}, "not set :: ", "inverted sections run when property is false"); - shouldCompileTo(string, {set: true}, " :: set", "inverted sections don't run when property is true"); -}); - -test("Mustache man page", function() { - var string = "Hello {{name}}. You have just won ${{value}}!{{#in_ca}} Well, ${{taxed_value}}, after taxes.{{/in_ca}}"; - var data = { - "name": "Chris", - "value": 10000, - "taxed_value": 10000 - (10000 * 0.4), - "in_ca": true - }; - - shouldCompileTo(string, data, "Hello Chris. You have just won $10000! Well, $6000, after taxes.", "the hello world mustache example works"); -}); - -test("GH-158: Using array index twice, breaks the template", function() { - var string = "{{arr.[0]}}, {{arr.[1]}}"; - var data = { "arr": [1,2] }; - - shouldCompileTo(string, data, "1, 2", "it works as expected"); -}); - -test("bug reported by @fat where lambdas weren't being properly resolved", function() { - var string = "This is a slightly more complicated {{thing}}..\n{{! Just ignore this business. }}\nCheck this out:\n{{#hasThings}}\n
    \n{{#things}}\n
  • {{word}}
  • \n{{/things}}
.\n{{/hasThings}}\n{{^hasThings}}\n\nNothing to check out...\n{{/hasThings}}"; - var data = { - thing: function() { - return "blah"; - }, - things: [ - {className: "one", word: "@fat"}, - {className: "two", word: "@dhg"}, - {className: "three", word:"@sayrer"} - ], - hasThings: function() { - return true; - } - }; - - var output = "This is a slightly more complicated blah..\n\nCheck this out:\n\n
    \n\n
  • @fat
  • \n\n
  • @dhg
  • \n\n
  • @sayrer
  • \n
.\n\n"; - shouldCompileTo(string, data, output); -}); - -test("Passing falsy values to Handlebars.compile throws an error", function() { - shouldThrow(function() { - CompilerContext.compile(null); - }, "You must pass a string or Handlebars AST to Handlebars.precompile. You passed null"); -}); - -test("can pass through an already-compiled AST via compile/precompile", function() { - equal(Handlebars.compile(new Handlebars.AST.ProgramNode([ new Handlebars.AST.ContentNode("Hello")]))(), 'Hello') -}); - -test('GH-408: Multiple loops fail', function() { - var context = [ - { name: "John Doe", location: { city: "Chicago" } }, - { name: "Jane Doe", location: { city: "New York"} } - ]; - - var template = CompilerContext.compile('{{#.}}{{name}}{{/.}}{{#.}}{{name}}{{/.}}{{#.}}{{name}}{{/.}}'); - - var result = template(context); - equals(result, "John DoeJane DoeJohn DoeJane DoeJohn DoeJane Doe", 'It should output multiple times'); -}); - -test('GS-428: Nested if else rendering', function() { - var succeedingTemplate = '{{#inverse}} {{#blk}} Unexpected {{/blk}} {{else}} {{#blk}} Expected {{/blk}} {{/inverse}}'; - var failingTemplate = '{{#inverse}} {{#blk}} Unexpected {{/blk}} {{else}} {{#blk}} Expected {{/blk}} {{/inverse}}'; - - var helpers = { - blk: function(block) { return block.fn(''); }, - inverse: function(block) { return block.inverse(''); } - }; - - shouldCompileTo(succeedingTemplate, [{}, helpers], ' Expected '); - shouldCompileTo(failingTemplate, [{}, helpers], ' Expected '); -}); - -test('GH-458: Scoped this identifier', function() { - shouldCompileTo('{{./foo}}', {foo: 'bar'}, 'bar'); -}); - -test('GH-375: Unicode line terminators', function() { - shouldCompileTo('\u2028', {}, '\u2028'); -}); - -test('GH-534: Object prototype aliases', function() { - Object.prototype[0xD834] = true; - - shouldCompileTo('{{foo}}', { foo: 'bar' }, 'bar'); - - delete Object.prototype[0xD834]; -}); - -test('GH-437: Matching escaping', function() { - shouldThrow(function() { - CompilerContext.compile('{{{a}}'); - }, Error); - shouldThrow(function() { - CompilerContext.compile('{{a}}}'); - }, Error); -}); - -suite('Utils'); - -test('escapeExpression', function() { - equal(Handlebars.Utils.escapeExpression('foo<&"\'>'), 'foo<&"'>'); - equal(Handlebars.Utils.escapeExpression(new Handlebars.SafeString('foo<&"\'>')), 'foo<&"\'>'); - equal(Handlebars.Utils.escapeExpression(''), ''); - equal(Handlebars.Utils.escapeExpression(undefined), ''); - equal(Handlebars.Utils.escapeExpression(null), ''); - equal(Handlebars.Utils.escapeExpression(false), ''); - - equal(Handlebars.Utils.escapeExpression(0), '0'); - equal(Handlebars.Utils.escapeExpression({}), {}.toString()); - equal(Handlebars.Utils.escapeExpression([]), [].toString()); -}); - -test('isEmpty', function() { - equal(Handlebars.Utils.isEmpty(undefined), true); - equal(Handlebars.Utils.isEmpty(null), true); - equal(Handlebars.Utils.isEmpty(false), true); - equal(Handlebars.Utils.isEmpty(''), true); - equal(Handlebars.Utils.isEmpty([]), true); - - equal(Handlebars.Utils.isEmpty(0), false); - equal(Handlebars.Utils.isEmpty([1]), false); - equal(Handlebars.Utils.isEmpty('foo'), false); - equal(Handlebars.Utils.isEmpty({bar: 1}), false); -}); - -if (typeof(require) !== 'undefined') { - suite('Require'); - - test('Load .handlebars files with require()', function() { - var template = require("./example_1"); - assert.deepEqual(template, require("./example_1.handlebars")); - - var expected = 'foo\n'; - var result = template({foo: "foo"}); - - equal(result, expected); - }); - - test('Load .hbs files with require()', function() { - var template = require("./example_2"); - assert.deepEqual(template, require("./example_2.hbs")); - - var expected = 'Hello, World!\n'; - var result = template({name: "World"}); - - equal(result, expected); - }); -} diff --git a/spec/regressions.js b/spec/regressions.js new file mode 100644 index 000000000..44bde81b6 --- /dev/null +++ b/spec/regressions.js @@ -0,0 +1,119 @@ +/*global CompilerContext, shouldCompileTo */ +describe('Regressions', function() { + it("GH-94: Cannot read property of undefined", function() { + var data = {"books":[{"title":"The origin of species","author":{"name":"Charles Darwin"}},{"title":"Lazarillo de Tormes"}]}; + var string = "{{#books}}{{title}}{{author.name}}{{/books}}"; + shouldCompileTo(string, data, "The origin of speciesCharles DarwinLazarillo de Tormes", + "Renders without an undefined property error"); + }); + + it("GH-150: Inverted sections print when they shouldn't", function() { + var string = "{{^set}}not set{{/set}} :: {{#set}}set{{/set}}"; + + shouldCompileTo(string, {}, "not set :: ", "inverted sections run when property isn't present in context"); + shouldCompileTo(string, {set: undefined}, "not set :: ", "inverted sections run when property is undefined"); + shouldCompileTo(string, {set: false}, "not set :: ", "inverted sections run when property is false"); + shouldCompileTo(string, {set: true}, " :: set", "inverted sections don't run when property is true"); + }); + + it("GH-158: Using array index twice, breaks the template", function() { + var string = "{{arr.[0]}}, {{arr.[1]}}"; + var data = { "arr": [1,2] }; + + shouldCompileTo(string, data, "1, 2", "it works as expected"); + }); + + it("bug reported by @fat where lambdas weren't being properly resolved", function() { + var string = "This is a slightly more complicated {{thing}}..\n{{! Just ignore this business. }}\nCheck this out:\n{{#hasThings}}\n
    \n{{#things}}\n
  • {{word}}
  • \n{{/things}}
.\n{{/hasThings}}\n{{^hasThings}}\n\nNothing to check out...\n{{/hasThings}}"; + var data = { + thing: function() { + return "blah"; + }, + things: [ + {className: "one", word: "@fat"}, + {className: "two", word: "@dhg"}, + {className: "three", word:"@sayrer"} + ], + hasThings: function() { + return true; + } + }; + + var output = "This is a slightly more complicated blah..\n\nCheck this out:\n\n
    \n\n
  • @fat
  • \n\n
  • @dhg
  • \n\n
  • @sayrer
  • \n
.\n\n"; + shouldCompileTo(string, data, output); + }); + + it('GH-408: Multiple loops fail', function() { + var context = [ + { name: "John Doe", location: { city: "Chicago" } }, + { name: "Jane Doe", location: { city: "New York"} } + ]; + + var template = CompilerContext.compile('{{#.}}{{name}}{{/.}}{{#.}}{{name}}{{/.}}{{#.}}{{name}}{{/.}}'); + + var result = template(context); + equals(result, "John DoeJane DoeJohn DoeJane DoeJohn DoeJane Doe", 'It should output multiple times'); + }); + + it('GS-428: Nested if else rendering', function() { + var succeedingTemplate = '{{#inverse}} {{#blk}} Unexpected {{/blk}} {{else}} {{#blk}} Expected {{/blk}} {{/inverse}}'; + var failingTemplate = '{{#inverse}} {{#blk}} Unexpected {{/blk}} {{else}} {{#blk}} Expected {{/blk}} {{/inverse}}'; + + var helpers = { + blk: function(block) { return block.fn(''); }, + inverse: function(block) { return block.inverse(''); } + }; + + shouldCompileTo(succeedingTemplate, [{}, helpers], ' Expected '); + shouldCompileTo(failingTemplate, [{}, helpers], ' Expected '); + }); + + it('GH-458: Scoped this identifier', function() { + shouldCompileTo('{{./foo}}', {foo: 'bar'}, 'bar'); + }); + + it('GH-375: Unicode line terminators', function() { + shouldCompileTo('\u2028', {}, '\u2028'); + }); + + it('GH-534: Object prototype aliases', function() { + Object.prototype[0xD834] = true; + + shouldCompileTo('{{foo}}', { foo: 'bar' }, 'bar'); + + delete Object.prototype[0xD834]; + }); + + it('GH-437: Matching escaping', function() { + (function() { + CompilerContext.compile('{{{a}}'); + }).should.throw(Error); + (function() { + CompilerContext.compile('{{a}}}'); + }).should.throw(Error); + }); + + it("Mustache man page", function() { + var string = "Hello {{name}}. You have just won ${{value}}!{{#in_ca}} Well, ${{taxed_value}}, after taxes.{{/in_ca}}"; + var data = { + "name": "Chris", + "value": 10000, + "taxed_value": 10000 - (10000 * 0.4), + "in_ca": true + }; + + shouldCompileTo(string, data, "Hello Chris. You have just won $10000! Well, $6000, after taxes.", "the hello world mustache example works"); + }); + + it("Passing falsy values to Handlebars.compile throws an error", function() { + (function() { + CompilerContext.compile(null); + }).should.throw("You must pass a string or Handlebars AST to Handlebars.precompile. You passed null"); + }); + + if (Handlebars.AST) { + it("can pass through an already-compiled AST via compile/precompile", function() { + equal(Handlebars.compile(new Handlebars.AST.ProgramNode([ new Handlebars.AST.ContentNode("Hello")]))(), 'Hello'); + }); + } +}); diff --git a/spec/require.js b/spec/require.js new file mode 100644 index 000000000..d750d7d3d --- /dev/null +++ b/spec/require.js @@ -0,0 +1,23 @@ +if (typeof(require) !== 'undefined' && require.extensions[".handlebars"]) { + describe('Require', function() { + it('Load .handlebars files with require()', function() { + var template = require("./artifacts/example_1"); + template.should.eql(require("./artifacts/example_1.handlebars")); + + var expected = 'foo\n'; + var result = template({foo: "foo"}); + + result.should.equal(expected); + }); + + it('Load .hbs files with require()', function() { + var template = require("./artifacts/example_2"); + template.should.eql(require("./artifacts/example_2.hbs")); + + var expected = 'Hello, World!\n'; + var result = template({name: "World"}); + + result.should.equal(expected); + }); + }); +} diff --git a/spec/source-map.js b/spec/source-map.js new file mode 100644 index 000000000..9b9fa516f --- /dev/null +++ b/spec/source-map.js @@ -0,0 +1,18 @@ +var sourceMap = require('source-map'), + SourceMapConsumer = sourceMap.SourceMapConsumer; + +describe('source-map', function() { + if (!Handlebars.Parser) { + return; + } + + it('returns source map', function() { + var template = Handlebars.precompile('{{foo}}', {filename: 'foo.handlebars', sourcemap: true}); + (typeof template.template).should.equal('string'); + (typeof template.sourcemap).should.equal('string'); + return; + var consumer = new SourceMapConsumer(template.sourcemap); + consumer.eachMapping(function(mapping) { + }, this, SourceMapConsumer.ORIGINAL_ORDER); + }); +}); diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb deleted file mode 100644 index eb2f26afd..000000000 --- a/spec/spec_helper.rb +++ /dev/null @@ -1,132 +0,0 @@ -require "v8" - -# Monkey patches due to bugs in RubyRacer -class V8::JSError - def initialize(try, to) - @to = to - begin - super(initialize_unsafe(try)) - rescue Exception => e - # Original code does not make an Array here - @boundaries = [Boundary.new(:rbframes => e.backtrace)] - @value = e - super("BUG! please report. JSError#initialize failed!: #{e.message}") - end - end - - def parse_js_frames(try) - raw = @to.rb(try.StackTrace()) - if raw && !raw.empty? - raw.split("\n")[1..-1].tap do |frames| - # Original code uses strip!, and the frames are not guaranteed to be strippable - frames.each {|frame| frame.strip.chomp!(",")} - end - else - [] - end - end -end - -module Handlebars - module Spec - def self.js_backtrace(context) - begin - context.eval("throw") - rescue V8::JSError => e - return e.backtrace(:javascript) - end - end - - def self.remove_exports(string) - match = string.match(%r{\A(.*?)^// BEGIN\(BROWSER\)\n(.*)\n^// END\(BROWSER\)(.*?)\Z}m) - prelines = match ? match[1].count("\n") + 1 : 0 - ret = match ? match[2] : string - ("\n" * prelines) + ret - end - - def self.load_helpers(context) - context["exports"] = nil - - context["p"] = proc do |this, val| - p val if ENV["DEBUG_JS"] - end - - context["puts"] = proc do |this, val| - puts val if ENV["DEBUG_JS"] - end - - context["puts_node"] = proc do |this, val| - puts context["Handlebars"]["PrintVisitor"].new.accept(val) - puts - end - - context["puts_caller"] = proc do - puts "BACKTRACE:" - puts Handlebars::Spec.js_backtrace(context) - puts - end - end - - def self.js_load(context, file) - str = File.read(file) - context.eval(remove_exports(str), file) - end - - CONTEXT = V8::Context.new - CONTEXT.instance_eval do |context| - Handlebars::Spec.load_helpers(context); - - Handlebars::Spec.js_load(context, 'dist/handlebars.js'); - - context["CompilerContext"] = {} - CompilerContext = context["CompilerContext"] - CompilerContext["compile"] = proc do |this, *args| - template, options = args[0], args[1] || nil - templateSpec = COMPILE_CONTEXT["Handlebars"]["precompile"].call(template, options); - context["Handlebars"]["template"].call(context.eval("(#{templateSpec})")); - end - CompilerContext["compileWithPartial"] = proc do |this, *args| - template, options = args[0], args[1] || nil - context["Handlebars"]["compile"].call(template, options); - end - end - - PARSER_CONTEXT = V8::Context.new - PARSER_CONTEXT.instance_eval do |context| - Handlebars::Spec.load_helpers(context); - - Handlebars::Spec.js_load(context, 'lib/handlebars/compiler/parser.js'); - end - - COMPILE_CONTEXT = V8::Context.new - COMPILE_CONTEXT.instance_eval do |context| - Handlebars::Spec.load_helpers(context); - - Handlebars::Spec.js_load(context, 'dist/handlebars.js'); - Handlebars::Spec.js_load(context, 'lib/handlebars/compiler/visitor.js'); - Handlebars::Spec.js_load(context, 'lib/handlebars/compiler/printer.js'); - - context["Handlebars"]["logger"]["level"] = ENV["DEBUG_JS"] ? context["Handlebars"]["logger"][ENV["DEBUG_JS"]] : 4 - - context["Handlebars"]["logger"]["log"] = proc do |this, level, str| - logger_level = context["Handlebars"]["logger"]["level"].to_i - - if logger_level <= level - puts str - end - end - end - end -end - - -require "test/unit/assertions" - -RSpec.configure do |config| - config.include Test::Unit::Assertions - - # Each is required to allow classes to mark themselves as compiler tests - config.before(:each) do - @context = @compiles ? Handlebars::Spec::COMPILE_CONTEXT : Handlebars::Spec::CONTEXT - end -end diff --git a/spec/string-params.js b/spec/string-params.js new file mode 100644 index 000000000..1ebb5838b --- /dev/null +++ b/spec/string-params.js @@ -0,0 +1,145 @@ +describe('string params mode', function() { + it("arguments to helpers can be retrieved from options hash in string form", function() { + var template = CompilerContext.compile('{{wycats is.a slave.driver}}', {stringParams: true}); + + var helpers = { + wycats: function(passiveVoice, noun) { + return "HELP ME MY BOSS " + passiveVoice + ' ' + noun; + } + }; + + var result = template({}, {helpers: helpers}); + + equals(result, "HELP ME MY BOSS is.a slave.driver", "String parameters output"); + }); + + it("when using block form, arguments to helpers can be retrieved from options hash in string form", function() { + var template = CompilerContext.compile('{{#wycats is.a slave.driver}}help :({{/wycats}}', {stringParams: true}); + + var helpers = { + wycats: function(passiveVoice, noun, options) { + return "HELP ME MY BOSS " + passiveVoice + ' ' + + noun + ': ' + options.fn(this); + } + }; + + var result = template({}, {helpers: helpers}); + + equals(result, "HELP ME MY BOSS is.a slave.driver: help :(", "String parameters output"); + }); + + it("when inside a block in String mode, .. passes the appropriate context in the options hash", function() { + var template = CompilerContext.compile('{{#with dale}}{{tomdale ../need dad.joke}}{{/with}}', {stringParams: true}); + + var helpers = { + tomdale: function(desire, noun, options) { + return "STOP ME FROM READING HACKER NEWS I " + + options.contexts[0][desire] + " " + noun; + }, + + "with": function(context, options) { + return options.fn(options.contexts[0][context]); + } + }; + + var result = template({ + dale: {}, + + need: 'need-a' + }, {helpers: helpers}); + + equals(result, "STOP ME FROM READING HACKER NEWS I need-a dad.joke", "Proper context variable output"); + }); + + it("information about the types is passed along", function() { + var template = CompilerContext.compile('{{tomdale "need" dad.joke true false}}', { stringParams: true }); + + var helpers = { + tomdale: function(desire, noun, trueBool, falseBool, options) { + equal(options.types[0], 'STRING', "the string type is passed"); + equal(options.types[1], 'ID', "the expression type is passed"); + equal(options.types[2], 'BOOLEAN', "the expression type is passed"); + equal(desire, "need", "the string form is passed for strings"); + equal(noun, "dad.joke", "the string form is passed for expressions"); + equal(trueBool, true, "raw booleans are passed through"); + equal(falseBool, false, "raw booleans are passed through"); + return "Helper called"; + } + }; + + var result = template({}, { helpers: helpers }); + equal(result, "Helper called"); + }); + + it("hash parameters get type information", function() { + var template = CompilerContext.compile('{{tomdale he.says desire="need" noun=dad.joke bool=true}}', { stringParams: true }); + + var helpers = { + tomdale: function(exclamation, options) { + equal(exclamation, "he.says"); + equal(options.types[0], "ID"); + + equal(options.hashTypes.desire, "STRING"); + equal(options.hashTypes.noun, "ID"); + equal(options.hashTypes.bool, "BOOLEAN"); + equal(options.hash.desire, "need"); + equal(options.hash.noun, "dad.joke"); + equal(options.hash.bool, true); + return "Helper called"; + } + }; + + var result = template({}, { helpers: helpers }); + equal(result, "Helper called"); + }); + + it("hash parameters get context information", function() { + var template = CompilerContext.compile('{{#with dale}}{{tomdale he.says desire="need" noun=../dad/joke bool=true}}{{/with}}', { stringParams: true }); + + var context = {dale: {}}; + + var helpers = { + tomdale: function(exclamation, options) { + equal(exclamation, "he.says"); + equal(options.types[0], "ID"); + + equal(options.contexts.length, 1); + equal(options.hashContexts.noun, context); + equal(options.hash.desire, "need"); + equal(options.hash.noun, "dad.joke"); + equal(options.hash.bool, true); + return "Helper called"; + }, + "with": function(context, options) { + return options.fn(options.contexts[0][context]); + } + }; + + var result = template(context, { helpers: helpers }); + equal(result, "Helper called"); + }); + + it("when inside a block in String mode, .. passes the appropriate context in the options hash to a block helper", function() { + var template = CompilerContext.compile('{{#with dale}}{{#tomdale ../need dad.joke}}wot{{/tomdale}}{{/with}}', {stringParams: true}); + + var helpers = { + tomdale: function(desire, noun, options) { + return "STOP ME FROM READING HACKER NEWS I " + + options.contexts[0][desire] + " " + noun + " " + + options.fn(this); + }, + + "with": function(context, options) { + return options.fn(options.contexts[0][context]); + } + }; + + var result = template({ + dale: {}, + + need: 'need-a' + }, {helpers: helpers}); + + equals(result, "STOP ME FROM READING HACKER NEWS I need-a dad.joke wot", "Proper context variable output"); + }); +}); diff --git a/spec/tokenizer.js b/spec/tokenizer.js index c69f406b5..f86774a69 100644 --- a/spec/tokenizer.js +++ b/spec/tokenizer.js @@ -1,5 +1,4 @@ -var Handlebars = require('../lib/handlebars'), - should = require('should'); +var should = require('should'); should.Assertion.prototype.match_tokens = function(tokens) { this.obj.forEach(function(value, index) { @@ -11,6 +10,10 @@ should.Assertion.prototype.be_token = function(name, text) { }; describe('Tokenizer', function() { + if (!Handlebars.Parser) { + return; + } + function tokenize(template) { var parser = Handlebars.Parser, lexer = parser.lexer; diff --git a/spec/utils.js b/spec/utils.js new file mode 100644 index 000000000..135beb430 --- /dev/null +++ b/spec/utils.js @@ -0,0 +1,56 @@ +/*global shouldCompileTo */ +describe('utils', function() { + describe('#SafeString', function() { + it("constructing a safestring from a string and checking its type", function() { + var safe = new Handlebars.SafeString("testing 1, 2, 3"); + safe.should.be.instanceof(Handlebars.SafeString); + (safe == "testing 1, 2, 3").should.equal(true, "SafeString is equivalent to its underlying string"); + }); + + it("it should not escape SafeString properties", function() { + var name = new Handlebars.SafeString("Sean O'Malley"); + + shouldCompileTo('{{name}}', [{ name: name }], "Sean O'Malley"); + }); + }); + + describe('#escapeExpression', function() { + it('shouhld escape html', function() { + Handlebars.Utils.escapeExpression('foo<&"\'>').should.equal('foo<&"'>'); + }); + it('should not escape SafeString', function() { + var string = new Handlebars.SafeString('foo<&"\'>'); + Handlebars.Utils.escapeExpression(string).should.equal('foo<&"\'>'); + + }); + it('should handle falsy', function() { + Handlebars.Utils.escapeExpression('').should.equal(''); + Handlebars.Utils.escapeExpression(undefined).should.equal(''); + Handlebars.Utils.escapeExpression(null).should.equal(''); + Handlebars.Utils.escapeExpression(false).should.equal(''); + + Handlebars.Utils.escapeExpression(0).should.equal('0'); + }); + it('should handle empty objects', function() { + Handlebars.Utils.escapeExpression({}).should.equal({}.toString()); + Handlebars.Utils.escapeExpression([]).should.equal([].toString()); + }); + }); + + describe('#isEmpty', function() { + it('should not be empty', function() { + Handlebars.Utils.isEmpty(undefined).should.equal(true); + Handlebars.Utils.isEmpty(null).should.equal(true); + Handlebars.Utils.isEmpty(false).should.equal(true); + Handlebars.Utils.isEmpty('').should.equal(true); + Handlebars.Utils.isEmpty([]).should.equal(true); + }); + + it('should be empty', function() { + Handlebars.Utils.isEmpty(0).should.equal(false); + Handlebars.Utils.isEmpty([1]).should.equal(false); + Handlebars.Utils.isEmpty('foo').should.equal(false); + Handlebars.Utils.isEmpty({bar: 1}).should.equal(false); + }); + }); +}); From 7b1ad8aa38379e9de26b1799c54a082d6b8a0be1 Mon Sep 17 00:00:00 2001 From: kpdecker Date: Sat, 1 Jun 2013 23:46:34 -0500 Subject: [PATCH 008/200] Remove remaining respec references. Fixes #544 --- .rspec | 1 - Gemfile | 2 -- Gemfile.lock | 14 -------------- README.markdown | 17 +++++------------ 4 files changed, 5 insertions(+), 29 deletions(-) delete mode 100644 .rspec diff --git a/.rspec b/.rspec deleted file mode 100644 index 62c58f049..000000000 --- a/.rspec +++ /dev/null @@ -1 +0,0 @@ --cfs diff --git a/Gemfile b/Gemfile index cf092ce28..7f062abad 100644 --- a/Gemfile +++ b/Gemfile @@ -1,5 +1,3 @@ source "http://rubygems.org" gem "rake" -gem "therubyracer", ">= 0.9.8", "< 0.11" -gem "rspec" diff --git a/Gemfile.lock b/Gemfile.lock index ce8338f4e..82ac7048b 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,24 +1,10 @@ GEM remote: http://rubygems.org/ specs: - diff-lcs (1.1.3) - libv8 (3.3.10.4) rake (10.0.3) - rspec (2.12.0) - rspec-core (~> 2.12.0) - rspec-expectations (~> 2.12.0) - rspec-mocks (~> 2.12.0) - rspec-core (2.12.2) - rspec-expectations (2.12.1) - diff-lcs (~> 1.1.3) - rspec-mocks (2.12.1) - therubyracer (0.10.2) - libv8 (~> 3.3.10) PLATFORMS ruby DEPENDENCIES rake - rspec - therubyracer (>= 0.9.8, < 0.11) diff --git a/README.markdown b/README.markdown index f775815e2..54557245f 100644 --- a/README.markdown +++ b/README.markdown @@ -362,22 +362,15 @@ To build Handlebars.js you'll need a few things installed. * Node.js * Ruby -* therubyracer, for running tests - `gem install therubyracer` -* rspec, for running tests - `gem install rspec` +* Rake -There's a Gemfile in the repo, so you can run `bundle` to install rspec -and therubyracer if you've got bundler installed. +There's a Gemfile in the repo, so you can run `bundle` to install rake +if you've got bundler installed. To build Handlebars.js from scratch, you'll want to run `rake compile` in the root of the project. That will build Handlebars and output the -results to the dist/ folder. To run tests, run `rake test`. You can also -run our set of benchmarks with `rake bench`. Node tests can be run with -`npm test` or `rake npm_test`. The default rake target will compile and -run both test suites. - -Some environments, notably Windows, have issues running therubyracer. Under these -envrionments the `rake compile` and `npm test` should be sufficient to test -most handlebars functionality. +results to the dist/ folder. To run tests, run `rake test` or `npm test. +You can also run our set of benchmarks with `rake bench`. If you notice any problems, please report them to the GitHub issue tracker at [http://github.com/wycats/handlebars.js/issues](http://github.com/wycats/handlebars.js/issues). From 9d4a94616a6e6cc6dd6503496a3f9716826e1bcf Mon Sep 17 00:00:00 2001 From: kpdecker Date: Sat, 1 Jun 2013 23:55:20 -0500 Subject: [PATCH 009/200] Add should npm declaration --- package.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/package.json b/package.json index 3f517c1b1..b6cc234f0 100644 --- a/package.json +++ b/package.json @@ -22,7 +22,8 @@ "dust": "~0.3", "jison": "~0.3", "mocha": "*", - "mustache": "~0.7.2" + "mustache": "~0.7.2", + "should": "~1.2.2" }, "main": "lib/handlebars.js", "bin": { From bcc8d00e892190b703aaf6dfffc5e00133c6370b Mon Sep 17 00:00:00 2001 From: kpdecker Date: Sat, 1 Jun 2013 23:57:21 -0500 Subject: [PATCH 010/200] Remove incorrectly commited test file --- spec/source-map.js | 18 ------------------ 1 file changed, 18 deletions(-) delete mode 100644 spec/source-map.js diff --git a/spec/source-map.js b/spec/source-map.js deleted file mode 100644 index 9b9fa516f..000000000 --- a/spec/source-map.js +++ /dev/null @@ -1,18 +0,0 @@ -var sourceMap = require('source-map'), - SourceMapConsumer = sourceMap.SourceMapConsumer; - -describe('source-map', function() { - if (!Handlebars.Parser) { - return; - } - - it('returns source map', function() { - var template = Handlebars.precompile('{{foo}}', {filename: 'foo.handlebars', sourcemap: true}); - (typeof template.template).should.equal('string'); - (typeof template.sourcemap).should.equal('string'); - return; - var consumer = new SourceMapConsumer(template.sourcemap); - consumer.eachMapping(function(mapping) { - }, this, SourceMapConsumer.ORIGINAL_ORDER); - }); -}); From 5ce7232dc34f338d252ef0795f95dbb13b6bb6cf Mon Sep 17 00:00:00 2001 From: kpdecker Date: Sun, 2 Jun 2013 00:37:18 -0500 Subject: [PATCH 011/200] Fix merge error --- Gemfile | 3 +++ Gemfile.lock | 6 ++++++ 2 files changed, 9 insertions(+) diff --git a/Gemfile b/Gemfile index 8ac3a37c1..96c9dd253 100644 --- a/Gemfile +++ b/Gemfile @@ -1,4 +1,7 @@ source "http://rubygems.org" gem "rake" +gem "json" +gem "nokogiri" gem "aws-sdk" +gem "uuidtools" diff --git a/Gemfile.lock b/Gemfile.lock index 31523df90..1a8fc1300 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -5,11 +5,17 @@ GEM json (~> 1.4) nokogiri (>= 1.4.4) uuidtools (~> 2.1) + json (1.8.0) + nokogiri (1.5.9) rake (10.0.3) + uuidtools (2.1.4) PLATFORMS ruby DEPENDENCIES aws-sdk + json + nokogiri rake + uuidtools From 26fcebe0c71ef33931ef591fd16e738a1eedc722 Mon Sep 17 00:00:00 2001 From: kpdecker Date: Sun, 2 Jun 2013 00:41:14 -0500 Subject: [PATCH 012/200] Use short SHA name --- Rakefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Rakefile b/Rakefile index 2527446c9..21d9ba9a4 100644 --- a/Rakefile +++ b/Rakefile @@ -133,7 +133,7 @@ task :publish do access_key_id = ENV['S3_ACCESS_KEY_ID'] secret_access_key = ENV['S3_SECRET_ACCESS_KEY'] bucket_name = ENV['S3_BUCKET_NAME'] - rev = `git rev-list HEAD -n 1`.to_s.strip + rev = `git rev-list --short HEAD -n 1`.to_s.strip master_rev = `git rev-list origin/master -n 1`.to_s.strip upload = true if rev == master_rev upload = upload && access_key_id && secret_access_key && bucket_name From 80078861f2abaa5ecfb5e702decc29b5f4f21f48 Mon Sep 17 00:00:00 2001 From: kpdecker Date: Sun, 2 Jun 2013 00:41:33 -0500 Subject: [PATCH 013/200] Include minimized files in build --- Rakefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Rakefile b/Rakefile index 21d9ba9a4..b1d3c5853 100644 --- a/Rakefile +++ b/Rakefile @@ -142,7 +142,7 @@ task :publish do root = File.expand_path(File.dirname(__FILE__)) + '/dist/' s3 = AWS::S3.new(access_key_id: access_key_id,secret_access_key: secret_access_key) bucket = s3.buckets[bucket_name] - files = ['handlebars.js', 'handlebars.runtime.js'].map { |file| root + file } + files = ['handlebars.js', 'handlebars.min.js', 'handlebars.runtime.js', 'handlebars.runtime.min.js'].map { |file| root + file } files.each do |file| basename = Pathname.new(file).basename.sub_ext('') s3_objs = ["#{basename}-latest.js", "#{basename}-#{rev}.js"].map do |file| From e624fb50b791f92736399e885f7c8d54d436201a Mon Sep 17 00:00:00 2001 From: kpdecker Date: Sun, 2 Jun 2013 00:52:35 -0500 Subject: [PATCH 014/200] Proper git commit command --- Rakefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Rakefile b/Rakefile index b1d3c5853..489bdcac7 100644 --- a/Rakefile +++ b/Rakefile @@ -133,7 +133,7 @@ task :publish do access_key_id = ENV['S3_ACCESS_KEY_ID'] secret_access_key = ENV['S3_SECRET_ACCESS_KEY'] bucket_name = ENV['S3_BUCKET_NAME'] - rev = `git rev-list --short HEAD -n 1`.to_s.strip + rev = `git rev-parse --short HEAD -n 1`.to_s.strip master_rev = `git rev-list origin/master -n 1`.to_s.strip upload = true if rev == master_rev upload = upload && access_key_id && secret_access_key && bucket_name From 8f24a2e384520082edc255ec8bda7448f3cf966a Mon Sep 17 00:00:00 2001 From: kpdecker Date: Sun, 2 Jun 2013 11:30:22 -0500 Subject: [PATCH 015/200] Pull dist file lookup logic out of publish method --- Rakefile | 38 +++++++++++++++++++++++++------------- 1 file changed, 25 insertions(+), 13 deletions(-) diff --git a/Rakefile b/Rakefile index 489bdcac7..1b4cb69a7 100644 --- a/Rakefile +++ b/Rakefile @@ -14,6 +14,22 @@ def compile_parser end end +def dist_files(&block) + rev = `git rev-parse --short HEAD`.to_s.strip + master_rev = `git rev-parse --short origin/master`.to_s.strip + + if rev == master_rev + root = File.expand_path(File.dirname(__FILE__)) + '/dist/' + + files = ['handlebars.js', 'handlebars.min.js', 'handlebars.runtime.js', 'handlebars.runtime.min.js'].map { |file| root + file } + files = files.map do |file| + basename = Pathname.new(file).basename.sub_ext('') + yield basename, rev + end + files = files.flatten + end +end + file "lib/handlebars/compiler/parser.js" => ["src/handlebars.yy","src/handlebars.l"] do if File.exists?('./node_modules/jison') compile_parser @@ -133,23 +149,19 @@ task :publish do access_key_id = ENV['S3_ACCESS_KEY_ID'] secret_access_key = ENV['S3_SECRET_ACCESS_KEY'] bucket_name = ENV['S3_BUCKET_NAME'] - rev = `git rev-parse --short HEAD -n 1`.to_s.strip - master_rev = `git rev-list origin/master -n 1`.to_s.strip - upload = true if rev == master_rev - upload = upload && access_key_id && secret_access_key && bucket_name - if upload + + files = dist_files do |basename, rev| + ["#{basename}-latest.js", "#{basename}-#{rev}.js"] + end + + if files && access_key_id && secret_access_key && bucket_name require 'aws-sdk' - root = File.expand_path(File.dirname(__FILE__)) + '/dist/' s3 = AWS::S3.new(access_key_id: access_key_id,secret_access_key: secret_access_key) bucket = s3.buckets[bucket_name] - files = ['handlebars.js', 'handlebars.min.js', 'handlebars.runtime.js', 'handlebars.runtime.min.js'].map { |file| root + file } - files.each do |file| - basename = Pathname.new(file).basename.sub_ext('') - s3_objs = ["#{basename}-latest.js", "#{basename}-#{rev}.js"].map do |file| - bucket.objects[file] - end - s3_objs.each { |obj| obj.write(Pathname.new(file)) } + s3_objs = files.map do |file| + bucket.objects[file] end + s3_objs.each { |obj| obj.write(Pathname.new(file)) } else puts "Not uploading any files to S3!" end From b3005f8f9ff03cff0e9ed920a8b28b444ef75ff1 Mon Sep 17 00:00:00 2001 From: kpdecker Date: Sun, 2 Jun 2013 12:01:17 -0500 Subject: [PATCH 016/200] Merge dist build targets --- Rakefile | 15 +++++---------- 1 file changed, 5 insertions(+), 10 deletions(-) diff --git a/Rakefile b/Rakefile index 1b4cb69a7..5a6473075 100644 --- a/Rakefile +++ b/Rakefile @@ -91,13 +91,12 @@ file "dist/handlebars.runtime.js" => runtime_deps do |task| build_for_task(task) end -task :build => [:compile] do |task| - # Since the tests are dependent on this always rebuild. +task :dist => [:compile] do |task| Rake::Task["dist/handlebars.js"].execute -end -task :runtime => [:compile] do |task| - # Since the tests are dependent on this always rebuild. Rake::Task["dist/handlebars.runtime.js"].execute + + system "./node_modules/.bin/uglifyjs --comments -o dist/handlebars.min.js dist/handlebars.js" + system "./node_modules/.bin/uglifyjs --comments -o dist/handlebars.runtime.min.js dist/handlebars.runtime.js" end # Updates the various version numbers. @@ -114,15 +113,11 @@ task :version => [] do |task| File.open("handlebars.js.nuspec", "w") do |file| file.puts content.gsub(/.*<\/version>/, "#{version}") end -end -task :minify => [] do |task| - system "./node_modules/.bin/uglifyjs --comments -o dist/handlebars.min.js dist/handlebars.js" - system "./node_modules/.bin/uglifyjs --comments -o dist/handlebars.runtime.min.js dist/handlebars.runtime.js" end desc "build the build and runtime version of handlebars" -task :release => [:version, :build, :runtime, :minify] +task :release => [:version, :dist] directory "vendor" From bb8bb048aa50820ec05a895716de0a087d59f045 Mon Sep 17 00:00:00 2001 From: kpdecker Date: Sun, 2 Jun 2013 12:20:01 -0500 Subject: [PATCH 017/200] Pass args to the rake version task --- Rakefile | 23 ++++++++++++++++++----- 1 file changed, 18 insertions(+), 5 deletions(-) diff --git a/Rakefile b/Rakefile index 5a6473075..da11b7079 100644 --- a/Rakefile +++ b/Rakefile @@ -100,9 +100,20 @@ task :dist => [:compile] do |task| end # Updates the various version numbers. -task :version => [] do |task| - # TODO : Pull from package.json when the version numbers are synced - version = File.read("lib/handlebars/base.js").match(/Handlebars.VERSION = "(.*)";/)[1] +desc "Updates the current release version" +task :version, [:version] => [] do |task, args| + version = args.version + fail "Must provide a version number" unless version + + changed = %x{git diff-index --name-only HEAD --} + fail "The repository must be clean" unless $?.success? && !changed + + puts "Updating to version #{version}" + + content = File.read("lib/handlebars/base.js") + File.open("lib/handlebars/base.js", "w") do | file| + file.puts content.gsub(/Handlebars.VERSION = "(.*)";/, "Handlebars.VERSION = \"#{version}\";") + end content = File.read("bower.json") File.open("bower.json", "w") do |file| @@ -114,10 +125,12 @@ task :version => [] do |task| file.puts content.gsub(/.*<\/version>/, "#{version}") end + Rake::Task[:dist].invoke + Rake::Task[:test].invoke + + # TODO : Make sure that all of these files are updated properly in git then run npm version end -desc "build the build and runtime version of handlebars" -task :release => [:version, :dist] directory "vendor" From 2022c94a3036849c434b53b4dac723fdc19f1d4a Mon Sep 17 00:00:00 2001 From: kpdecker Date: Sun, 2 Jun 2013 12:21:10 -0500 Subject: [PATCH 018/200] Update release notes --- release-notes.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/release-notes.md b/release-notes.md index 7de9ef2a0..d2800f925 100644 --- a/release-notes.md +++ b/release-notes.md @@ -1,6 +1,14 @@ # Release Notes ## Development + +- [#537](https://github.com/wycats/handlebars.js/issues/537) - Add CommonJS and AMD loader support ([@spikebrehm](https://github.com/spikebrehm)) +- [#544](https://github.com/wycats/handlebars.js/issues/544) - Push travis builds to build server ([@fivetanley](https://github.com/fivetanley)) + +Compatibility notes: +- The client-code has been wrapped in a hybrid AMD/CommonJS loader. + This may cause unexpected issues with different build/loading mechanisms so this change is being made early in the 1.1 lifecycle. + [Commits](https://github.com/wycats/handlebars.js/compare/v1.0.12...master) ## v1.0.12 / 1.0.0 - May 31 2013 From cfd51f657989cec1ed81715e4816664e0ae62ec3 Mon Sep 17 00:00:00 2001 From: kpdecker Date: Sun, 2 Jun 2013 12:25:07 -0500 Subject: [PATCH 019/200] Fix empty repo check --- Rakefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Rakefile b/Rakefile index da11b7079..d0a8370c8 100644 --- a/Rakefile +++ b/Rakefile @@ -106,7 +106,7 @@ task :version, [:version] => [] do |task, args| fail "Must provide a version number" unless version changed = %x{git diff-index --name-only HEAD --} - fail "The repository must be clean" unless $?.success? && !changed + fail "The repository must be clean" unless $?.success? && changed.empty? puts "Updating to version #{version}" From 5de919f2f7decc27ff62bd91ee216bca1921cb7a Mon Sep 17 00:00:00 2001 From: kpdecker Date: Sun, 2 Jun 2013 12:25:28 -0500 Subject: [PATCH 020/200] Fix test reference --- Rakefile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Rakefile b/Rakefile index d0a8370c8..d3df2ded0 100644 --- a/Rakefile +++ b/Rakefile @@ -43,7 +43,7 @@ end task :compile => "lib/handlebars/compiler/parser.js" desc "run the spec suite" -task :spec => [:release] do +task :spec => [:dist] do rc = system "npm test" fail "npm test failed with exit code #{$?.exitstatus}" if (rc.nil? || ! rc || $?.exitstatus != 0) end @@ -126,7 +126,7 @@ task :version, [:version] => [] do |task, args| end Rake::Task[:dist].invoke - Rake::Task[:test].invoke + Rake::Task[:spec].invoke # TODO : Make sure that all of these files are updated properly in git then run npm version end From e2a8bdab3c5273adc0cadbd5b5e0f988cb9baad1 Mon Sep 17 00:00:00 2001 From: kpdecker Date: Sun, 2 Jun 2013 12:28:54 -0500 Subject: [PATCH 021/200] Prevent name/dir conflict --- Rakefile | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Rakefile b/Rakefile index d3df2ded0..42eb73a14 100644 --- a/Rakefile +++ b/Rakefile @@ -43,7 +43,7 @@ end task :compile => "lib/handlebars/compiler/parser.js" desc "run the spec suite" -task :spec => [:dist] do +task :spec => [:build] do rc = system "npm test" fail "npm test failed with exit code #{$?.exitstatus}" if (rc.nil? || ! rc || $?.exitstatus != 0) end @@ -91,7 +91,7 @@ file "dist/handlebars.runtime.js" => runtime_deps do |task| build_for_task(task) end -task :dist => [:compile] do |task| +task :build => [:compile] do |task| Rake::Task["dist/handlebars.js"].execute Rake::Task["dist/handlebars.runtime.js"].execute @@ -125,7 +125,7 @@ task :version, [:version] => [] do |task, args| file.puts content.gsub(/.*<\/version>/, "#{version}") end - Rake::Task[:dist].invoke + Rake::Task[:build].invoke Rake::Task[:spec].invoke # TODO : Make sure that all of these files are updated properly in git then run npm version From 4a545cc68693b4935e5aca447660efae33efe147 Mon Sep 17 00:00:00 2001 From: kpdecker Date: Sun, 2 Jun 2013 12:54:43 -0500 Subject: [PATCH 022/200] Fix publish file mapping --- Rakefile | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/Rakefile b/Rakefile index 42eb73a14..3f6fb9356 100644 --- a/Rakefile +++ b/Rakefile @@ -15,6 +15,8 @@ def compile_parser end def dist_files(&block) + map = {} + rev = `git rev-parse --short HEAD`.to_s.strip master_rev = `git rev-parse --short origin/master`.to_s.strip @@ -24,9 +26,9 @@ def dist_files(&block) files = ['handlebars.js', 'handlebars.min.js', 'handlebars.runtime.js', 'handlebars.runtime.min.js'].map { |file| root + file } files = files.map do |file| basename = Pathname.new(file).basename.sub_ext('') - yield basename, rev + map[file] = yield basename, rev end - files = files.flatten + map end end @@ -166,10 +168,13 @@ task :publish do require 'aws-sdk' s3 = AWS::S3.new(access_key_id: access_key_id,secret_access_key: secret_access_key) bucket = s3.buckets[bucket_name] - s3_objs = files.map do |file| - bucket.objects[file] + files.each do |source, outputs| + s3_objs = outputs.map do |file| + file + bucket.objects[file] + end + s3_objs.each { |obj| obj.write(Pathname.new(source)) } end - s3_objs.each { |obj| obj.write(Pathname.new(file)) } else puts "Not uploading any files to S3!" end From bf4ec5eef7b9331841483aebbea34a13d69ed146 Mon Sep 17 00:00:00 2001 From: kpdecker Date: Sun, 2 Jun 2013 13:14:07 -0500 Subject: [PATCH 023/200] Move helper method with others to match convention --- Rakefile | 37 ++++++++++++++++++------------------- 1 file changed, 18 insertions(+), 19 deletions(-) diff --git a/Rakefile b/Rakefile index 3f6fb9356..a26673eff 100644 --- a/Rakefile +++ b/Rakefile @@ -14,24 +14,6 @@ def compile_parser end end -def dist_files(&block) - map = {} - - rev = `git rev-parse --short HEAD`.to_s.strip - master_rev = `git rev-parse --short origin/master`.to_s.strip - - if rev == master_rev - root = File.expand_path(File.dirname(__FILE__)) + '/dist/' - - files = ['handlebars.js', 'handlebars.min.js', 'handlebars.runtime.js', 'handlebars.runtime.min.js'].map { |file| root + file } - files = files.map do |file| - basename = Pathname.new(file).basename.sub_ext('') - map[file] = yield basename, rev - end - map - end -end - file "lib/handlebars/compiler/parser.js" => ["src/handlebars.yy","src/handlebars.l"] do if File.exists?('./node_modules/jison') compile_parser @@ -155,6 +137,24 @@ task :bench => "vendor" do system "node bench/handlebars.js" end +def dist_files(&block) + map = {} + + rev = `git rev-parse --short HEAD`.to_s.strip + master_rev = `git rev-parse --short origin/master`.to_s.strip + + if rev == master_rev + root = File.expand_path(File.dirname(__FILE__)) + '/dist/' + + files = ['handlebars.js', 'handlebars.min.js', 'handlebars.runtime.js', 'handlebars.runtime.min.js'].map { |file| root + file } + files = files.map do |file| + basename = Pathname.new(file).basename.sub_ext('') + map[file] = yield basename, rev + end + map + end +end + task :publish do access_key_id = ENV['S3_ACCESS_KEY_ID'] secret_access_key = ENV['S3_SECRET_ACCESS_KEY'] @@ -170,7 +170,6 @@ task :publish do bucket = s3.buckets[bucket_name] files.each do |source, outputs| s3_objs = outputs.map do |file| - file bucket.objects[file] end s3_objs.each { |obj| obj.write(Pathname.new(source)) } From 0f4ef161d120b63b1a8630e50352be507b47afc8 Mon Sep 17 00:00:00 2001 From: kpdecker Date: Sun, 2 Jun 2013 13:14:31 -0500 Subject: [PATCH 024/200] Pull out publish_s3 logic --- Rakefile | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/Rakefile b/Rakefile index a26673eff..d025c072b 100644 --- a/Rakefile +++ b/Rakefile @@ -155,15 +155,11 @@ def dist_files(&block) end end -task :publish do +def publish_s3(files) access_key_id = ENV['S3_ACCESS_KEY_ID'] secret_access_key = ENV['S3_SECRET_ACCESS_KEY'] bucket_name = ENV['S3_BUCKET_NAME'] - files = dist_files do |basename, rev| - ["#{basename}-latest.js", "#{basename}-#{rev}.js"] - end - if files && access_key_id && secret_access_key && bucket_name require 'aws-sdk' s3 = AWS::S3.new(access_key_id: access_key_id,secret_access_key: secret_access_key) @@ -178,3 +174,12 @@ task :publish do puts "Not uploading any files to S3!" end end + +task :publish do + files = dist_files do |basename, rev| + ["#{basename}-latest.js", "#{basename}-#{rev}.js"] + end + + publish_s3 files +end + From 7def10195bf384861f976dc9a771bc42e892faf6 Mon Sep 17 00:00:00 2001 From: kpdecker Date: Sun, 2 Jun 2013 13:18:54 -0500 Subject: [PATCH 025/200] Create publish_version rake task --- Rakefile | 37 ++++++++++++++++++++++++------------- 1 file changed, 24 insertions(+), 13 deletions(-) diff --git a/Rakefile b/Rakefile index d025c072b..404c681ad 100644 --- a/Rakefile +++ b/Rakefile @@ -140,19 +140,14 @@ end def dist_files(&block) map = {} - rev = `git rev-parse --short HEAD`.to_s.strip - master_rev = `git rev-parse --short origin/master`.to_s.strip - - if rev == master_rev - root = File.expand_path(File.dirname(__FILE__)) + '/dist/' + root = File.expand_path(File.dirname(__FILE__)) + '/dist/' - files = ['handlebars.js', 'handlebars.min.js', 'handlebars.runtime.js', 'handlebars.runtime.min.js'].map { |file| root + file } - files = files.map do |file| - basename = Pathname.new(file).basename.sub_ext('') - map[file] = yield basename, rev - end - map + files = ['handlebars.js', 'handlebars.min.js', 'handlebars.runtime.js', 'handlebars.runtime.min.js'].map { |file| root + file } + files = files.map do |file| + basename = Pathname.new(file).basename.sub_ext('') + map[file] = yield basename end + map end def publish_s3(files) @@ -176,10 +171,26 @@ def publish_s3(files) end task :publish do - files = dist_files do |basename, rev| - ["#{basename}-latest.js", "#{basename}-#{rev}.js"] + rev = `git rev-parse --short HEAD`.to_s.strip + master_rev = `git rev-parse --short origin/master`.to_s.strip + + if rev == master_rev + files = dist_files do |basename| + ["#{basename}-latest.js", "#{basename}-#{rev}.js"] + end end publish_s3 files end +task :publish_version do + tag = `git tag -l --points-at HEAD`.to_s.strip.split(/\n/) + fail "The current commit must be tagged." if tag.empty? + fail "Multiple tags, aborting: #{tag}" if tag.length > 1 + tag = tag.first + + files = dist_files do |basename| + ["#{basename}-#{tag}.js"] + end + publish_s3 files +end From 6174c3b6f8579a2bcb20f6c7ffba83f654fff0b1 Mon Sep 17 00:00:00 2001 From: kpdecker Date: Sun, 2 Jun 2013 23:36:34 -0500 Subject: [PATCH 026/200] Remove unnecessary function --- lib/handlebars/compiler/ast.js | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/lib/handlebars/compiler/ast.js b/lib/handlebars/compiler/ast.js index c99728ccd..567e29747 100644 --- a/lib/handlebars/compiler/ast.js +++ b/lib/handlebars/compiler/ast.js @@ -38,13 +38,10 @@ Handlebars.AST.PartialNode = function(partialName, context) { }; Handlebars.AST.BlockNode = function(mustache, program, inverse, close) { - var verifyMatch = function(open, close) { - if(open.original !== close.original) { - throw new Handlebars.Exception(open.original + " doesn't match " + close.original); - } - }; + if(mustache.id.original !== close.original) { + throw new Handlebars.Exception(mustache.id.original + " doesn't match " + close.original); + } - verifyMatch(mustache.id, close); this.type = "block"; this.mustache = mustache; this.program = program; From f11aaa7b7c754c8185c0a1f6a12a503447e7be34 Mon Sep 17 00:00:00 2001 From: Yehuda Katz Date: Mon, 3 Jun 2013 00:56:35 -0600 Subject: [PATCH 027/200] Update .travis.yml --- .travis.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.travis.yml b/.travis.yml index a3f385064..9fb0d4afc 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,5 +1,8 @@ --- after_success: bundle exec rake publish +email: + on_failure: change + on_success: never env: global: - S3_BUCKET_NAME=builds.handlebarsjs.com From c3ff57318452c731f9af280a6183a20cad9e03f7 Mon Sep 17 00:00:00 2001 From: kpdecker Date: Mon, 3 Jun 2013 23:27:35 -0500 Subject: [PATCH 028/200] Break JavascriptCompiler into standalone file --- Rakefile | 2 +- lib/handlebars/compiler/compiler.js | 846 +---------------- lib/handlebars/compiler/index.js | 4 +- .../compiler/javascript-compiler.js | 856 ++++++++++++++++++ 4 files changed, 862 insertions(+), 846 deletions(-) create mode 100644 lib/handlebars/compiler/javascript-compiler.js diff --git a/Rakefile b/Rakefile index 404c681ad..30187d743 100644 --- a/Rakefile +++ b/Rakefile @@ -39,7 +39,7 @@ def remove_exports(string) match ? match[1] : string end -minimal_deps = %w(browser-prefix base compiler/parser compiler/base compiler/ast utils compiler/compiler runtime browser-suffix).map do |file| +minimal_deps = %w(browser-prefix base compiler/parser compiler/base compiler/ast utils compiler/compiler compiler/javascript-compiler runtime browser-suffix).map do |file| "lib/handlebars/#{file}.js" end diff --git a/lib/handlebars/compiler/compiler.js b/lib/handlebars/compiler/compiler.js index 8bb1fc521..98e12b1dc 100644 --- a/lib/handlebars/compiler/compiler.js +++ b/lib/handlebars/compiler/compiler.js @@ -8,7 +8,6 @@ compilerbase.attach(Handlebars); /*jshint eqnull:true*/ var Compiler = Handlebars.Compiler = function() {}; -var JavaScriptCompiler = Handlebars.JavaScriptCompiler = function() {}; // the foundHelper register will disambiguate helper lookup from finding a // function in a context. This is necessary for mustache compatibility, which @@ -416,845 +415,6 @@ Compiler.prototype = { } }; -var Literal = function(value) { - this.value = value; -}; - -JavaScriptCompiler.prototype = { - // PUBLIC API: You can override these methods in a subclass to provide - // alternative compiled forms for name lookup and buffering semantics - nameLookup: function(parent, name /* , type*/) { - if (/^[0-9]+$/.test(name)) { - return parent + "[" + name + "]"; - } else if (JavaScriptCompiler.isValidJavaScriptVariableName(name)) { - return parent + "." + name; - } - else { - return parent + "['" + name + "']"; - } - }, - - appendToBuffer: function(string) { - if (this.environment.isSimple) { - return "return " + string + ";"; - } else { - return { - appendToBuffer: true, - content: string, - toString: function() { return "buffer += " + string + ";"; } - }; - } - }, - - initializeBuffer: function() { - return this.quotedString(""); - }, - - namespace: "Handlebars", - // END PUBLIC API - - compile: function(environment, options, context, asObject) { - this.environment = environment; - this.options = options || {}; - - Handlebars.log(Handlebars.logger.DEBUG, this.environment.disassemble() + "\n\n"); - - this.name = this.environment.name; - this.isChild = !!context; - this.context = context || { - programs: [], - environments: [], - aliases: { } - }; - - this.preamble(); - - this.stackSlot = 0; - this.stackVars = []; - this.registers = { list: [] }; - this.compileStack = []; - this.inlineStack = []; - - this.compileChildren(environment, options); - - var opcodes = environment.opcodes, opcode; - - this.i = 0; - - for(l=opcodes.length; this.i 0) { - this.source[1] = this.source[1] + ", " + locals.join(", "); - } - - // Generate minimizer alias mappings - if (!this.isChild) { - for (var alias in this.context.aliases) { - if (this.context.aliases.hasOwnProperty(alias)) { - this.source[1] = this.source[1] + ', ' + alias + '=' + this.context.aliases[alias]; - } - } - } - - if (this.source[1]) { - this.source[1] = "var " + this.source[1].substring(2) + ";"; - } - - // Merge children - if (!this.isChild) { - this.source[1] += '\n' + this.context.programs.join('\n') + '\n'; - } - - if (!this.environment.isSimple) { - this.source.push("return buffer;"); - } - - var params = this.isChild ? ["depth0", "data"] : ["Handlebars", "depth0", "helpers", "partials", "data"]; - - for(var i=0, l=this.environment.depths.list.length; i this.stackVars.length) { this.stackVars.push("stack" + this.stackSlot); } - return this.topStackName(); - }, - topStackName: function() { - return "stack" + this.stackSlot; - }, - flushInline: function() { - var inlineStack = this.inlineStack; - if (inlineStack.length) { - this.inlineStack = []; - for (var i = 0, len = inlineStack.length; i < len; i++) { - var entry = inlineStack[i]; - if (entry instanceof Literal) { - this.compileStack.push(entry); - } else { - this.pushStack(entry); - } - } - } - }, - isInline: function() { - return this.inlineStack.length; - }, - - popStack: function(wrapped) { - var inline = this.isInline(), - item = (inline ? this.inlineStack : this.compileStack).pop(); - - if (!wrapped && (item instanceof Literal)) { - return item.value; - } else { - if (!inline) { - this.stackSlot--; - } - return item; - } - }, - - topStack: function(wrapped) { - var stack = (this.isInline() ? this.inlineStack : this.compileStack), - item = stack[stack.length - 1]; - - if (!wrapped && (item instanceof Literal)) { - return item.value; - } else { - return item; - } - }, - - quotedString: function(str) { - return '"' + str - .replace(/\\/g, '\\\\') - .replace(/"/g, '\\"') - .replace(/\n/g, '\\n') - .replace(/\r/g, '\\r') - .replace(/\u2028/g, '\\u2028') // Per Ecma-262 7.3 + 7.8.4 - .replace(/\u2029/g, '\\u2029') + '"'; - }, - - setupHelper: function(paramSize, name, missingParams) { - var params = []; - this.setupParams(paramSize, params, missingParams); - var foundHelper = this.nameLookup('helpers', name, 'helper'); - - return { - params: params, - name: foundHelper, - callParams: ["depth0"].concat(params).join(", "), - helperMissingParams: missingParams && ["depth0", this.quotedString(name)].concat(params).join(", ") - }; - }, - - // the params and contexts arguments are passed in arrays - // to fill in - setupParams: function(paramSize, params, useRegister) { - var options = [], contexts = [], types = [], param, inverse, program; - - options.push("hash:" + this.popStack()); - - inverse = this.popStack(); - program = this.popStack(); - - // Avoid setting fn and inverse if neither are set. This allows - // helpers to do a check for `if (options.fn)` - if (program || inverse) { - if (!program) { - this.context.aliases.self = "this"; - program = "self.noop"; - } - - if (!inverse) { - this.context.aliases.self = "this"; - inverse = "self.noop"; - } - - options.push("inverse:" + inverse); - options.push("fn:" + program); - } - - for(var i=0; i 0) { + this.source[1] = this.source[1] + ", " + locals.join(", "); + } + + // Generate minimizer alias mappings + if (!this.isChild) { + for (var alias in this.context.aliases) { + if (this.context.aliases.hasOwnProperty(alias)) { + this.source[1] = this.source[1] + ', ' + alias + '=' + this.context.aliases[alias]; + } + } + } + + if (this.source[1]) { + this.source[1] = "var " + this.source[1].substring(2) + ";"; + } + + // Merge children + if (!this.isChild) { + this.source[1] += '\n' + this.context.programs.join('\n') + '\n'; + } + + if (!this.environment.isSimple) { + this.source.push("return buffer;"); + } + + var params = this.isChild ? ["depth0", "data"] : ["Handlebars", "depth0", "helpers", "partials", "data"]; + + for(var i=0, l=this.environment.depths.list.length; i this.stackVars.length) { this.stackVars.push("stack" + this.stackSlot); } + return this.topStackName(); + }, + topStackName: function() { + return "stack" + this.stackSlot; + }, + flushInline: function() { + var inlineStack = this.inlineStack; + if (inlineStack.length) { + this.inlineStack = []; + for (var i = 0, len = inlineStack.length; i < len; i++) { + var entry = inlineStack[i]; + if (entry instanceof Literal) { + this.compileStack.push(entry); + } else { + this.pushStack(entry); + } + } + } + }, + isInline: function() { + return this.inlineStack.length; + }, + + popStack: function(wrapped) { + var inline = this.isInline(), + item = (inline ? this.inlineStack : this.compileStack).pop(); + + if (!wrapped && (item instanceof Literal)) { + return item.value; + } else { + if (!inline) { + this.stackSlot--; + } + return item; + } + }, + + topStack: function(wrapped) { + var stack = (this.isInline() ? this.inlineStack : this.compileStack), + item = stack[stack.length - 1]; + + if (!wrapped && (item instanceof Literal)) { + return item.value; + } else { + return item; + } + }, + + quotedString: function(str) { + return '"' + str + .replace(/\\/g, '\\\\') + .replace(/"/g, '\\"') + .replace(/\n/g, '\\n') + .replace(/\r/g, '\\r') + .replace(/\u2028/g, '\\u2028') // Per Ecma-262 7.3 + 7.8.4 + .replace(/\u2029/g, '\\u2029') + '"'; + }, + + setupHelper: function(paramSize, name, missingParams) { + var params = []; + this.setupParams(paramSize, params, missingParams); + var foundHelper = this.nameLookup('helpers', name, 'helper'); + + return { + params: params, + name: foundHelper, + callParams: ["depth0"].concat(params).join(", "), + helperMissingParams: missingParams && ["depth0", this.quotedString(name)].concat(params).join(", ") + }; + }, + + // the params and contexts arguments are passed in arrays + // to fill in + setupParams: function(paramSize, params, useRegister) { + var options = [], contexts = [], types = [], param, inverse, program; + + options.push("hash:" + this.popStack()); + + inverse = this.popStack(); + program = this.popStack(); + + // Avoid setting fn and inverse if neither are set. This allows + // helpers to do a check for `if (options.fn)` + if (program || inverse) { + if (!program) { + this.context.aliases.self = "this"; + program = "self.noop"; + } + + if (!inverse) { + this.context.aliases.self = "this"; + inverse = "self.noop"; + } + + options.push("inverse:" + inverse); + options.push("fn:" + program); + } + + for(var i=0; i Date: Mon, 3 Jun 2013 23:27:38 -0500 Subject: [PATCH 029/200] Rebuild --- dist/handlebars.js | 100 +++++++++++++++++++++++---------------------- 1 file changed, 51 insertions(+), 49 deletions(-) diff --git a/dist/handlebars.js b/dist/handlebars.js index 7763cfcd6..a63247396 100644 --- a/dist/handlebars.js +++ b/dist/handlebars.js @@ -714,13 +714,10 @@ Handlebars.AST.PartialNode = function(partialName, context) { }; Handlebars.AST.BlockNode = function(mustache, program, inverse, close) { - var verifyMatch = function(open, close) { - if(open.original !== close.original) { - throw new Handlebars.Exception(open.original + " doesn't match " + close.original); - } - }; + if(mustache.id.original !== close.original) { + throw new Handlebars.Exception(mustache.id.original + " doesn't match " + close.original); + } - verifyMatch(mustache.id, close); this.type = "block"; this.mustache = mustache; this.program = program; @@ -886,7 +883,6 @@ Handlebars.Utils = { /*jshint eqnull:true*/ var Compiler = Handlebars.Compiler = function() {}; -var JavaScriptCompiler = Handlebars.JavaScriptCompiler = function() {}; // the foundHelper register will disambiguate helper lookup from finding a // function in a context. This is necessary for mustache compatibility, which @@ -1294,10 +1290,57 @@ Compiler.prototype = { } }; +Handlebars.precompile = function(input, options) { + if (input == null || (typeof input !== 'string' && input.constructor !== Handlebars.AST.ProgramNode)) { + throw new Handlebars.Exception("You must pass a string or Handlebars AST to Handlebars.precompile. You passed " + input); + } + + options = options || {}; + if (!('data' in options)) { + options.data = true; + } + var ast = Handlebars.parse(input); + var environment = new Compiler().compile(ast, options); + return new Handlebars.JavaScriptCompiler().compile(environment, options); +}; + +Handlebars.compile = function(input, options) { + if (input == null || (typeof input !== 'string' && input.constructor !== Handlebars.AST.ProgramNode)) { + throw new Handlebars.Exception("You must pass a string or Handlebars AST to Handlebars.compile. You passed " + input); + } + + options = options || {}; + if (!('data' in options)) { + options.data = true; + } + var compiled; + function compile() { + var ast = Handlebars.parse(input); + var environment = new Compiler().compile(ast, options); + var templateSpec = new Handlebars.JavaScriptCompiler().compile(environment, options, undefined, true); + return Handlebars.template(templateSpec); + } + + // Template is only compiled on first use and cached after that point. + return function(context, options) { + if (!compiled) { + compiled = compile(); + } + return compiled.call(this, context, options); + }; +}; + +; +// lib/handlebars/compiler/javascript-compiler.js +/*jshint eqnull:true*/ + var Literal = function(value) { this.value = value; }; + +var JavaScriptCompiler = Handlebars.JavaScriptCompiler = function() {}; + JavaScriptCompiler.prototype = { // PUBLIC API: You can override these methods in a subclass to provide // alternative compiled forms for name lookup and buffering semantics @@ -1359,7 +1402,7 @@ JavaScriptCompiler.prototype = { this.i = 0; - for(l=opcodes.length; this.i Date: Tue, 4 Jun 2013 23:06:40 +0530 Subject: [PATCH 030/200] Added BOM removal flag. Byte order mark is now removed from the beginning of the files when b flag is used. --- bin/handlebars | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/bin/handlebars b/bin/handlebars index 4835770f9..b30a1fe19 100755 --- a/bin/handlebars +++ b/bin/handlebars @@ -70,6 +70,11 @@ var optimist = require('optimist') 'description': 'Template extension.', 'alias': 'extension', 'default': 'handlebars' + }, + 'b': { + 'type': 'boolean', + 'description': 'Removes the BOM (Byte Order Mark) from the beginning of the templates.', + 'alias': 'bom' } }) @@ -147,6 +152,10 @@ function processTemplate(template, root) { }); } else { var data = fs.readFileSync(path, 'utf8'); + + if (argv.bom && data.indexOf('\uFEFF') === 0) { + data = data.substring(1); + } var options = { knownHelpers: known, From 68e647a17e5add360b4f2042db975291a0c38e02 Mon Sep 17 00:00:00 2001 From: Ryunosuke SATO Date: Sat, 8 Jun 2013 23:19:55 +0900 Subject: [PATCH 031/200] Modify keywords type in package.json `keywords` should be an array. - https://npmjs.org/doc/json.html#keywords --- package.json | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/package.json b/package.json index b6cc234f0..13fdc74ee 100644 --- a/package.json +++ b/package.json @@ -4,7 +4,10 @@ "version": "1.0.12", "homepage": "http://www.handlebarsjs.com/", "keywords": [ - "handlebars mustache template html" + "handlebars", + "mustache", + "template", + "html" ], "repository": { "type": "git", From 96718441ec774d97527a7b6fe9aab0c0839a6d2c Mon Sep 17 00:00:00 2001 From: Blessan Mathew Date: Sun, 9 Jun 2013 21:05:58 +0530 Subject: [PATCH 032/200] Added additional options to the doc. Added additional precompilation options to the readme doc. --- README.markdown | 21 ++++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) diff --git a/README.markdown b/README.markdown index 0946d9035..ebd4cd561 100644 --- a/README.markdown +++ b/README.markdown @@ -271,13 +271,20 @@ Precompile handlebar templates. Usage: handlebars template... Options: - -a, --amd Create an AMD format function (allows loading with RequireJS) [boolean] - -f, --output Output File [string] - -k, --known Known helpers [string] - -o, --knownOnly Known helpers only [boolean] - -m, --min Minimize output [boolean] - -s, --simple Output template function only. [boolean] - -r, --root Template root. Base value that will be stripped from template names. [string] + -a, --amd Create an AMD format function (allows loading with RequireJS) [boolean] + -f, --output Output File [string] + -k, --known Known helpers [string] + -o, --knownOnly Known helpers only [boolean] + -m, --min Minimize output [boolean] + -s, --simple Output template function only. [boolean] + -r, --root Template root. Base value that will be stripped from template names. [string] + -c, --commonjs Exports CommonJS style, path to Handlebars module [string] + -h, --handlebarPath Path to handlebar.js (only valid for amd-style) [string] + -n, --namespace Template namespace [string] + -p, --partial Compiling a partial template [boolean] + -d, --data Include data when compiling [boolean] + -e, --extension Template extension. [string] + -b, --bom Removes the BOM (Byte Order Mark) from the beginning of the templates. [boolean] If using the precompiler's normal mode, the resulting templates will be From e4929df45473e200e103e2fe86d5fb820131bfa4 Mon Sep 17 00:00:00 2001 From: Daniel Marcotte Date: Wed, 12 Jun 2013 09:10:22 -0700 Subject: [PATCH 033/200] Update readme to reflect latest rake targets --- README.markdown | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.markdown b/README.markdown index 0946d9035..8e2fc2226 100644 --- a/README.markdown +++ b/README.markdown @@ -333,7 +333,7 @@ and we will have some benchmarks in the near future. Building -------- -To build handlebars, just run `rake release`, and you will get two files +To build handlebars, just run `rake build`, and you will get two files in the `dist` directory. @@ -378,9 +378,9 @@ To build Handlebars.js you'll need a few things installed. There's a Gemfile in the repo, so you can run `bundle` to install rake if you've got bundler installed. -To build Handlebars.js from scratch, you'll want to run `rake compile` +To build Handlebars.js from scratch, you'll want to run `rake build` in the root of the project. That will build Handlebars and output the -results to the dist/ folder. To run tests, run `rake test` or `npm test. +results to the dist/ folder. To run tests, run `rake spec` or `npm test`. You can also run our set of benchmarks with `rake bench`. If you notice any problems, please report them to the GitHub issue tracker at From ca5c3995fededcb5e6dcdad5e5da11857ffc8ec6 Mon Sep 17 00:00:00 2001 From: Alberto La Rocca Date: Thu, 27 Jun 2013 20:26:16 +0200 Subject: [PATCH 034/200] Announcing jQuery plugin --- README.markdown | 1 + 1 file changed, 1 insertion(+) diff --git a/README.markdown b/README.markdown index 0946d9035..2621798f4 100644 --- a/README.markdown +++ b/README.markdown @@ -364,6 +364,7 @@ Handlebars in the Wild * [Gist about Synchronous and asynchronous loading of external handlebars templates](https://gist.github.com/2287070) * [Lumbar](walmartlabs.github.io/lumbar) provides easy module-based template management for handlebars projects. * [YUI](http://yuilibrary.com/yui/docs/handlebars/) implements a port of handlebars +* This [jQuery plugin](http://71104.github.io/jquery-handlebars/) allows you to use Handlebars.js with [jQuery](http://jquery.com/). Have a project using Handlebars? Send us a [pull request](https://github.com/wycats/handlebars.js/pull/new/master)! From 88ee4757e77f97afb206132eddb64e688700eb37 Mon Sep 17 00:00:00 2001 From: Yehuda Katz Date: Mon, 1 Jul 2013 13:59:58 -0700 Subject: [PATCH 035/200] Initial work on ES6 modules --- Gruntfile.js | 51 ++++ configurations/browser.js | 6 + configurations/concat.js | 16 ++ configurations/connect.js | 8 + configurations/transpile.js | 25 ++ configurations/watch.js | 4 + lib/handlebars.js | 22 +- lib/handlebars/base.js | 246 +++++++++--------- lib/handlebars/compiler/ast.js | 39 ++- lib/handlebars/compiler/base.js | 25 +- lib/handlebars/compiler/compiler.js | 46 ++-- .../compiler/javascript-compiler.js | 24 +- lib/handlebars/runtime.js | 202 +++++++------- lib/handlebars/utils.js | 83 +++--- package.json | 54 ++-- 15 files changed, 452 insertions(+), 399 deletions(-) create mode 100644 Gruntfile.js create mode 100644 configurations/browser.js create mode 100644 configurations/concat.js create mode 100644 configurations/connect.js create mode 100644 configurations/transpile.js create mode 100644 configurations/watch.js diff --git a/Gruntfile.js b/Gruntfile.js new file mode 100644 index 000000000..56231b7c8 --- /dev/null +++ b/Gruntfile.js @@ -0,0 +1,51 @@ +function config(name) { + return require('./configurations/' + name); +} + +module.exports = function(grunt) { + + grunt.initConfig({ + pkg: grunt.file.readJSON('package.json'), + + clean: ["dist"], + watch: config('watch') , + concat: config('concat'), + browser: config('browser'), + connect: config('connect'), + transpile: config('transpile') + }); + + // By default, (i.e., if you invoke `grunt` without arguments), do + // a new build. + this.registerTask('default', ['build']); + + // Build a new version of the library + this.registerTask('build', "Builds a distributable version of the current project", [ + 'clean', + 'transpile:amd', + 'concat:library', + 'concat:browser', + 'browser:dist', + 'bytes']); + + this.registerTask('tests', "Builds the test package", [ + 'build', + 'concat:deps', + 'transpile:tests']); + + // Run a server. This is ideal for running the QUnit tests in the browser. + this.registerTask('server', [ + 'build', + 'tests', + 'connect', + 'watch']); + + // Load tasks from npm + grunt.loadNpmTasks('grunt-contrib-clean'); + grunt.loadNpmTasks('grunt-contrib-concat'); + grunt.loadNpmTasks('grunt-contrib-connect'); + grunt.loadNpmTasks('grunt-contrib-watch'); + grunt.loadNpmTasks('grunt-es6-module-transpiler'); + + grunt.task.loadTasks('tasks'); +}; diff --git a/configurations/browser.js b/configurations/browser.js new file mode 100644 index 000000000..e3d0dc3b2 --- /dev/null +++ b/configurations/browser.js @@ -0,0 +1,6 @@ +module.exports = { + dist: { + src: 'tmp/<%= pkg.barename %>.browser1.js', + dest: 'dist/<%= pkg.name %>-<%= pkg.version %>.js' + } +}; diff --git a/configurations/concat.js b/configurations/concat.js new file mode 100644 index 000000000..4111eee6b --- /dev/null +++ b/configurations/concat.js @@ -0,0 +1,16 @@ +module.exports = { + library: { + src: ['tmp/<%= pkg.barename %>.amd.js'], + dest: 'dist/<%= pkg.name %>-<%= pkg.version %>.amd.js' + }, + + deps: { + src: ['vendor/deps/*.js'], + dest: 'tmp/deps.amd.js' + }, + + browser: { + src: ['vendor/loader.js', 'tmp/<%= pkg.barename %>.amd.js'], + dest: 'tmp/<%= pkg.barename %>.browser1.js' + } +}; diff --git a/configurations/connect.js b/configurations/connect.js new file mode 100644 index 000000000..dacc7600b --- /dev/null +++ b/configurations/connect.js @@ -0,0 +1,8 @@ +module.exports = { + server: {}, + options: { + hostname: '0.0.0.0', + port: 8000, + base: '.' + } +}; diff --git a/configurations/transpile.js b/configurations/transpile.js new file mode 100644 index 000000000..e912ecfb4 --- /dev/null +++ b/configurations/transpile.js @@ -0,0 +1,25 @@ +module.exports = { + amd: { + type: 'amd', + src: ["lib/<%= pkg.barename %>.js", "lib/*/**/*.js"], + dest: "tmp/<%= pkg.barename %>.amd.js" + }, + + cjs: { + type: 'cjs', + src: ["lib/<%= pkg.barename %>.js", "lib/*/**/*.js"], + dest: "tmp/<%= pkg.barename %>.cjs.js" + }, + + globals: { + type: 'globals', + src: ["lib/<%= pkg.barename %>.js", "lib/*/**/*.js"], + dest: "tmp/<%= pkg.barename %>.globals.js" + }, + + tests: { + type: 'amd', + src: ['test/test_helpers.js', 'test/tests.js', 'test/tests/**/*_test.js'], + dest: 'tmp/tests.amd.js' + } +}; diff --git a/configurations/watch.js b/configurations/watch.js new file mode 100644 index 000000000..49b1c49bd --- /dev/null +++ b/configurations/watch.js @@ -0,0 +1,4 @@ +module.exports = { + files: ['lib/**', 'vendor/*', 'test/**/*'], + tasks: ['build', 'tests'] +}; diff --git a/lib/handlebars.js b/lib/handlebars.js index f82ec3bad..e051aa5c0 100644 --- a/lib/handlebars.js +++ b/lib/handlebars.js @@ -1,15 +1,19 @@ -var handlebars = require("./handlebars/base"), +import handlebars from "handlebars/base"; // Each of these augment the Handlebars object. No need to setup here. // (This is done to easily share code between commonjs and browse envs) - utils = require("./handlebars/utils"), - compiler = require("./handlebars/compiler"), - runtime = require("./handlebars/runtime"); +import { SafeString, Exception, extend, escapeExpression, isEmpty } from "handlebars/utils"; +import compiler from "handlebars/compiler"; +import runtime from "handlebars/runtime"; +// For compatibility and usage outside of module systems, make the Handlebars object a namespace var create = function() { var hb = handlebars.create(); - utils.attach(hb); + hb.SafeString = SafeString; + hb.Exception = Exception; + hb.utils = { extend: extend, escapeExpression: escapeExpression, isEmpty: isEmpty }; + compiler.attach(hb); runtime.attach(hb); @@ -19,10 +23,10 @@ var create = function() { var Handlebars = create(); Handlebars.create = create; -module.exports = Handlebars; // instantiate an instance +export default Handlebars; // Publish a Node.js require() handler for .handlebars and .hbs files -if (require.extensions) { +if (typeof require !== 'undefined' && require.extensions) { var extension = function(module, filename) { var fs = require("fs"); var templateString = fs.readFileSync(filename, "utf8"); @@ -32,10 +36,6 @@ if (require.extensions) { require.extensions[".hbs"] = extension; } -// BEGIN(BROWSER) - -// END(BROWSER) - // USAGE: // var handlebars = require('handlebars'); diff --git a/lib/handlebars/base.js b/lib/handlebars/base.js index 44a369c5e..98496786d 100644 --- a/lib/handlebars/base.js +++ b/lib/handlebars/base.js @@ -1,166 +1,168 @@ /*jshint eqnull: true */ -module.exports.create = function() { +import { Exception, extend } from "handlebars/utils"; -var Handlebars = {}; +var K = function() { return this; }; -// BEGIN(BROWSER) +export VERSION = "1.0.0"; +export COMPILER_REVISION = 4; -Handlebars.VERSION = "1.0.0"; -Handlebars.COMPILER_REVISION = 4; - -Handlebars.REVISION_CHANGES = { +export REVISION_CHANGES = { 1: '<= 1.0.rc.2', // 1.0.rc.2 is actually rev2 but doesn't report it 2: '== 1.0.0-rc.3', 3: '== 1.0.0-rc.4', 4: '>= 1.0.0' }; -Handlebars.helpers = {}; -Handlebars.partials = {}; +// TODO: Make this a class +export default function(helpers, partials) { -var toString = Object.prototype.toString, - functionType = '[object Function]', - objectType = '[object Object]'; + var exports = {}; -Handlebars.registerHelper = function(name, fn, inverse) { - if (toString.call(name) === objectType) { - if (inverse || fn) { throw new Handlebars.Exception('Arg not supported with multiple helpers'); } - Handlebars.Utils.extend(this.helpers, name); - } else { - if (inverse) { fn.not = inverse; } - this.helpers[name] = fn; - } -}; -Handlebars.registerPartial = function(name, str) { - if (toString.call(name) === objectType) { - Handlebars.Utils.extend(this.partials, name); - } else { - this.partials[name] = str; - } -}; - -Handlebars.registerHelper('helperMissing', function(arg) { - if(arguments.length === 2) { - return undefined; - } else { - throw new Error("Missing helper: '" + arg + "'"); - } -}); + helpers = helpers || {}; + partials = partials || {}; -Handlebars.registerHelper('blockHelperMissing', function(context, options) { - var inverse = options.inverse || function() {}, fn = options.fn; + var toString = Object.prototype.toString, + functionType = '[object Function]', + objectType = '[object Object]'; - var type = toString.call(context); + exports.registerHelper(name, fn, inverse) { + if (toString.call(name) === objectType) { + if (inverse || fn) { throw new Exception('Arg not supported with multiple helpers'); } + extend(helpers, name); + } else { + if (inverse) { fn.not = inverse; } + helpers[name] = fn; + } + }; - if(type === functionType) { context = context.call(this); } + exports.registerPartial(name, str) { + if (toString.call(name) === objectType) { + extend(partials, name); + } else { + partials[name] = str; + } + }; - if(context === true) { - return fn(this); - } else if(context === false || context == null) { - return inverse(this); - } else if(type === "[object Array]") { - if(context.length > 0) { - return Handlebars.helpers.each(context, options); + exports.registerHelper('helperMissing', function(arg) { + if(arguments.length === 2) { + return undefined; } else { - return inverse(this); + throw new Error("Missing helper: '" + arg + "'"); } - } else { - return fn(context); - } -}); + }); -Handlebars.K = function() {}; + exports.registerHelper('blockHelperMissing', function(context, options) { + var inverse = options.inverse || function() {}, fn = options.fn; -Handlebars.createFrame = Object.create || function(object) { - Handlebars.K.prototype = object; - var obj = new Handlebars.K(); - Handlebars.K.prototype = null; - return obj; -}; - -Handlebars.logger = { - DEBUG: 0, INFO: 1, WARN: 2, ERROR: 3, level: 3, + var type = toString.call(context); - methodMap: {0: 'debug', 1: 'info', 2: 'warn', 3: 'error'}, + if(type === functionType) { context = context.call(this); } - // can be overridden in the host environment - log: function(level, obj) { - if (Handlebars.logger.level <= level) { - var method = Handlebars.logger.methodMap[level]; - if (typeof console !== 'undefined' && console[method]) { - console[method].call(console, obj); + if(context === true) { + return fn(this); + } else if(context === false || context == null) { + return inverse(this); + } else if(type === "[object Array]") { + if(context.length > 0) { + return Handlebars.helpers.each(context, options); + } else { + return inverse(this); } + } else { + return fn(context); } - } -}; - -Handlebars.log = function(level, obj) { Handlebars.logger.log(level, obj); }; + }); -Handlebars.registerHelper('each', function(context, options) { - var fn = options.fn, inverse = options.inverse; - var i = 0, ret = "", data; + exports.registerHelper('each', function(context, options) { + var fn = options.fn, inverse = options.inverse; + var i = 0, ret = "", data; - var type = toString.call(context); - if(type === functionType) { context = context.call(this); } + var type = toString.call(context); + if(type === functionType) { context = context.call(this); } - if (options.data) { - data = Handlebars.createFrame(options.data); - } + if (options.data) { + data = Handlebars.createFrame(options.data); + } - if(context && typeof context === 'object') { - if(context instanceof Array){ - for(var j = context.length; i=0.4.7" - }, - "dependencies": { - "optimist": "~0.3", - "uglify-js": "~2.3" + "url": "https://github.com/wycats/handlebars.js.git" }, + "author": "Yehuda Katz", + "license": "BSD", + "readmeFilename": "README.md", "devDependencies": { - "benchmark": "~1.0", - "dust": "~0.3", - "jison": "~0.3", - "mocha": "*", - "mustache": "~0.7.2", - "should": "~1.2.2" - }, - "main": "lib/handlebars.js", - "bin": { - "handlebars": "bin/handlebars" - }, - "scripts": { - "test": "node ./spec/env/runner" - }, - "optionalDependencies": {} + "grunt": "~0.4.1", + "connect": "~2.7.4", + "grunt-contrib-concat": "~0.3.0", + "grunt-contrib-clean": "~0.4.1", + "grunt-contrib-connect": "~0.3.0", + "grunt-contrib-watch": "~0.4.4", + "grunt-hang": "~0.1.2", + "grunt-es6-module-transpiler": "~0.3.0" + } } From 12f8299eb25e605d825f96f50599fc3851c9d8ec Mon Sep 17 00:00:00 2001 From: Yehuda Katz Date: Tue, 2 Jul 2013 18:57:28 +0000 Subject: [PATCH 036/200] More modularization --- lib/handlebars/browser-prefix.js | 2 -- lib/handlebars/browser-suffix.js | 13 --------- lib/handlebars/compiler/index.js | 18 ------------- lib/handlebars/compiler/printer.js | 42 +++++++++++++----------------- lib/handlebars/compiler/visitor.js | 15 +++-------- 5 files changed, 21 insertions(+), 69 deletions(-) delete mode 100644 lib/handlebars/browser-prefix.js delete mode 100644 lib/handlebars/browser-suffix.js delete mode 100644 lib/handlebars/compiler/index.js diff --git a/lib/handlebars/browser-prefix.js b/lib/handlebars/browser-prefix.js deleted file mode 100644 index a8f172231..000000000 --- a/lib/handlebars/browser-prefix.js +++ /dev/null @@ -1,2 +0,0 @@ -(function(undefined) { - var Handlebars = {}; diff --git a/lib/handlebars/browser-suffix.js b/lib/handlebars/browser-suffix.js deleted file mode 100644 index d357c720a..000000000 --- a/lib/handlebars/browser-suffix.js +++ /dev/null @@ -1,13 +0,0 @@ - if (typeof module === 'object' && module.exports) { - // CommonJS - module.exports = Handlebars; - - } else if (typeof define === "function" && define.amd) { - // AMD modules - define(function() { return Handlebars; }); - - } else { - // other, i.e. browser - this.Handlebars = Handlebars; - } -}).call(this); diff --git a/lib/handlebars/compiler/index.js b/lib/handlebars/compiler/index.js deleted file mode 100644 index 0cc5c017a..000000000 --- a/lib/handlebars/compiler/index.js +++ /dev/null @@ -1,18 +0,0 @@ -// Each of these module will augment the Handlebars object as it loads. No need to perform addition operations -module.exports.attach = function(Handlebars) { - -var visitor = require("./visitor"), - printer = require("./printer"), - ast = require("./ast"), - compiler = require("./compiler"), - javascriptCompiler = require("./javascript-compiler"); - -visitor.attach(Handlebars); -printer.attach(Handlebars); -ast.attach(Handlebars); -compiler.attach(Handlebars); -javascriptCompiler.attach(Handlebars); - -return Handlebars; - -}; diff --git a/lib/handlebars/compiler/printer.js b/lib/handlebars/compiler/printer.js index d9eb7a577..6b72c2552 100644 --- a/lib/handlebars/compiler/printer.js +++ b/lib/handlebars/compiler/printer.js @@ -1,15 +1,13 @@ -exports.attach = function(Handlebars) { +import Visitor from "handlebars/compiler/visitor"; -// BEGIN(BROWSER) - -Handlebars.print = function(ast) { - return new Handlebars.PrintVisitor().accept(ast); +export print = function(ast) { + return new PrintVisitor().accept(ast); }; -Handlebars.PrintVisitor = function() { this.padding = 0; }; -Handlebars.PrintVisitor.prototype = new Handlebars.Visitor(); +export function PrintVisitor() { this.padding = 0; }; +PrintVisitor.prototype = new Handlebars.Visitor(); -Handlebars.PrintVisitor.prototype.pad = function(string, newline) { +PrintVisitor.prototype.pad = function(string, newline) { var out = ""; for(var i=0,l=this.padding; i " + content + " }}"); }; -Handlebars.PrintVisitor.prototype.hash = function(hash) { +PrintVisitor.prototype.hash = function(hash) { var pairs = hash.pairs; var joinedPairs = [], left, right; @@ -95,19 +93,19 @@ Handlebars.PrintVisitor.prototype.hash = function(hash) { return "HASH{" + joinedPairs.join(", ") + "}"; }; -Handlebars.PrintVisitor.prototype.STRING = function(string) { +PrintVisitor.prototype.STRING = function(string) { return '"' + string.string + '"'; }; -Handlebars.PrintVisitor.prototype.INTEGER = function(integer) { +PrintVisitor.prototype.INTEGER = function(integer) { return "INTEGER{" + integer.integer + "}"; }; -Handlebars.PrintVisitor.prototype.BOOLEAN = function(bool) { +PrintVisitor.prototype.BOOLEAN = function(bool) { return "BOOLEAN{" + bool.bool + "}"; }; -Handlebars.PrintVisitor.prototype.ID = function(id) { +PrintVisitor.prototype.ID = function(id) { var path = id.parts.join("/"); if(id.parts.length > 1) { return "PATH:" + path; @@ -116,23 +114,19 @@ Handlebars.PrintVisitor.prototype.ID = function(id) { } }; -Handlebars.PrintVisitor.prototype.PARTIAL_NAME = function(partialName) { +PrintVisitor.prototype.PARTIAL_NAME = function(partialName) { return "PARTIAL:" + partialName.name; }; -Handlebars.PrintVisitor.prototype.DATA = function(data) { +PrintVisitor.prototype.DATA = function(data) { return "@" + this.accept(data.id); }; -Handlebars.PrintVisitor.prototype.content = function(content) { +PrintVisitor.prototype.content = function(content) { return this.pad("CONTENT[ '" + content.string + "' ]"); }; -Handlebars.PrintVisitor.prototype.comment = function(comment) { +PrintVisitor.prototype.comment = function(comment) { return this.pad("{{! '" + comment.comment + "' }}"); }; -// END(BROWSER) - -return Handlebars; -}; diff --git a/lib/handlebars/compiler/visitor.js b/lib/handlebars/compiler/visitor.js index 5d0731407..29ea82c58 100644 --- a/lib/handlebars/compiler/visitor.js +++ b/lib/handlebars/compiler/visitor.js @@ -1,18 +1,9 @@ -exports.attach = function(Handlebars) { +export function Visitor() {}; -// BEGIN(BROWSER) +Visitor.prototype = { + constructor: Visitor, -Handlebars.Visitor = function() {}; - -Handlebars.Visitor.prototype = { accept: function(object) { return this[object.type](object); } }; - -// END(BROWSER) - -return Handlebars; -}; - - From a27e366f28ca2aec3295dea606d8cc362d8accaf Mon Sep 17 00:00:00 2001 From: Dario Sneidermanis Date: Fri, 12 Jul 2013 23:14:50 -0300 Subject: [PATCH 037/200] Added missing quotes --- README.markdown | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.markdown b/README.markdown index 1c4c030bc..4da762708 100644 --- a/README.markdown +++ b/README.markdown @@ -119,7 +119,7 @@ To display data from descendant contexts, use the `.` character. So, for example, if your data were structured like: ```js -var data = {"person": { "name": "Alan" }, company: {"name": "Rad, Inc." } }; +var data = {"person": { "name": "Alan" }, "company": {"name": "Rad, Inc." } }; ``` You could display the person's name from the top-level context with the From da130f7745fc338d7ea31c60d9954ab6e0e1511a Mon Sep 17 00:00:00 2001 From: Yehuda Katz Date: Tue, 16 Jul 2013 19:10:37 +0000 Subject: [PATCH 038/200] Building an AMD file --- .gitignore | 3 +- Gruntfile.js | 6 +- configurations/concat.js | 2 +- configurations/transpile.js | 10 +- dist/handlebars.js | 2292 ----------------- dist/handlebars.runtime.js | 374 --- lib/handlebars/base.js | 2 +- lib/handlebars/compiler/base.js | 2 +- lib/handlebars/compiler/compiler.js | 3 +- .../compiler/javascript-compiler.js | 3 +- lib/handlebars/compiler/printer.js | 2 +- package.json | 2 +- 12 files changed, 17 insertions(+), 2684 deletions(-) delete mode 100644 dist/handlebars.js delete mode 100644 dist/handlebars.runtime.js diff --git a/.gitignore b/.gitignore index db9d40a00..fc7d79ae6 100644 --- a/.gitignore +++ b/.gitignore @@ -2,7 +2,8 @@ vendor .rvmrc .DS_Store lib/handlebars/compiler/parser.js -dist +/dist/ +/tmp/ node_modules *.sublime-project *.sublime-workspace diff --git a/Gruntfile.js b/Gruntfile.js index 56231b7c8..285f75e5f 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -10,7 +10,6 @@ module.exports = function(grunt) { clean: ["dist"], watch: config('watch') , concat: config('concat'), - browser: config('browser'), connect: config('connect'), transpile: config('transpile') }); @@ -23,10 +22,7 @@ module.exports = function(grunt) { this.registerTask('build', "Builds a distributable version of the current project", [ 'clean', 'transpile:amd', - 'concat:library', - 'concat:browser', - 'browser:dist', - 'bytes']); + 'concat:library']); this.registerTask('tests', "Builds the test package", [ 'build', diff --git a/configurations/concat.js b/configurations/concat.js index 4111eee6b..f708501ce 100644 --- a/configurations/concat.js +++ b/configurations/concat.js @@ -1,6 +1,6 @@ module.exports = { library: { - src: ['tmp/<%= pkg.barename %>.amd.js'], + src: ['tmp/<%= pkg.barename %>.amd.js', 'tmp/<%= pkg.barename %>/*.js'], dest: 'dist/<%= pkg.name %>-<%= pkg.version %>.amd.js' }, diff --git a/configurations/transpile.js b/configurations/transpile.js index e912ecfb4..679fbf288 100644 --- a/configurations/transpile.js +++ b/configurations/transpile.js @@ -1,8 +1,12 @@ module.exports = { amd: { - type: 'amd', - src: ["lib/<%= pkg.barename %>.js", "lib/*/**/*.js"], - dest: "tmp/<%= pkg.barename %>.amd.js" + type: "amd", + files: [{ + expand: true, + cwd: 'lib/', + src: '**/*.js', + dest: 'tmp' + }] }, cjs: { diff --git a/dist/handlebars.js b/dist/handlebars.js deleted file mode 100644 index a63247396..000000000 --- a/dist/handlebars.js +++ /dev/null @@ -1,2292 +0,0 @@ -/* - -Copyright (C) 2011 by Yehuda Katz - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -THE SOFTWARE. - -@license -*/ - -// lib/handlebars/browser-prefix.js -(function(undefined) { - var Handlebars = {}; -; -// lib/handlebars/base.js - -Handlebars.VERSION = "1.0.0"; -Handlebars.COMPILER_REVISION = 4; - -Handlebars.REVISION_CHANGES = { - 1: '<= 1.0.rc.2', // 1.0.rc.2 is actually rev2 but doesn't report it - 2: '== 1.0.0-rc.3', - 3: '== 1.0.0-rc.4', - 4: '>= 1.0.0' -}; - -Handlebars.helpers = {}; -Handlebars.partials = {}; - -var toString = Object.prototype.toString, - functionType = '[object Function]', - objectType = '[object Object]'; - -Handlebars.registerHelper = function(name, fn, inverse) { - if (toString.call(name) === objectType) { - if (inverse || fn) { throw new Handlebars.Exception('Arg not supported with multiple helpers'); } - Handlebars.Utils.extend(this.helpers, name); - } else { - if (inverse) { fn.not = inverse; } - this.helpers[name] = fn; - } -}; - -Handlebars.registerPartial = function(name, str) { - if (toString.call(name) === objectType) { - Handlebars.Utils.extend(this.partials, name); - } else { - this.partials[name] = str; - } -}; - -Handlebars.registerHelper('helperMissing', function(arg) { - if(arguments.length === 2) { - return undefined; - } else { - throw new Error("Missing helper: '" + arg + "'"); - } -}); - -Handlebars.registerHelper('blockHelperMissing', function(context, options) { - var inverse = options.inverse || function() {}, fn = options.fn; - - var type = toString.call(context); - - if(type === functionType) { context = context.call(this); } - - if(context === true) { - return fn(this); - } else if(context === false || context == null) { - return inverse(this); - } else if(type === "[object Array]") { - if(context.length > 0) { - return Handlebars.helpers.each(context, options); - } else { - return inverse(this); - } - } else { - return fn(context); - } -}); - -Handlebars.K = function() {}; - -Handlebars.createFrame = Object.create || function(object) { - Handlebars.K.prototype = object; - var obj = new Handlebars.K(); - Handlebars.K.prototype = null; - return obj; -}; - -Handlebars.logger = { - DEBUG: 0, INFO: 1, WARN: 2, ERROR: 3, level: 3, - - methodMap: {0: 'debug', 1: 'info', 2: 'warn', 3: 'error'}, - - // can be overridden in the host environment - log: function(level, obj) { - if (Handlebars.logger.level <= level) { - var method = Handlebars.logger.methodMap[level]; - if (typeof console !== 'undefined' && console[method]) { - console[method].call(console, obj); - } - } - } -}; - -Handlebars.log = function(level, obj) { Handlebars.logger.log(level, obj); }; - -Handlebars.registerHelper('each', function(context, options) { - var fn = options.fn, inverse = options.inverse; - var i = 0, ret = "", data; - - var type = toString.call(context); - if(type === functionType) { context = context.call(this); } - - if (options.data) { - data = Handlebars.createFrame(options.data); - } - - if(context && typeof context === 'object') { - if(context instanceof Array){ - for(var j = context.length; i 2) { - expected.push("'" + this.terminals_[p] + "'"); - } - if (this.lexer.showPosition) { - errStr = "Parse error on line " + (yylineno + 1) + ":\n" + this.lexer.showPosition() + "\nExpecting " + expected.join(", ") + ", got '" + (this.terminals_[symbol] || symbol) + "'"; - } else { - errStr = "Parse error on line " + (yylineno + 1) + ": Unexpected " + (symbol == 1?"end of input":"'" + (this.terminals_[symbol] || symbol) + "'"); - } - this.parseError(errStr, {text: this.lexer.match, token: this.terminals_[symbol] || symbol, line: this.lexer.yylineno, loc: yyloc, expected: expected}); - } - } - if (action[0] instanceof Array && action.length > 1) { - throw new Error("Parse Error: multiple actions possible at state: " + state + ", token: " + symbol); - } - switch (action[0]) { - case 1: - stack.push(symbol); - vstack.push(this.lexer.yytext); - lstack.push(this.lexer.yylloc); - stack.push(action[1]); - symbol = null; - if (!preErrorSymbol) { - yyleng = this.lexer.yyleng; - yytext = this.lexer.yytext; - yylineno = this.lexer.yylineno; - yyloc = this.lexer.yylloc; - if (recovering > 0) - recovering--; - } else { - symbol = preErrorSymbol; - preErrorSymbol = null; - } - break; - case 2: - len = this.productions_[action[1]][1]; - yyval.$ = vstack[vstack.length - len]; - yyval._$ = {first_line: lstack[lstack.length - (len || 1)].first_line, last_line: lstack[lstack.length - 1].last_line, first_column: lstack[lstack.length - (len || 1)].first_column, last_column: lstack[lstack.length - 1].last_column}; - if (ranges) { - yyval._$.range = [lstack[lstack.length - (len || 1)].range[0], lstack[lstack.length - 1].range[1]]; - } - r = this.performAction.call(yyval, yytext, yyleng, yylineno, this.yy, action[1], vstack, lstack); - if (typeof r !== "undefined") { - return r; - } - if (len) { - stack = stack.slice(0, -1 * len * 2); - vstack = vstack.slice(0, -1 * len); - lstack = lstack.slice(0, -1 * len); - } - stack.push(this.productions_[action[1]][0]); - vstack.push(yyval.$); - lstack.push(yyval._$); - newState = table[stack[stack.length - 2]][stack[stack.length - 1]]; - stack.push(newState); - break; - case 3: - return true; - } - } - return true; -} -}; -/* Jison generated lexer */ -var lexer = (function(){ -var lexer = ({EOF:1, -parseError:function parseError(str, hash) { - if (this.yy.parser) { - this.yy.parser.parseError(str, hash); - } else { - throw new Error(str); - } - }, -setInput:function (input) { - this._input = input; - this._more = this._less = this.done = false; - this.yylineno = this.yyleng = 0; - this.yytext = this.matched = this.match = ''; - this.conditionStack = ['INITIAL']; - this.yylloc = {first_line:1,first_column:0,last_line:1,last_column:0}; - if (this.options.ranges) this.yylloc.range = [0,0]; - this.offset = 0; - return this; - }, -input:function () { - var ch = this._input[0]; - this.yytext += ch; - this.yyleng++; - this.offset++; - this.match += ch; - this.matched += ch; - var lines = ch.match(/(?:\r\n?|\n).*/g); - if (lines) { - this.yylineno++; - this.yylloc.last_line++; - } else { - this.yylloc.last_column++; - } - if (this.options.ranges) this.yylloc.range[1]++; - - this._input = this._input.slice(1); - return ch; - }, -unput:function (ch) { - var len = ch.length; - var lines = ch.split(/(?:\r\n?|\n)/g); - - this._input = ch + this._input; - this.yytext = this.yytext.substr(0, this.yytext.length-len-1); - //this.yyleng -= len; - this.offset -= len; - var oldLines = this.match.split(/(?:\r\n?|\n)/g); - this.match = this.match.substr(0, this.match.length-1); - this.matched = this.matched.substr(0, this.matched.length-1); - - if (lines.length-1) this.yylineno -= lines.length-1; - var r = this.yylloc.range; - - this.yylloc = {first_line: this.yylloc.first_line, - last_line: this.yylineno+1, - first_column: this.yylloc.first_column, - last_column: lines ? - (lines.length === oldLines.length ? this.yylloc.first_column : 0) + oldLines[oldLines.length - lines.length].length - lines[0].length: - this.yylloc.first_column - len - }; - - if (this.options.ranges) { - this.yylloc.range = [r[0], r[0] + this.yyleng - len]; - } - return this; - }, -more:function () { - this._more = true; - return this; - }, -less:function (n) { - this.unput(this.match.slice(n)); - }, -pastInput:function () { - var past = this.matched.substr(0, this.matched.length - this.match.length); - return (past.length > 20 ? '...':'') + past.substr(-20).replace(/\n/g, ""); - }, -upcomingInput:function () { - var next = this.match; - if (next.length < 20) { - next += this._input.substr(0, 20-next.length); - } - return (next.substr(0,20)+(next.length > 20 ? '...':'')).replace(/\n/g, ""); - }, -showPosition:function () { - var pre = this.pastInput(); - var c = new Array(pre.length + 1).join("-"); - return pre + this.upcomingInput() + "\n" + c+"^"; - }, -next:function () { - if (this.done) { - return this.EOF; - } - if (!this._input) this.done = true; - - var token, - match, - tempMatch, - index, - col, - lines; - if (!this._more) { - this.yytext = ''; - this.match = ''; - } - var rules = this._currentRules(); - for (var i=0;i < rules.length; i++) { - tempMatch = this._input.match(this.rules[rules[i]]); - if (tempMatch && (!match || tempMatch[0].length > match[0].length)) { - match = tempMatch; - index = i; - if (!this.options.flex) break; - } - } - if (match) { - lines = match[0].match(/(?:\r\n?|\n).*/g); - if (lines) this.yylineno += lines.length; - this.yylloc = {first_line: this.yylloc.last_line, - last_line: this.yylineno+1, - first_column: this.yylloc.last_column, - last_column: lines ? lines[lines.length-1].length-lines[lines.length-1].match(/\r?\n?/)[0].length : this.yylloc.last_column + match[0].length}; - this.yytext += match[0]; - this.match += match[0]; - this.matches = match; - this.yyleng = this.yytext.length; - if (this.options.ranges) { - this.yylloc.range = [this.offset, this.offset += this.yyleng]; - } - this._more = false; - this._input = this._input.slice(match[0].length); - this.matched += match[0]; - token = this.performAction.call(this, this.yy, this, rules[index],this.conditionStack[this.conditionStack.length-1]); - if (this.done && this._input) this.done = false; - if (token) return token; - else return; - } - if (this._input === "") { - return this.EOF; - } else { - return this.parseError('Lexical error on line '+(this.yylineno+1)+'. Unrecognized text.\n'+this.showPosition(), - {text: "", token: null, line: this.yylineno}); - } - }, -lex:function lex() { - var r = this.next(); - if (typeof r !== 'undefined') { - return r; - } else { - return this.lex(); - } - }, -begin:function begin(condition) { - this.conditionStack.push(condition); - }, -popState:function popState() { - return this.conditionStack.pop(); - }, -_currentRules:function _currentRules() { - return this.conditions[this.conditionStack[this.conditionStack.length-1]].rules; - }, -topState:function () { - return this.conditionStack[this.conditionStack.length-2]; - }, -pushState:function begin(condition) { - this.begin(condition); - }}); -lexer.options = {}; -lexer.performAction = function anonymous(yy,yy_,$avoiding_name_collisions,YY_START) { - -var YYSTATE=YY_START -switch($avoiding_name_collisions) { -case 0: yy_.yytext = "\\"; return 14; -break; -case 1: - if(yy_.yytext.slice(-1) !== "\\") this.begin("mu"); - if(yy_.yytext.slice(-1) === "\\") yy_.yytext = yy_.yytext.substr(0,yy_.yyleng-1), this.begin("emu"); - if(yy_.yytext) return 14; - -break; -case 2: return 14; -break; -case 3: - if(yy_.yytext.slice(-1) !== "\\") this.popState(); - if(yy_.yytext.slice(-1) === "\\") yy_.yytext = yy_.yytext.substr(0,yy_.yyleng-1); - return 14; - -break; -case 4: yy_.yytext = yy_.yytext.substr(0, yy_.yyleng-4); this.popState(); return 15; -break; -case 5: return 25; -break; -case 6: return 16; -break; -case 7: return 20; -break; -case 8: return 19; -break; -case 9: return 19; -break; -case 10: return 23; -break; -case 11: return 22; -break; -case 12: this.popState(); this.begin('com'); -break; -case 13: yy_.yytext = yy_.yytext.substr(3,yy_.yyleng-5); this.popState(); return 15; -break; -case 14: return 22; -break; -case 15: return 37; -break; -case 16: return 36; -break; -case 17: return 36; -break; -case 18: return 40; -break; -case 19: /*ignore whitespace*/ -break; -case 20: this.popState(); return 24; -break; -case 21: this.popState(); return 18; -break; -case 22: yy_.yytext = yy_.yytext.substr(1,yy_.yyleng-2).replace(/\\"/g,'"'); return 31; -break; -case 23: yy_.yytext = yy_.yytext.substr(1,yy_.yyleng-2).replace(/\\'/g,"'"); return 31; -break; -case 24: return 38; -break; -case 25: return 33; -break; -case 26: return 33; -break; -case 27: return 32; -break; -case 28: return 36; -break; -case 29: yy_.yytext = yy_.yytext.substr(1, yy_.yyleng-2); return 36; -break; -case 30: return 'INVALID'; -break; -case 31: return 5; -break; -} -}; -lexer.rules = [/^(?:\\\\(?=(\{\{)))/,/^(?:[^\x00]*?(?=(\{\{)))/,/^(?:[^\x00]+)/,/^(?:[^\x00]{2,}?(?=(\{\{|$)))/,/^(?:[\s\S]*?--\}\})/,/^(?:\{\{>)/,/^(?:\{\{#)/,/^(?:\{\{\/)/,/^(?:\{\{\^)/,/^(?:\{\{\s*else\b)/,/^(?:\{\{\{)/,/^(?:\{\{&)/,/^(?:\{\{!--)/,/^(?:\{\{![\s\S]*?\}\})/,/^(?:\{\{)/,/^(?:=)/,/^(?:\.(?=[}\/ ]))/,/^(?:\.\.)/,/^(?:[\/.])/,/^(?:\s+)/,/^(?:\}\}\})/,/^(?:\}\})/,/^(?:"(\\["]|[^"])*")/,/^(?:'(\\[']|[^'])*')/,/^(?:@)/,/^(?:true(?=[}\s]))/,/^(?:false(?=[}\s]))/,/^(?:-?[0-9]+(?=[}\s]))/,/^(?:[^\s!"#%-,\.\/;->@\[-\^`\{-~]+(?=[=}\s\/.]))/,/^(?:\[[^\]]*\])/,/^(?:.)/,/^(?:$)/]; -lexer.conditions = {"mu":{"rules":[5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31],"inclusive":false},"emu":{"rules":[3],"inclusive":false},"com":{"rules":[4],"inclusive":false},"INITIAL":{"rules":[0,1,2,31],"inclusive":true}}; -return lexer;})() -parser.lexer = lexer; -function Parser () { this.yy = {}; }Parser.prototype = parser;parser.Parser = Parser; -return new Parser; -})();; -// lib/handlebars/compiler/base.js - -Handlebars.Parser = handlebars; - -Handlebars.parse = function(input) { - - // Just return if an already-compile AST was passed in. - if(input.constructor === Handlebars.AST.ProgramNode) { return input; } - - Handlebars.Parser.yy = Handlebars.AST; - return Handlebars.Parser.parse(input); -}; -; -// lib/handlebars/compiler/ast.js -Handlebars.AST = {}; - -Handlebars.AST.ProgramNode = function(statements, inverse) { - this.type = "program"; - this.statements = statements; - if(inverse) { this.inverse = new Handlebars.AST.ProgramNode(inverse); } -}; - -Handlebars.AST.MustacheNode = function(rawParams, hash, unescaped) { - this.type = "mustache"; - this.escaped = !unescaped; - this.hash = hash; - - var id = this.id = rawParams[0]; - var params = this.params = rawParams.slice(1); - - // a mustache is an eligible helper if: - // * its id is simple (a single part, not `this` or `..`) - var eligibleHelper = this.eligibleHelper = id.isSimple; - - // a mustache is definitely a helper if: - // * it is an eligible helper, and - // * it has at least one parameter or hash segment - this.isHelper = eligibleHelper && (params.length || hash); - - // if a mustache is an eligible helper but not a definite - // helper, it is ambiguous, and will be resolved in a later - // pass or at runtime. -}; - -Handlebars.AST.PartialNode = function(partialName, context) { - this.type = "partial"; - this.partialName = partialName; - this.context = context; -}; - -Handlebars.AST.BlockNode = function(mustache, program, inverse, close) { - if(mustache.id.original !== close.original) { - throw new Handlebars.Exception(mustache.id.original + " doesn't match " + close.original); - } - - this.type = "block"; - this.mustache = mustache; - this.program = program; - this.inverse = inverse; - - if (this.inverse && !this.program) { - this.isInverse = true; - } -}; - -Handlebars.AST.ContentNode = function(string) { - this.type = "content"; - this.string = string; -}; - -Handlebars.AST.HashNode = function(pairs) { - this.type = "hash"; - this.pairs = pairs; -}; - -Handlebars.AST.IdNode = function(parts) { - this.type = "ID"; - - var original = "", - dig = [], - depth = 0; - - for(var i=0,l=parts.length; i 0) { throw new Handlebars.Exception("Invalid path: " + original); } - else if (part === "..") { depth++; } - else { this.isScoped = true; } - } - else { dig.push(part); } - } - - this.original = original; - this.parts = dig; - this.string = dig.join('.'); - this.depth = depth; - - // an ID is simple if it only has one part, and that part is not - // `..` or `this`. - this.isSimple = parts.length === 1 && !this.isScoped && depth === 0; - - this.stringModeValue = this.string; -}; - -Handlebars.AST.PartialNameNode = function(name) { - this.type = "PARTIAL_NAME"; - this.name = name.original; -}; - -Handlebars.AST.DataNode = function(id) { - this.type = "DATA"; - this.id = id; -}; - -Handlebars.AST.StringNode = function(string) { - this.type = "STRING"; - this.original = - this.string = - this.stringModeValue = string; -}; - -Handlebars.AST.IntegerNode = function(integer) { - this.type = "INTEGER"; - this.original = - this.integer = integer; - this.stringModeValue = Number(integer); -}; - -Handlebars.AST.BooleanNode = function(bool) { - this.type = "BOOLEAN"; - this.bool = bool; - this.stringModeValue = bool === "true"; -}; - -Handlebars.AST.CommentNode = function(comment) { - this.type = "comment"; - this.comment = comment; -}; -; -// lib/handlebars/utils.js - -var errorProps = ['description', 'fileName', 'lineNumber', 'message', 'name', 'number', 'stack']; - -Handlebars.Exception = function(message) { - var tmp = Error.prototype.constructor.apply(this, arguments); - - // Unfortunately errors are not enumerable in Chrome (at least), so `for prop in tmp` doesn't work. - for (var idx = 0; idx < errorProps.length; idx++) { - this[errorProps[idx]] = tmp[errorProps[idx]]; - } -}; -Handlebars.Exception.prototype = new Error(); - -// Build out our basic SafeString type -Handlebars.SafeString = function(string) { - this.string = string; -}; -Handlebars.SafeString.prototype.toString = function() { - return this.string.toString(); -}; - -var escape = { - "&": "&", - "<": "<", - ">": ">", - '"': """, - "'": "'", - "`": "`" -}; - -var badChars = /[&<>"'`]/g; -var possible = /[&<>"'`]/; - -var escapeChar = function(chr) { - return escape[chr] || "&"; -}; - -Handlebars.Utils = { - extend: function(obj, value) { - for(var key in value) { - if(value.hasOwnProperty(key)) { - obj[key] = value[key]; - } - } - }, - - 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 === false) { - return ""; - } - - // Force a string conversion as this will be done by the append regardless and - // the regex test will do this transparently behind the scenes, causing issues if - // an object's to string has escaped characters in it. - string = string.toString(); - - if(!possible.test(string)) { return string; } - return string.replace(badChars, escapeChar); - }, - - isEmpty: function(value) { - if (!value && value !== 0) { - return true; - } else if(toString.call(value) === "[object Array]" && value.length === 0) { - return true; - } else { - return false; - } - } -}; -; -// lib/handlebars/compiler/compiler.js - -/*jshint eqnull:true*/ -var Compiler = Handlebars.Compiler = function() {}; - -// the foundHelper register will disambiguate helper lookup from finding a -// function in a context. This is necessary for mustache compatibility, which -// requires that context functions in blocks are evaluated by blockHelperMissing, -// and then proceed as if the resulting value was provided to blockHelperMissing. - -Compiler.prototype = { - compiler: Compiler, - - disassemble: function() { - var opcodes = this.opcodes, opcode, out = [], params, param; - - for (var i=0, l=opcodes.length; i 0) { - this.source[1] = this.source[1] + ", " + locals.join(", "); - } - - // Generate minimizer alias mappings - if (!this.isChild) { - for (var alias in this.context.aliases) { - if (this.context.aliases.hasOwnProperty(alias)) { - this.source[1] = this.source[1] + ', ' + alias + '=' + this.context.aliases[alias]; - } - } - } - - if (this.source[1]) { - this.source[1] = "var " + this.source[1].substring(2) + ";"; - } - - // Merge children - if (!this.isChild) { - this.source[1] += '\n' + this.context.programs.join('\n') + '\n'; - } - - if (!this.environment.isSimple) { - this.source.push("return buffer;"); - } - - var params = this.isChild ? ["depth0", "data"] : ["Handlebars", "depth0", "helpers", "partials", "data"]; - - for(var i=0, l=this.environment.depths.list.length; i this.stackVars.length) { this.stackVars.push("stack" + this.stackSlot); } - return this.topStackName(); - }, - topStackName: function() { - return "stack" + this.stackSlot; - }, - flushInline: function() { - var inlineStack = this.inlineStack; - if (inlineStack.length) { - this.inlineStack = []; - for (var i = 0, len = inlineStack.length; i < len; i++) { - var entry = inlineStack[i]; - if (entry instanceof Literal) { - this.compileStack.push(entry); - } else { - this.pushStack(entry); - } - } - } - }, - isInline: function() { - return this.inlineStack.length; - }, - - popStack: function(wrapped) { - var inline = this.isInline(), - item = (inline ? this.inlineStack : this.compileStack).pop(); - - if (!wrapped && (item instanceof Literal)) { - return item.value; - } else { - if (!inline) { - this.stackSlot--; - } - return item; - } - }, - - topStack: function(wrapped) { - var stack = (this.isInline() ? this.inlineStack : this.compileStack), - item = stack[stack.length - 1]; - - if (!wrapped && (item instanceof Literal)) { - return item.value; - } else { - return item; - } - }, - - quotedString: function(str) { - return '"' + str - .replace(/\\/g, '\\\\') - .replace(/"/g, '\\"') - .replace(/\n/g, '\\n') - .replace(/\r/g, '\\r') - .replace(/\u2028/g, '\\u2028') // Per Ecma-262 7.3 + 7.8.4 - .replace(/\u2029/g, '\\u2029') + '"'; - }, - - setupHelper: function(paramSize, name, missingParams) { - var params = []; - this.setupParams(paramSize, params, missingParams); - var foundHelper = this.nameLookup('helpers', name, 'helper'); - - return { - params: params, - name: foundHelper, - callParams: ["depth0"].concat(params).join(", "), - helperMissingParams: missingParams && ["depth0", this.quotedString(name)].concat(params).join(", ") - }; - }, - - // the params and contexts arguments are passed in arrays - // to fill in - setupParams: function(paramSize, params, useRegister) { - var options = [], contexts = [], types = [], param, inverse, program; - - options.push("hash:" + this.popStack()); - - inverse = this.popStack(); - program = this.popStack(); - - // Avoid setting fn and inverse if neither are set. This allows - // helpers to do a check for `if (options.fn)` - if (program || inverse) { - if (!program) { - this.context.aliases.self = "this"; - program = "self.noop"; - } - - if (!inverse) { - this.context.aliases.self = "this"; - inverse = "self.noop"; - } - - options.push("inverse:" + inverse); - options.push("fn:" + program); - } - - for(var i=0; i= 1.0.0' -}; - -Handlebars.helpers = {}; -Handlebars.partials = {}; - -var toString = Object.prototype.toString, - functionType = '[object Function]', - objectType = '[object Object]'; - -Handlebars.registerHelper = function(name, fn, inverse) { - if (toString.call(name) === objectType) { - if (inverse || fn) { throw new Handlebars.Exception('Arg not supported with multiple helpers'); } - Handlebars.Utils.extend(this.helpers, name); - } else { - if (inverse) { fn.not = inverse; } - this.helpers[name] = fn; - } -}; - -Handlebars.registerPartial = function(name, str) { - if (toString.call(name) === objectType) { - Handlebars.Utils.extend(this.partials, name); - } else { - this.partials[name] = str; - } -}; - -Handlebars.registerHelper('helperMissing', function(arg) { - if(arguments.length === 2) { - return undefined; - } else { - throw new Error("Missing helper: '" + arg + "'"); - } -}); - -Handlebars.registerHelper('blockHelperMissing', function(context, options) { - var inverse = options.inverse || function() {}, fn = options.fn; - - var type = toString.call(context); - - if(type === functionType) { context = context.call(this); } - - if(context === true) { - return fn(this); - } else if(context === false || context == null) { - return inverse(this); - } else if(type === "[object Array]") { - if(context.length > 0) { - return Handlebars.helpers.each(context, options); - } else { - return inverse(this); - } - } else { - return fn(context); - } -}); - -Handlebars.K = function() {}; - -Handlebars.createFrame = Object.create || function(object) { - Handlebars.K.prototype = object; - var obj = new Handlebars.K(); - Handlebars.K.prototype = null; - return obj; -}; - -Handlebars.logger = { - DEBUG: 0, INFO: 1, WARN: 2, ERROR: 3, level: 3, - - methodMap: {0: 'debug', 1: 'info', 2: 'warn', 3: 'error'}, - - // can be overridden in the host environment - log: function(level, obj) { - if (Handlebars.logger.level <= level) { - var method = Handlebars.logger.methodMap[level]; - if (typeof console !== 'undefined' && console[method]) { - console[method].call(console, obj); - } - } - } -}; - -Handlebars.log = function(level, obj) { Handlebars.logger.log(level, obj); }; - -Handlebars.registerHelper('each', function(context, options) { - var fn = options.fn, inverse = options.inverse; - var i = 0, ret = "", data; - - var type = toString.call(context); - if(type === functionType) { context = context.call(this); } - - if (options.data) { - data = Handlebars.createFrame(options.data); - } - - if(context && typeof context === 'object') { - if(context instanceof Array){ - for(var j = context.length; i": ">", - '"': """, - "'": "'", - "`": "`" -}; - -var badChars = /[&<>"'`]/g; -var possible = /[&<>"'`]/; - -var escapeChar = function(chr) { - return escape[chr] || "&"; -}; - -Handlebars.Utils = { - extend: function(obj, value) { - for(var key in value) { - if(value.hasOwnProperty(key)) { - obj[key] = value[key]; - } - } - }, - - 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 === false) { - return ""; - } - - // Force a string conversion as this will be done by the append regardless and - // the regex test will do this transparently behind the scenes, causing issues if - // an object's to string has escaped characters in it. - string = string.toString(); - - if(!possible.test(string)) { return string; } - return string.replace(badChars, escapeChar); - }, - - isEmpty: function(value) { - if (!value && value !== 0) { - return true; - } else if(toString.call(value) === "[object Array]" && value.length === 0) { - return true; - } else { - return false; - } - } -}; -; -// lib/handlebars/runtime.js - -Handlebars.VM = { - template: function(templateSpec) { - // Just add water - var container = { - escapeExpression: Handlebars.Utils.escapeExpression, - invokePartial: Handlebars.VM.invokePartial, - programs: [], - program: function(i, fn, data) { - var programWrapper = this.programs[i]; - if(data) { - programWrapper = Handlebars.VM.program(i, fn, data); - } else if (!programWrapper) { - programWrapper = this.programs[i] = Handlebars.VM.program(i, fn); - } - return programWrapper; - }, - merge: function(param, common) { - var ret = param || common; - - if (param && common) { - ret = {}; - Handlebars.Utils.extend(ret, common); - Handlebars.Utils.extend(ret, param); - } - return ret; - }, - programWithDepth: Handlebars.VM.programWithDepth, - noop: Handlebars.VM.noop, - compilerInfo: null - }; - - return function(context, options) { - options = options || {}; - var result = templateSpec.call(container, Handlebars, context, options.helpers, options.partials, options.data); - - var compilerInfo = container.compilerInfo || [], - compilerRevision = compilerInfo[0] || 1, - currentRevision = Handlebars.COMPILER_REVISION; - - if (compilerRevision !== currentRevision) { - if (compilerRevision < currentRevision) { - var runtimeVersions = Handlebars.REVISION_CHANGES[currentRevision], - compilerVersions = Handlebars.REVISION_CHANGES[compilerRevision]; - throw "Template was precompiled with an older version of Handlebars than the current runtime. "+ - "Please update your precompiler to a newer version ("+runtimeVersions+") or downgrade your runtime to an older version ("+compilerVersions+")."; - } else { - // Use the embedded version info since the runtime doesn't know about this revision yet - throw "Template was precompiled with a newer version of Handlebars than the current runtime. "+ - "Please update your runtime to a newer version ("+compilerInfo[1]+")."; - } - } - - return result; - }; - }, - - programWithDepth: function(i, fn, data /*, $depth */) { - var args = Array.prototype.slice.call(arguments, 3); - - var program = function(context, options) { - options = options || {}; - - return fn.apply(this, [context, options.data || data].concat(args)); - }; - program.program = i; - program.depth = args.length; - return program; - }, - program: function(i, fn, data) { - var program = function(context, options) { - options = options || {}; - - return fn(context, options.data || data); - }; - program.program = i; - program.depth = 0; - return program; - }, - noop: function() { return ""; }, - invokePartial: function(partial, name, context, helpers, partials, data) { - var options = { helpers: helpers, partials: partials, data: data }; - - if(partial === undefined) { - throw new Handlebars.Exception("The partial " + name + " could not be found"); - } else if(partial instanceof Function) { - return partial(context, options); - } else if (!Handlebars.compile) { - throw new Handlebars.Exception("The partial " + name + " could not be compiled when running in runtime-only mode"); - } else { - partials[name] = Handlebars.compile(partial, {data: data !== undefined}); - return partials[name](context, options); - } - } -}; - -Handlebars.template = Handlebars.VM.template; -; -// lib/handlebars/browser-suffix.js - if (typeof module === 'object' && module.exports) { - // CommonJS - module.exports = Handlebars; - - } else if (typeof define === "function" && define.amd) { - // AMD modules - define(function() { return Handlebars; }); - - } else { - // other, i.e. browser - this.Handlebars = Handlebars; - } -}).call(this); -; diff --git a/lib/handlebars/base.js b/lib/handlebars/base.js index 98496786d..875fe0379 100644 --- a/lib/handlebars/base.js +++ b/lib/handlebars/base.js @@ -15,7 +15,7 @@ export REVISION_CHANGES = { }; // TODO: Make this a class -export default function(helpers, partials) { +export function base(helpers, partials) { var exports = {}; diff --git a/lib/handlebars/compiler/base.js b/lib/handlebars/compiler/base.js index 525cd5bfa..8dd464de5 100644 --- a/lib/handlebars/compiler/base.js +++ b/lib/handlebars/compiler/base.js @@ -3,7 +3,7 @@ module AST from "handlebars/compiler/ast": export Parser = parser; -export function(input) { +export function parse(input) { // Just return if an already-compile AST was passed in. if(input.constructor === AST.ProgramNode) { return input; } diff --git a/lib/handlebars/compiler/compiler.js b/lib/handlebars/compiler/compiler.js index a21ccc0d9..6e051b7ba 100644 --- a/lib/handlebars/compiler/compiler.js +++ b/lib/handlebars/compiler/compiler.js @@ -4,8 +4,7 @@ import { template } from "handlebars/runtime"; /*jshint eqnull:true*/ -var Compiler = function() {}; -export default Compiler; +export function Compiler() {}; // the foundHelper register will disambiguate helper lookup from finding a // function in a context. This is necessary for mustache compatibility, which diff --git a/lib/handlebars/compiler/javascript-compiler.js b/lib/handlebars/compiler/javascript-compiler.js index 49660ffdd..f5d70eee1 100644 --- a/lib/handlebars/compiler/javascript-compiler.js +++ b/lib/handlebars/compiler/javascript-compiler.js @@ -5,8 +5,7 @@ function Literal(value) { }; -function JavaScriptCompiler() {}; -export default JavaScriptCompiler; +export function JavaScriptCompiler() {}; JavaScriptCompiler.prototype = { // PUBLIC API: You can override these methods in a subclass to provide diff --git a/lib/handlebars/compiler/printer.js b/lib/handlebars/compiler/printer.js index 6b72c2552..c6e08bbef 100644 --- a/lib/handlebars/compiler/printer.js +++ b/lib/handlebars/compiler/printer.js @@ -1,6 +1,6 @@ import Visitor from "handlebars/compiler/visitor"; -export print = function(ast) { +export function print(ast) { return new PrintVisitor().accept(ast); }; diff --git a/package.json b/package.json index 024ba0a82..b7ff47f3b 100644 --- a/package.json +++ b/package.json @@ -22,6 +22,6 @@ "grunt-contrib-connect": "~0.3.0", "grunt-contrib-watch": "~0.4.4", "grunt-hang": "~0.1.2", - "grunt-es6-module-transpiler": "~0.3.0" + "grunt-es6-module-transpiler": "~0.4.1" } } From f5c8484ea0c1ac1aec6994ad3b5928fb67b7aa62 Mon Sep 17 00:00:00 2001 From: Yehuda Katz Date: Wed, 24 Jul 2013 05:03:27 +0000 Subject: [PATCH 039/200] Further progress towards modularization. At this point, I have only 2 fails in the Node build, but I'm doing a bunch of manual stuff locally and still have a bunch of hacks. --- Rakefile | 6 +-- lib/handlebars.js | 18 ++++---- lib/handlebars/base.js | 26 ++++++----- lib/handlebars/compiler/ast.js | 34 +++++++------- lib/handlebars/compiler/base.js | 6 +-- lib/handlebars/compiler/compiler.js | 14 +++--- .../compiler/javascript-compiler.js | 17 +++++-- lib/handlebars/compiler/printer.js | 2 +- lib/handlebars/runtime.js | 45 +++++++++++++------ lib/handlebars/template.js | 3 ++ package.json | 6 ++- spec/env/node.js | 3 +- spec/env/runner.js | 13 +++--- spec/helpers.js | 14 +++--- spec/utils.js | 1 + 15 files changed, 129 insertions(+), 79 deletions(-) create mode 100644 lib/handlebars/template.js diff --git a/Rakefile b/Rakefile index 30187d743..eabeeb07e 100644 --- a/Rakefile +++ b/Rakefile @@ -5,7 +5,7 @@ def compile_parser system "./node_modules/.bin/jison -m js src/handlebars.yy src/handlebars.l" if $?.success? File.open("lib/handlebars/compiler/parser.js", "w") do |file| - file.puts File.read("src/parser-prefix.js") + File.read("handlebars.js") + File.read("src/parser-suffix.js") + file.puts File.read("handlebars.js") end sh "rm handlebars.js" @@ -39,11 +39,11 @@ def remove_exports(string) match ? match[1] : string end -minimal_deps = %w(browser-prefix base compiler/parser compiler/base compiler/ast utils compiler/compiler compiler/javascript-compiler runtime browser-suffix).map do |file| +minimal_deps = %w(base compiler/parser compiler/base compiler/ast utils compiler/compiler compiler/javascript-compiler runtime).map do |file| "lib/handlebars/#{file}.js" end -runtime_deps = %w(browser-prefix base utils runtime browser-suffix).map do |file| +runtime_deps = %w(base utils runtime).map do |file| "lib/handlebars/#{file}.js" end diff --git a/lib/handlebars.js b/lib/handlebars.js index e051aa5c0..b448658f2 100644 --- a/lib/handlebars.js +++ b/lib/handlebars.js @@ -1,21 +1,21 @@ -import handlebars from "handlebars/base"; +import { base as handlebars } from "./handlebars/base"; // Each of these augment the Handlebars object. No need to setup here. // (This is done to easily share code between commonjs and browse envs) -import { SafeString, Exception, extend, escapeExpression, isEmpty } from "handlebars/utils"; -import compiler from "handlebars/compiler"; -import runtime from "handlebars/runtime"; +import { SafeString, Exception, extend, escapeExpression, isEmpty } from "./handlebars/utils"; +import { compile, precompile } from "./handlebars/compiler/compiler"; +import { template } from "./handlebars/runtime"; // For compatibility and usage outside of module systems, make the Handlebars object a namespace var create = function() { - var hb = handlebars.create(); + var hb = handlebars(); hb.SafeString = SafeString; hb.Exception = Exception; - hb.utils = { extend: extend, escapeExpression: escapeExpression, isEmpty: isEmpty }; - - compiler.attach(hb); - runtime.attach(hb); + hb.Utils = { extend: extend, escapeExpression: escapeExpression, isEmpty: isEmpty }; + hb.compile = compile; + hb.precompile = precompile; + hb.template = template; return hb; }; diff --git a/lib/handlebars/base.js b/lib/handlebars/base.js index 875fe0379..0e3383eec 100644 --- a/lib/handlebars/base.js +++ b/lib/handlebars/base.js @@ -1,13 +1,13 @@ /*jshint eqnull: true */ -import { Exception, extend } from "handlebars/utils"; +import { Exception, extend } from "./utils"; var K = function() { return this; }; -export VERSION = "1.0.0"; -export COMPILER_REVISION = 4; +export var VERSION = "1.0.0"; +export var COMPILER_REVISION = 4; -export REVISION_CHANGES = { +export var REVISION_CHANGES = { 1: '<= 1.0.rc.2', // 1.0.rc.2 is actually rev2 but doesn't report it 2: '== 1.0.0-rc.3', 3: '== 1.0.0-rc.4', @@ -19,15 +19,17 @@ export function base(helpers, partials) { var exports = {}; + var helpers = helpers || {}; + var partials = partials || {}; - helpers = helpers || {}; - partials = partials || {}; + exports.helpers = helpers; + exports.partials = partials; var toString = Object.prototype.toString, functionType = '[object Function]', objectType = '[object Object]'; - exports.registerHelper(name, fn, inverse) { + exports.registerHelper = function(name, fn, inverse) { if (toString.call(name) === objectType) { if (inverse || fn) { throw new Exception('Arg not supported with multiple helpers'); } extend(helpers, name); @@ -37,7 +39,7 @@ export function base(helpers, partials) { } }; - exports.registerPartial(name, str) { + exports.registerPartial = function(name, str) { if (toString.call(name) === objectType) { extend(partials, name); } else { @@ -83,7 +85,7 @@ export function base(helpers, partials) { if(type === functionType) { context = context.call(this); } if (options.data) { - data = Handlebars.createFrame(options.data); + data = createFrame(options.data); } if(context && typeof context === 'object') { @@ -137,7 +139,7 @@ export function base(helpers, partials) { Handlebars.log(level, context); }); - return Handlebars; + return exports; } var levels = { @@ -146,7 +148,7 @@ var levels = { var methodMap = { 0: 'debug', 1: 'info', 2: 'warn', 3: 'error' }; -export logger = { +export var logger = { // can be overridden in the host environment log: function(level, obj) { if (Handlebars.logger.level <= level) { @@ -160,7 +162,7 @@ export logger = { export function log(level, obj) { logger.log(level, obj); }; -export createFrame = Object.create || function(object) { +export var createFrame = Object.create || function(object) { K.prototype = object; var obj = new K(); K.prototype = null; diff --git a/lib/handlebars/compiler/ast.js b/lib/handlebars/compiler/ast.js index fcf5c6e87..b61fa0e9f 100644 --- a/lib/handlebars/compiler/ast.js +++ b/lib/handlebars/compiler/ast.js @@ -1,12 +1,14 @@ -import { Exception } from "handlebars/utils"; +import { Exception } from "../utils"; -export function ProgramNode(statements, inverse) { +var exports = {}; + +exports.ProgramNode = function ProgramNode(statements, inverse) { this.type = "program"; this.statements = statements; - if(inverse) { this.inverse = new Handlebars.AST.ProgramNode(inverse); } + if(inverse) { this.inverse = new ProgramNode(inverse); } }; -export function MustacheNode(rawParams, hash, unescaped) { +exports.MustacheNode = function(rawParams, hash, unescaped) { this.type = "mustache"; this.escaped = !unescaped; this.hash = hash; @@ -28,13 +30,13 @@ export function MustacheNode(rawParams, hash, unescaped) { // pass or at runtime. }; -export function PartialNode(partialName, context) { +exports.PartialNode = function(partialName, context) { this.type = "partial"; this.partialName = partialName; this.context = context; }; -export function BlockNode(mustache, program, inverse, close) { +exports.BlockNode = function(mustache, program, inverse, close) { if(mustache.id.original !== close.original) { throw new Exception(mustache.id.original + " doesn't match " + close.original); } @@ -49,17 +51,17 @@ export function BlockNode(mustache, program, inverse, close) { } }; -export function ContentNode(string) { +exports.ContentNode = function(string) { this.type = "content"; this.string = string; }; -export function HashNode(pairs) { +exports.HashNode = function(pairs) { this.type = "hash"; this.pairs = pairs; }; -export function IdNode(part) { +exports.IdNode = function(parts) { this.type = "ID"; var original = "", @@ -90,37 +92,39 @@ export function IdNode(part) { this.stringModeValue = this.string; }; -export function PartialNameNode(name) { +exports.PartialNameNode = function(name) { this.type = "PARTIAL_NAME"; this.name = name.original; }; -export function DataNode(id) { +exports.DataNode = function(id) { this.type = "DATA"; this.id = id; }; -export function StringNode(string) { +exports.StringNode = function(string) { this.type = "STRING"; this.original = this.string = this.stringModeValue = string; }; -export function IntegerNode(integer) { +exports.IntegerNode = function(integer) { this.type = "INTEGER"; this.original = this.integer = integer; this.stringModeValue = Number(integer); }; -export function BooleanNode(bool) { +exports.BooleanNode = function(bool) { this.type = "BOOLEAN"; this.bool = bool; this.stringModeValue = bool === "true"; }; -export function CommentNode(comment) { +exports.CommentNode = function(comment) { this.type = "comment"; this.comment = comment; }; + +export default exports; diff --git a/lib/handlebars/compiler/base.js b/lib/handlebars/compiler/base.js index 8dd464de5..adbf4f292 100644 --- a/lib/handlebars/compiler/base.js +++ b/lib/handlebars/compiler/base.js @@ -1,7 +1,7 @@ -import { parser } from "handlebars/compiler/parser"; -module AST from "handlebars/compiler/ast": +import parser from "./parser"; +import AST from "./ast"; -export Parser = parser; +export var parser = parser; export function parse(input) { // Just return if an already-compile AST was passed in. diff --git a/lib/handlebars/compiler/compiler.js b/lib/handlebars/compiler/compiler.js index 6e051b7ba..b7796171a 100644 --- a/lib/handlebars/compiler/compiler.js +++ b/lib/handlebars/compiler/compiler.js @@ -1,6 +1,8 @@ -import { Exception } from "handlebars/utils"; -import parse from "handlebars/compiler/base"; -import { template } from "handlebars/runtime"; +import { Exception } from "../utils"; +import { template } from "../runtime"; +import { parse } from "./base"; +import { JavaScriptCompiler } from "./javascript-compiler"; +import AST from "./ast"; /*jshint eqnull:true*/ @@ -414,7 +416,7 @@ Compiler.prototype = { }; export function precompile(input, options) { - if (input == null || (typeof input !== 'string' && input.constructor !== Handlebars.AST.ProgramNode)) { + if (input == null || (typeof input !== 'string' && input.constructor !== AST.ProgramNode)) { throw new Exception("You must pass a string or Handlebars AST to Handlebars.precompile. You passed " + input); } @@ -428,8 +430,8 @@ export function precompile(input, options) { return new JavaScriptCompiler().compile(environment, options); }; -export function compile = function(input, options) { - if (input == null || (typeof input !== 'string' && input.constructor !== Handlebars.AST.ProgramNode)) { +export function compile(input, options) { + if (input == null || (typeof input !== 'string' && input.constructor !== AST.ProgramNode)) { throw new Exception("You must pass a string or Handlebars AST to Handlebars.compile. You passed " + input); } diff --git a/lib/handlebars/compiler/javascript-compiler.js b/lib/handlebars/compiler/javascript-compiler.js index f5d70eee1..a41643110 100644 --- a/lib/handlebars/compiler/javascript-compiler.js +++ b/lib/handlebars/compiler/javascript-compiler.js @@ -1,10 +1,9 @@ -import { COMPILER_REVISION, REVISION_CHANGES } from "handlebars/base"; +import { COMPILER_REVISION, REVISION_CHANGES } from "../base"; function Literal(value) { this.value = value; }; - export function JavaScriptCompiler() {}; JavaScriptCompiler.prototype = { @@ -44,7 +43,9 @@ JavaScriptCompiler.prototype = { this.environment = environment; this.options = options || {}; - Handlebars.log(Handlebars.logger.DEBUG, this.environment.disassemble() + "\n\n"); + // TODO: a module-compatible logger + // Handlebars.log(Handlebars.logger.DEBUG, this.environment.disassemble() + "\n\n"); + log(this.environment.disassemble() + "\n\n"); this.name = this.environment.name; this.isChild = !!context; @@ -166,7 +167,11 @@ JavaScriptCompiler.prototype = { return Function.apply(this, params); } else { var functionSource = 'function ' + (this.name || '') + '(' + params.join(',') + ') {\n ' + source + '}'; - Handlebars.log(Handlebars.logger.DEBUG, functionSource + "\n\n"); + + // TODO: a module-compatible logger + //Handlebars.log(Handlebars.logger.DEBUG, functionSource + "\n\n"); + log(functionSource + "\n\n"); + return functionSource; } }, @@ -841,3 +846,7 @@ JavaScriptCompiler.isValidJavaScriptVariableName = function(name) { } return false; }; + +function log(string) { + //console.log(string); +} diff --git a/lib/handlebars/compiler/printer.js b/lib/handlebars/compiler/printer.js index c6e08bbef..0280960c9 100644 --- a/lib/handlebars/compiler/printer.js +++ b/lib/handlebars/compiler/printer.js @@ -1,4 +1,4 @@ -import Visitor from "handlebars/compiler/visitor"; +import Visitor from "./visitor"; export function print(ast) { return new PrintVisitor().accept(ast); diff --git a/lib/handlebars/runtime.js b/lib/handlebars/runtime.js index 2585ef3e0..991bdfdcf 100644 --- a/lib/handlebars/runtime.js +++ b/lib/handlebars/runtime.js @@ -1,14 +1,33 @@ -import { escapeExpression, extend, Exceptions } from "handlebars/utils"; -import { COMPILER_REVISION, REVISION_CHANGES } from "handlebars/base"; +import { escapeExpression, extend, Exception } from "./utils"; +import { COMPILER_REVISION, REVISION_CHANGES } from "./base"; -// TODO: Deal with the fact that compile is necessary for on-the-fly partial -// compilation but won't be present for precompiled templates. +// TODO: Remove this line and break up compilePartial + +export function template(templateSpec, Hbars) { + // TODO: Make this less global + Hbars = Hbars || Handlebars; + + if (Hbars.compile) { + var invokePartialWrapper = function(partial, name, context, helpers, partials, data) { + var result = invokePartial.apply(this, arguments); + if (result) { return result; } + + var options = { helpers: helpers, partials: partials, data: data }; + partials[name] = Hbars.compile(partial, { data: data !== undefined }); + return partials[name](context, options); + }; + } else { + var invokePartialWrapper = function(partial, name, context, helpers, partials, data) { + var result = invokePartial.apply(this, arguments); + if (result) { return result; } + throw new Exception("The partial " + name + " could not be compiled when running in runtime-only mode"); + }; + } -export function template(templateSpec) { // Just add water var container = { escapeExpression: escapeExpression, - invokePartial: invokePartial, + invokePartial: invokePartialWrapper, programs: [], program: function(i, fn, data) { var programWrapper = this.programs[i]; @@ -36,8 +55,11 @@ export function template(templateSpec) { return function(context, options) { options = options || {}; + + Hbars = Hbars || require("handlebars"); + // TODO: Why does templateSpec require a reference to the global Handlebars? - var result = templateSpec.call(container, Handlebars, context, options.helpers, options.partials, options.data); + var result = templateSpec.call(container, Hbars, context, options.helpers, options.partials, options.data); var compilerInfo = container.compilerInfo || [], compilerRevision = compilerInfo[0] || 1, @@ -84,8 +106,6 @@ export function program(i, fn, data) { return program; } -export function noop() { return ""; } - export function invokePartial(partial, name, context, helpers, partials, data) { var options = { helpers: helpers, partials: partials, data: data }; @@ -93,10 +113,7 @@ export function invokePartial(partial, name, context, helpers, partials, data) { throw new Exception("The partial " + name + " could not be found"); } else if(partial instanceof Function) { return partial(context, options); - } else if (!compile) { - throw new Exception("The partial " + name + " could not be compiled when running in runtime-only mode"); - } else { - partials[name] = compile(partial, {data: data !== undefined}); - return partials[name](context, options); } } + +export function noop() { return ""; } diff --git a/lib/handlebars/template.js b/lib/handlebars/template.js new file mode 100644 index 000000000..befba7eed --- /dev/null +++ b/lib/handlebars/template.js @@ -0,0 +1,3 @@ +import { escapeExpression, extend } from "./utils"; +import { COMPILER_REVISION, REVISION_CHANGES } from "./base"; + diff --git a/package.json b/package.json index b7ff47f3b..5ee6544e8 100644 --- a/package.json +++ b/package.json @@ -22,6 +22,10 @@ "grunt-contrib-connect": "~0.3.0", "grunt-contrib-watch": "~0.4.4", "grunt-hang": "~0.1.2", - "grunt-es6-module-transpiler": "~0.4.1" + "grunt-es6-module-transpiler": "~0.4.1", + "es6-module-transpiler": "*", + "jison": "~0.3.0", + "mocha": "*", + "should": "~1.2.2" } } diff --git a/spec/env/node.js b/spec/env/node.js index fe34f9467..e5349a154 100644 --- a/spec/env/node.js +++ b/spec/env/node.js @@ -1,10 +1,11 @@ require('./common'); -global.Handlebars = require('../../lib/handlebars'); +global.Handlebars = require('../../zomg/lib/handlebars'); global.CompilerContext = { compile: function(template, options) { var templateSpec = Handlebars.precompile(template, options); + console.log(templateSpec); return Handlebars.template(eval('(' + templateSpec + ')')); }, compileWithPartial: function(template, options) { diff --git a/spec/env/runner.js b/spec/env/runner.js index 919f0aeef..0d0cef2e9 100644 --- a/spec/env/runner.js +++ b/spec/env/runner.js @@ -6,16 +6,19 @@ var errors = 0, testDir = path.dirname(__dirname), grep = process.argv[2]; +var files = [ testDir + "/basic.js" ]; + var files = fs.readdirSync(testDir) .filter(function(name) { return (/.*\.js$/).test(name); }) .map(function(name) { return testDir + '/' + name; }); run('./node', function() { - run('./browser', function() { - run('./runtime', function() { - process.exit(errors); - }); - }); + process.exit(errors); + //run('./browser', function() { + //run('./runtime', function() { + //process.exit(errors); + //}); + //}); }); diff --git a/spec/helpers.js b/spec/helpers.js index 9fb32e6c6..fad6e5aa2 100644 --- a/spec/helpers.js +++ b/spec/helpers.js @@ -176,11 +176,15 @@ describe('helpers', function() { var helpers = Handlebars.helpers; try { Handlebars.helpers = {}; - Handlebars.registerHelper({ - 'if': helpers['if'], - world: function() { return "world!"; }, - test_helper: function() { return 'found it!'; } - }); + Handlebars.registerHelper('if', helpers['if']); + Handlebars.registerHelper('world', function() { return "world!"; }); + Handlebars.registerHelper('test_helper', function() { return 'found it!'; }); + + //Handlebars.registerHelper({ + //'if': helpers['if'], + //world: function() { return "world!"; }, + //test_helper: function() { return 'found it!'; } + //}); shouldCompileTo( "{{test_helper}} {{#if cruel}}Goodbye {{cruel}} {{world}}!{{/if}}", diff --git a/spec/utils.js b/spec/utils.js index 135beb430..5eee69e06 100644 --- a/spec/utils.js +++ b/spec/utils.js @@ -1,4 +1,5 @@ /*global shouldCompileTo */ + describe('utils', function() { describe('#SafeString', function() { it("constructing a safestring from a string and checking its type", function() { From f9ed0d30859be79e78147c11ecd3f4c6381a097f Mon Sep 17 00:00:00 2001 From: kpdecker Date: Wed, 24 Jul 2013 21:57:30 -0500 Subject: [PATCH 040/200] Use arrow syntax for bnf --- dist/handlebars.js | 86 ++++++++++++++++++++++---------------------- src/handlebars.yy | 88 ++++++++++++++++++++++++---------------------- 2 files changed, 88 insertions(+), 86 deletions(-) diff --git a/dist/handlebars.js b/dist/handlebars.js index a63247396..4e287b8f3 100644 --- a/dist/handlebars.js +++ b/dist/handlebars.js @@ -197,106 +197,106 @@ var $0 = $$.length - 1; switch (yystate) { case 1: return $$[$0-1]; break; -case 2: this.$ = new yy.ProgramNode([], $$[$0]); +case 2:this.$ = new yy.ProgramNode([], $$[$0]); break; -case 3: this.$ = new yy.ProgramNode($$[$0-2], $$[$0]); +case 3:this.$ = new yy.ProgramNode($$[$0-2], $$[$0]); break; -case 4: this.$ = new yy.ProgramNode($$[$0-1], []); +case 4:this.$ = new yy.ProgramNode($$[$0-1], []); break; -case 5: this.$ = new yy.ProgramNode($$[$0]); +case 5:this.$ = new yy.ProgramNode($$[$0]); break; -case 6: this.$ = new yy.ProgramNode([], []); +case 6:this.$ = new yy.ProgramNode([], []); break; -case 7: this.$ = new yy.ProgramNode([]); +case 7:this.$ = new yy.ProgramNode([]); break; -case 8: this.$ = [$$[$0]]; +case 8:this.$ = [$$[$0]]; break; case 9: $$[$0-1].push($$[$0]); this.$ = $$[$0-1]; break; -case 10: this.$ = new yy.BlockNode($$[$0-2], $$[$0-1].inverse, $$[$0-1], $$[$0]); +case 10:this.$ = new yy.BlockNode($$[$0-2], $$[$0-1].inverse, $$[$0-1], $$[$0]); break; -case 11: this.$ = new yy.BlockNode($$[$0-2], $$[$0-1], $$[$0-1].inverse, $$[$0]); +case 11:this.$ = new yy.BlockNode($$[$0-2], $$[$0-1], $$[$0-1].inverse, $$[$0]); break; -case 12: this.$ = $$[$0]; +case 12:this.$ = $$[$0]; break; -case 13: this.$ = $$[$0]; +case 13:this.$ = $$[$0]; break; -case 14: this.$ = new yy.ContentNode($$[$0]); +case 14:this.$ = new yy.ContentNode($$[$0]); break; -case 15: this.$ = new yy.CommentNode($$[$0]); +case 15:this.$ = new yy.CommentNode($$[$0]); break; -case 16: this.$ = new yy.MustacheNode($$[$0-1][0], $$[$0-1][1]); +case 16:this.$ = new yy.MustacheNode($$[$0-1][0], $$[$0-1][1]); break; -case 17: this.$ = new yy.MustacheNode($$[$0-1][0], $$[$0-1][1]); +case 17:this.$ = new yy.MustacheNode($$[$0-1][0], $$[$0-1][1]); break; -case 18: this.$ = $$[$0-1]; +case 18:this.$ = $$[$0-1]; break; case 19: // Parsing out the '&' escape token at this level saves ~500 bytes after min due to the removal of one parser node. this.$ = new yy.MustacheNode($$[$0-1][0], $$[$0-1][1], $$[$0-2][2] === '&'); break; -case 20: this.$ = new yy.MustacheNode($$[$0-1][0], $$[$0-1][1], true); +case 20:this.$ = new yy.MustacheNode($$[$0-1][0], $$[$0-1][1], true); break; -case 21: this.$ = new yy.PartialNode($$[$0-1]); +case 21:this.$ = new yy.PartialNode($$[$0-1]); break; -case 22: this.$ = new yy.PartialNode($$[$0-2], $$[$0-1]); +case 22:this.$ = new yy.PartialNode($$[$0-2], $$[$0-1]); break; case 23: break; -case 24: this.$ = [[$$[$0-2]].concat($$[$0-1]), $$[$0]]; +case 24:this.$ = [[$$[$0-2]].concat($$[$0-1]), $$[$0]]; break; -case 25: this.$ = [[$$[$0-1]].concat($$[$0]), null]; +case 25:this.$ = [[$$[$0-1]].concat($$[$0]), null]; break; -case 26: this.$ = [[$$[$0-1]], $$[$0]]; +case 26:this.$ = [[$$[$0-1]], $$[$0]]; break; -case 27: this.$ = [[$$[$0]], null]; +case 27:this.$ = [[$$[$0]], null]; break; -case 28: this.$ = [[$$[$0]], null]; +case 28:this.$ = [[$$[$0]], null]; break; case 29: $$[$0-1].push($$[$0]); this.$ = $$[$0-1]; break; -case 30: this.$ = [$$[$0]]; +case 30:this.$ = [$$[$0]]; break; -case 31: this.$ = $$[$0]; +case 31:this.$ = $$[$0]; break; -case 32: this.$ = new yy.StringNode($$[$0]); +case 32:this.$ = new yy.StringNode($$[$0]); break; -case 33: this.$ = new yy.IntegerNode($$[$0]); +case 33:this.$ = new yy.IntegerNode($$[$0]); break; -case 34: this.$ = new yy.BooleanNode($$[$0]); +case 34:this.$ = new yy.BooleanNode($$[$0]); break; -case 35: this.$ = $$[$0]; +case 35:this.$ = $$[$0]; break; -case 36: this.$ = new yy.HashNode($$[$0]); +case 36:this.$ = new yy.HashNode($$[$0]); break; case 37: $$[$0-1].push($$[$0]); this.$ = $$[$0-1]; break; -case 38: this.$ = [$$[$0]]; +case 38:this.$ = [$$[$0]]; break; -case 39: this.$ = [$$[$0-2], $$[$0]]; +case 39:this.$ = [$$[$0-2], $$[$0]]; break; -case 40: this.$ = [$$[$0-2], new yy.StringNode($$[$0])]; +case 40:this.$ = [$$[$0-2], new yy.StringNode($$[$0])]; break; -case 41: this.$ = [$$[$0-2], new yy.IntegerNode($$[$0])]; +case 41:this.$ = [$$[$0-2], new yy.IntegerNode($$[$0])]; break; -case 42: this.$ = [$$[$0-2], new yy.BooleanNode($$[$0])]; +case 42:this.$ = [$$[$0-2], new yy.BooleanNode($$[$0])]; break; -case 43: this.$ = [$$[$0-2], $$[$0]]; +case 43:this.$ = [$$[$0-2], $$[$0]]; break; -case 44: this.$ = new yy.PartialNameNode($$[$0]); +case 44:this.$ = new yy.PartialNameNode($$[$0]); break; -case 45: this.$ = new yy.PartialNameNode(new yy.StringNode($$[$0])); +case 45:this.$ = new yy.PartialNameNode(new yy.StringNode($$[$0])); break; -case 46: this.$ = new yy.PartialNameNode(new yy.IntegerNode($$[$0])); +case 46:this.$ = new yy.PartialNameNode(new yy.IntegerNode($$[$0])); break; -case 47: this.$ = new yy.DataNode($$[$0]); +case 47:this.$ = new yy.DataNode($$[$0]); break; -case 48: this.$ = new yy.IdNode($$[$0]); +case 48:this.$ = new yy.IdNode($$[$0]); break; case 49: $$[$0-2].push({part: $$[$0], separator: $$[$0-1]}); this.$ = $$[$0-2]; break; -case 50: this.$ = [{part: $$[$0]}]; +case 50:this.$ = [{part: $$[$0]}]; break; } }, diff --git a/src/handlebars.yy b/src/handlebars.yy index 56b3b7040..2fec53ddf 100644 --- a/src/handlebars.yy +++ b/src/handlebars.yy @@ -1,5 +1,7 @@ %start root +%ebnf + %% root @@ -7,38 +9,38 @@ root ; program - : simpleInverse statements { $$ = new yy.ProgramNode([], $2); } - | statements simpleInverse statements { $$ = new yy.ProgramNode($1, $3); } - | statements simpleInverse { $$ = new yy.ProgramNode($1, []); } - | statements { $$ = new yy.ProgramNode($1); } - | simpleInverse { $$ = new yy.ProgramNode([], []); } - | "" { $$ = new yy.ProgramNode([]); } + : simpleInverse statements -> new yy.ProgramNode([], $2) + | statements simpleInverse statements -> new yy.ProgramNode($1, $3) + | statements simpleInverse -> new yy.ProgramNode($1, []) + | statements -> new yy.ProgramNode($1) + | simpleInverse -> new yy.ProgramNode([], []) + | "" -> new yy.ProgramNode([]) ; statements - : statement { $$ = [$1]; } + : statement -> [$1] | statements statement { $1.push($2); $$ = $1; } ; statement - : openInverse program closeBlock { $$ = new yy.BlockNode($1, $2.inverse, $2, $3); } - | openBlock program closeBlock { $$ = new yy.BlockNode($1, $2, $2.inverse, $3); } - | mustache { $$ = $1; } - | partial { $$ = $1; } - | CONTENT { $$ = new yy.ContentNode($1); } - | COMMENT { $$ = new yy.CommentNode($1); } + : openInverse program closeBlock -> new yy.BlockNode($1, $2.inverse, $2, $3) + | openBlock program closeBlock -> new yy.BlockNode($1, $2, $2.inverse, $3) + | mustache -> $1 + | partial -> $1 + | CONTENT -> new yy.ContentNode($1) + | COMMENT -> new yy.CommentNode($1) ; openBlock - : OPEN_BLOCK inMustache CLOSE { $$ = new yy.MustacheNode($2[0], $2[1]); } + : OPEN_BLOCK inMustache CLOSE -> new yy.MustacheNode($2[0], $2[1]) ; openInverse - : OPEN_INVERSE inMustache CLOSE { $$ = new yy.MustacheNode($2[0], $2[1]); } + : OPEN_INVERSE inMustache CLOSE -> new yy.MustacheNode($2[0], $2[1]) ; closeBlock - : OPEN_ENDBLOCK path CLOSE { $$ = $2; } + : OPEN_ENDBLOCK path CLOSE -> $2 ; mustache @@ -46,13 +48,13 @@ mustache // Parsing out the '&' escape token at this level saves ~500 bytes after min due to the removal of one parser node. $$ = new yy.MustacheNode($2[0], $2[1], $1[2] === '&'); } - | OPEN_UNESCAPED inMustache CLOSE_UNESCAPED { $$ = new yy.MustacheNode($2[0], $2[1], true); } + | OPEN_UNESCAPED inMustache CLOSE_UNESCAPED -> new yy.MustacheNode($2[0], $2[1], true) ; partial - : OPEN_PARTIAL partialName CLOSE { $$ = new yy.PartialNode($2); } - | OPEN_PARTIAL partialName 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 @@ -60,59 +62,59 @@ simpleInverse ; inMustache - : path params hash { $$ = [[$1].concat($2), $3]; } - | path params { $$ = [[$1].concat($2), null]; } - | path hash { $$ = [[$1], $2]; } - | path { $$ = [[$1], null]; } - | dataName { $$ = [[$1], null]; } + : path params hash -> [[$1].concat($2), $3] + | path params -> [[$1].concat($2), null] + | path hash -> [[$1], $2] + | path -> [[$1], null] + | dataName -> [[$1], null] ; params : params param { $1.push($2); $$ = $1; } - | param { $$ = [$1]; } + | param -> [$1] ; param - : path { $$ = $1; } - | STRING { $$ = new yy.StringNode($1); } - | INTEGER { $$ = new yy.IntegerNode($1); } - | BOOLEAN { $$ = new yy.BooleanNode($1); } - | dataName { $$ = $1; } + : path -> $1 + | STRING -> new yy.StringNode($1) + | INTEGER -> new yy.IntegerNode($1) + | BOOLEAN -> new yy.BooleanNode($1) + | dataName -> $1 ; hash - : hashSegments { $$ = new yy.HashNode($1); } + : hashSegments -> new yy.HashNode($1) ; hashSegments : hashSegments hashSegment { $1.push($2); $$ = $1; } - | hashSegment { $$ = [$1]; } + | hashSegment -> [$1] ; hashSegment - : ID EQUALS path { $$ = [$1, $3]; } - | ID EQUALS STRING { $$ = [$1, new yy.StringNode($3)]; } - | ID EQUALS INTEGER { $$ = [$1, new yy.IntegerNode($3)]; } - | ID EQUALS BOOLEAN { $$ = [$1, new yy.BooleanNode($3)]; } - | ID EQUALS dataName { $$ = [$1, $3]; } + : ID EQUALS path -> [$1, $3] + | ID EQUALS STRING -> [$1, new yy.StringNode($3)] + | ID EQUALS INTEGER -> [$1, new yy.IntegerNode($3)] + | ID EQUALS BOOLEAN -> [$1, new yy.BooleanNode($3)] + | ID EQUALS dataName -> [$1, $3] ; partialName - : path { $$ = new yy.PartialNameNode($1); } - | STRING { $$ = new yy.PartialNameNode(new yy.StringNode($1)); } - | INTEGER { $$ = new yy.PartialNameNode(new yy.IntegerNode($1)); } + : path -> new yy.PartialNameNode($1) + | STRING -> new yy.PartialNameNode(new yy.StringNode($1)) + | INTEGER -> new yy.PartialNameNode(new yy.IntegerNode($1)) ; dataName - : DATA path { $$ = new yy.DataNode($2); } + : DATA path -> new yy.DataNode($2) ; path - : pathSegments { $$ = new yy.IdNode($1); } + : pathSegments -> new yy.IdNode($1) ; pathSegments : pathSegments SEP ID { $1.push({part: $3, separator: $2}); $$ = $1; } - | ID { $$ = [{part: $1}]; } + | ID -> [{part: $1}] ; From 199f3984c6c07f731af2e4cbe2a42b62d98c6e46 Mon Sep 17 00:00:00 2001 From: kpdecker Date: Wed, 24 Jul 2013 23:35:43 -0500 Subject: [PATCH 041/200] Use param production for hash value --- dist/handlebars.js | 26 +++++++++----------------- src/handlebars.yy | 6 +----- 2 files changed, 10 insertions(+), 22 deletions(-) diff --git a/dist/handlebars.js b/dist/handlebars.js index 4e287b8f3..bd3f10bc9 100644 --- a/dist/handlebars.js +++ b/dist/handlebars.js @@ -190,7 +190,7 @@ var parser = {trace: function trace() { }, yy: {}, symbols_: {"error":2,"root":3,"program":4,"EOF":5,"simpleInverse":6,"statements":7,"statement":8,"openInverse":9,"closeBlock":10,"openBlock":11,"mustache":12,"partial":13,"CONTENT":14,"COMMENT":15,"OPEN_BLOCK":16,"inMustache":17,"CLOSE":18,"OPEN_INVERSE":19,"OPEN_ENDBLOCK":20,"path":21,"OPEN":22,"OPEN_UNESCAPED":23,"CLOSE_UNESCAPED":24,"OPEN_PARTIAL":25,"partialName":26,"params":27,"hash":28,"dataName":29,"param":30,"STRING":31,"INTEGER":32,"BOOLEAN":33,"hashSegments":34,"hashSegment":35,"ID":36,"EQUALS":37,"DATA":38,"pathSegments":39,"SEP":40,"$accept":0,"$end":1}, terminals_: {2:"error",5:"EOF",14:"CONTENT",15:"COMMENT",16:"OPEN_BLOCK",18:"CLOSE",19:"OPEN_INVERSE",20:"OPEN_ENDBLOCK",22:"OPEN",23:"OPEN_UNESCAPED",24:"CLOSE_UNESCAPED",25:"OPEN_PARTIAL",31:"STRING",32:"INTEGER",33:"BOOLEAN",36:"ID",37:"EQUALS",38:"DATA",40:"SEP"}, -productions_: [0,[3,2],[4,2],[4,3],[4,2],[4,1],[4,1],[4,0],[7,1],[7,2],[8,3],[8,3],[8,1],[8,1],[8,1],[8,1],[11,3],[9,3],[10,3],[12,3],[12,3],[13,3],[13,4],[6,2],[17,3],[17,2],[17,2],[17,1],[17,1],[27,2],[27,1],[30,1],[30,1],[30,1],[30,1],[30,1],[28,1],[34,2],[34,1],[35,3],[35,3],[35,3],[35,3],[35,3],[26,1],[26,1],[26,1],[29,2],[21,1],[39,3],[39,1]], +productions_: [0,[3,2],[4,2],[4,3],[4,2],[4,1],[4,1],[4,0],[7,1],[7,2],[8,3],[8,3],[8,1],[8,1],[8,1],[8,1],[11,3],[9,3],[10,3],[12,3],[12,3],[13,3],[13,4],[6,2],[17,3],[17,2],[17,2],[17,1],[17,1],[27,2],[27,1],[30,1],[30,1],[30,1],[30,1],[30,1],[28,1],[34,2],[34,1],[35,3],[26,1],[26,1],[26,1],[29,2],[21,1],[39,3],[39,1]], performAction: function anonymous(yytext,yyleng,yylineno,yy,yystate,$$,_$) { var $0 = $$.length - 1; @@ -276,31 +276,23 @@ case 38:this.$ = [$$[$0]]; break; case 39:this.$ = [$$[$0-2], $$[$0]]; break; -case 40:this.$ = [$$[$0-2], new yy.StringNode($$[$0])]; +case 40:this.$ = new yy.PartialNameNode($$[$0]); break; -case 41:this.$ = [$$[$0-2], new yy.IntegerNode($$[$0])]; +case 41:this.$ = new yy.PartialNameNode(new yy.StringNode($$[$0])); break; -case 42:this.$ = [$$[$0-2], new yy.BooleanNode($$[$0])]; +case 42:this.$ = new yy.PartialNameNode(new yy.IntegerNode($$[$0])); break; -case 43:this.$ = [$$[$0-2], $$[$0]]; +case 43:this.$ = new yy.DataNode($$[$0]); break; -case 44:this.$ = new yy.PartialNameNode($$[$0]); +case 44:this.$ = new yy.IdNode($$[$0]); break; -case 45:this.$ = new yy.PartialNameNode(new yy.StringNode($$[$0])); +case 45: $$[$0-2].push({part: $$[$0], separator: $$[$0-1]}); this.$ = $$[$0-2]; break; -case 46:this.$ = new yy.PartialNameNode(new yy.IntegerNode($$[$0])); -break; -case 47:this.$ = new yy.DataNode($$[$0]); -break; -case 48:this.$ = new yy.IdNode($$[$0]); -break; -case 49: $$[$0-2].push({part: $$[$0], separator: $$[$0-1]}); this.$ = $$[$0-2]; -break; -case 50:this.$ = [{part: $$[$0]}]; +case 46:this.$ = [{part: $$[$0]}]; break; } }, -table: [{3:1,4:2,5:[2,7],6:3,7:4,8:6,9:7,11:8,12:9,13:10,14:[1,11],15:[1,12],16:[1,13],19:[1,5],22:[1,14],23:[1,15],25:[1,16]},{1:[3]},{5:[1,17]},{5:[2,6],7:18,8:6,9:7,11:8,12:9,13:10,14:[1,11],15:[1,12],16:[1,13],19:[1,19],20:[2,6],22:[1,14],23:[1,15],25:[1,16]},{5:[2,5],6:20,8:21,9:7,11:8,12:9,13:10,14:[1,11],15:[1,12],16:[1,13],19:[1,5],20:[2,5],22:[1,14],23:[1,15],25:[1,16]},{17:23,18:[1,22],21:24,29:25,36:[1,28],38:[1,27],39:26},{5:[2,8],14:[2,8],15:[2,8],16:[2,8],19:[2,8],20:[2,8],22:[2,8],23:[2,8],25:[2,8]},{4:29,6:3,7:4,8:6,9:7,11:8,12:9,13:10,14:[1,11],15:[1,12],16:[1,13],19:[1,5],20:[2,7],22:[1,14],23:[1,15],25:[1,16]},{4:30,6:3,7:4,8:6,9:7,11:8,12:9,13:10,14:[1,11],15:[1,12],16:[1,13],19:[1,5],20:[2,7],22:[1,14],23:[1,15],25:[1,16]},{5:[2,12],14:[2,12],15:[2,12],16:[2,12],19:[2,12],20:[2,12],22:[2,12],23:[2,12],25:[2,12]},{5:[2,13],14:[2,13],15:[2,13],16:[2,13],19:[2,13],20:[2,13],22:[2,13],23:[2,13],25:[2,13]},{5:[2,14],14:[2,14],15:[2,14],16:[2,14],19:[2,14],20:[2,14],22:[2,14],23:[2,14],25:[2,14]},{5:[2,15],14:[2,15],15:[2,15],16:[2,15],19:[2,15],20:[2,15],22:[2,15],23:[2,15],25:[2,15]},{17:31,21:24,29:25,36:[1,28],38:[1,27],39:26},{17:32,21:24,29:25,36:[1,28],38:[1,27],39:26},{17:33,21:24,29:25,36:[1,28],38:[1,27],39:26},{21:35,26:34,31:[1,36],32:[1,37],36:[1,28],39:26},{1:[2,1]},{5:[2,2],8:21,9:7,11:8,12:9,13:10,14:[1,11],15:[1,12],16:[1,13],19:[1,19],20:[2,2],22:[1,14],23:[1,15],25:[1,16]},{17:23,21:24,29:25,36:[1,28],38:[1,27],39:26},{5:[2,4],7:38,8:6,9:7,11:8,12:9,13:10,14:[1,11],15:[1,12],16:[1,13],19:[1,19],20:[2,4],22:[1,14],23:[1,15],25:[1,16]},{5:[2,9],14:[2,9],15:[2,9],16:[2,9],19:[2,9],20:[2,9],22:[2,9],23:[2,9],25:[2,9]},{5:[2,23],14:[2,23],15:[2,23],16:[2,23],19:[2,23],20:[2,23],22:[2,23],23:[2,23],25:[2,23]},{18:[1,39]},{18:[2,27],21:44,24:[2,27],27:40,28:41,29:48,30:42,31:[1,45],32:[1,46],33:[1,47],34:43,35:49,36:[1,50],38:[1,27],39:26},{18:[2,28],24:[2,28]},{18:[2,48],24:[2,48],31:[2,48],32:[2,48],33:[2,48],36:[2,48],38:[2,48],40:[1,51]},{21:52,36:[1,28],39:26},{18:[2,50],24:[2,50],31:[2,50],32:[2,50],33:[2,50],36:[2,50],38:[2,50],40:[2,50]},{10:53,20:[1,54]},{10:55,20:[1,54]},{18:[1,56]},{18:[1,57]},{24:[1,58]},{18:[1,59],21:60,36:[1,28],39:26},{18:[2,44],36:[2,44]},{18:[2,45],36:[2,45]},{18:[2,46],36:[2,46]},{5:[2,3],8:21,9:7,11:8,12:9,13:10,14:[1,11],15:[1,12],16:[1,13],19:[1,19],20:[2,3],22:[1,14],23:[1,15],25:[1,16]},{14:[2,17],15:[2,17],16:[2,17],19:[2,17],20:[2,17],22:[2,17],23:[2,17],25:[2,17]},{18:[2,25],21:44,24:[2,25],28:61,29:48,30:62,31:[1,45],32:[1,46],33:[1,47],34:43,35:49,36:[1,50],38:[1,27],39:26},{18:[2,26],24:[2,26]},{18:[2,30],24:[2,30],31:[2,30],32:[2,30],33:[2,30],36:[2,30],38:[2,30]},{18:[2,36],24:[2,36],35:63,36:[1,64]},{18:[2,31],24:[2,31],31:[2,31],32:[2,31],33:[2,31],36:[2,31],38:[2,31]},{18:[2,32],24:[2,32],31:[2,32],32:[2,32],33:[2,32],36:[2,32],38:[2,32]},{18:[2,33],24:[2,33],31:[2,33],32:[2,33],33:[2,33],36:[2,33],38:[2,33]},{18:[2,34],24:[2,34],31:[2,34],32:[2,34],33:[2,34],36:[2,34],38:[2,34]},{18:[2,35],24:[2,35],31:[2,35],32:[2,35],33:[2,35],36:[2,35],38:[2,35]},{18:[2,38],24:[2,38],36:[2,38]},{18:[2,50],24:[2,50],31:[2,50],32:[2,50],33:[2,50],36:[2,50],37:[1,65],38:[2,50],40:[2,50]},{36:[1,66]},{18:[2,47],24:[2,47],31:[2,47],32:[2,47],33:[2,47],36:[2,47],38:[2,47]},{5:[2,10],14:[2,10],15:[2,10],16:[2,10],19:[2,10],20:[2,10],22:[2,10],23:[2,10],25:[2,10]},{21:67,36:[1,28],39:26},{5:[2,11],14:[2,11],15:[2,11],16:[2,11],19:[2,11],20:[2,11],22:[2,11],23:[2,11],25:[2,11]},{14:[2,16],15:[2,16],16:[2,16],19:[2,16],20:[2,16],22:[2,16],23:[2,16],25:[2,16]},{5:[2,19],14:[2,19],15:[2,19],16:[2,19],19:[2,19],20:[2,19],22:[2,19],23:[2,19],25:[2,19]},{5:[2,20],14:[2,20],15:[2,20],16:[2,20],19:[2,20],20:[2,20],22:[2,20],23:[2,20],25:[2,20]},{5:[2,21],14:[2,21],15:[2,21],16:[2,21],19:[2,21],20:[2,21],22:[2,21],23:[2,21],25:[2,21]},{18:[1,68]},{18:[2,24],24:[2,24]},{18:[2,29],24:[2,29],31:[2,29],32:[2,29],33:[2,29],36:[2,29],38:[2,29]},{18:[2,37],24:[2,37],36:[2,37]},{37:[1,65]},{21:69,29:73,31:[1,70],32:[1,71],33:[1,72],36:[1,28],38:[1,27],39:26},{18:[2,49],24:[2,49],31:[2,49],32:[2,49],33:[2,49],36:[2,49],38:[2,49],40:[2,49]},{18:[1,74]},{5:[2,22],14:[2,22],15:[2,22],16:[2,22],19:[2,22],20:[2,22],22:[2,22],23:[2,22],25:[2,22]},{18:[2,39],24:[2,39],36:[2,39]},{18:[2,40],24:[2,40],36:[2,40]},{18:[2,41],24:[2,41],36:[2,41]},{18:[2,42],24:[2,42],36:[2,42]},{18:[2,43],24:[2,43],36:[2,43]},{5:[2,18],14:[2,18],15:[2,18],16:[2,18],19:[2,18],20:[2,18],22:[2,18],23:[2,18],25:[2,18]}], +table: [{3:1,4:2,5:[2,7],6:3,7:4,8:6,9:7,11:8,12:9,13:10,14:[1,11],15:[1,12],16:[1,13],19:[1,5],22:[1,14],23:[1,15],25:[1,16]},{1:[3]},{5:[1,17]},{5:[2,6],7:18,8:6,9:7,11:8,12:9,13:10,14:[1,11],15:[1,12],16:[1,13],19:[1,19],20:[2,6],22:[1,14],23:[1,15],25:[1,16]},{5:[2,5],6:20,8:21,9:7,11:8,12:9,13:10,14:[1,11],15:[1,12],16:[1,13],19:[1,5],20:[2,5],22:[1,14],23:[1,15],25:[1,16]},{17:23,18:[1,22],21:24,29:25,36:[1,28],38:[1,27],39:26},{5:[2,8],14:[2,8],15:[2,8],16:[2,8],19:[2,8],20:[2,8],22:[2,8],23:[2,8],25:[2,8]},{4:29,6:3,7:4,8:6,9:7,11:8,12:9,13:10,14:[1,11],15:[1,12],16:[1,13],19:[1,5],20:[2,7],22:[1,14],23:[1,15],25:[1,16]},{4:30,6:3,7:4,8:6,9:7,11:8,12:9,13:10,14:[1,11],15:[1,12],16:[1,13],19:[1,5],20:[2,7],22:[1,14],23:[1,15],25:[1,16]},{5:[2,12],14:[2,12],15:[2,12],16:[2,12],19:[2,12],20:[2,12],22:[2,12],23:[2,12],25:[2,12]},{5:[2,13],14:[2,13],15:[2,13],16:[2,13],19:[2,13],20:[2,13],22:[2,13],23:[2,13],25:[2,13]},{5:[2,14],14:[2,14],15:[2,14],16:[2,14],19:[2,14],20:[2,14],22:[2,14],23:[2,14],25:[2,14]},{5:[2,15],14:[2,15],15:[2,15],16:[2,15],19:[2,15],20:[2,15],22:[2,15],23:[2,15],25:[2,15]},{17:31,21:24,29:25,36:[1,28],38:[1,27],39:26},{17:32,21:24,29:25,36:[1,28],38:[1,27],39:26},{17:33,21:24,29:25,36:[1,28],38:[1,27],39:26},{21:35,26:34,31:[1,36],32:[1,37],36:[1,28],39:26},{1:[2,1]},{5:[2,2],8:21,9:7,11:8,12:9,13:10,14:[1,11],15:[1,12],16:[1,13],19:[1,19],20:[2,2],22:[1,14],23:[1,15],25:[1,16]},{17:23,21:24,29:25,36:[1,28],38:[1,27],39:26},{5:[2,4],7:38,8:6,9:7,11:8,12:9,13:10,14:[1,11],15:[1,12],16:[1,13],19:[1,19],20:[2,4],22:[1,14],23:[1,15],25:[1,16]},{5:[2,9],14:[2,9],15:[2,9],16:[2,9],19:[2,9],20:[2,9],22:[2,9],23:[2,9],25:[2,9]},{5:[2,23],14:[2,23],15:[2,23],16:[2,23],19:[2,23],20:[2,23],22:[2,23],23:[2,23],25:[2,23]},{18:[1,39]},{18:[2,27],21:44,24:[2,27],27:40,28:41,29:48,30:42,31:[1,45],32:[1,46],33:[1,47],34:43,35:49,36:[1,50],38:[1,27],39:26},{18:[2,28],24:[2,28]},{18:[2,44],24:[2,44],31:[2,44],32:[2,44],33:[2,44],36:[2,44],38:[2,44],40:[1,51]},{21:52,36:[1,28],39:26},{18:[2,46],24:[2,46],31:[2,46],32:[2,46],33:[2,46],36:[2,46],38:[2,46],40:[2,46]},{10:53,20:[1,54]},{10:55,20:[1,54]},{18:[1,56]},{18:[1,57]},{24:[1,58]},{18:[1,59],21:60,36:[1,28],39:26},{18:[2,40],36:[2,40]},{18:[2,41],36:[2,41]},{18:[2,42],36:[2,42]},{5:[2,3],8:21,9:7,11:8,12:9,13:10,14:[1,11],15:[1,12],16:[1,13],19:[1,19],20:[2,3],22:[1,14],23:[1,15],25:[1,16]},{14:[2,17],15:[2,17],16:[2,17],19:[2,17],20:[2,17],22:[2,17],23:[2,17],25:[2,17]},{18:[2,25],21:44,24:[2,25],28:61,29:48,30:62,31:[1,45],32:[1,46],33:[1,47],34:43,35:49,36:[1,50],38:[1,27],39:26},{18:[2,26],24:[2,26]},{18:[2,30],24:[2,30],31:[2,30],32:[2,30],33:[2,30],36:[2,30],38:[2,30]},{18:[2,36],24:[2,36],35:63,36:[1,64]},{18:[2,31],24:[2,31],31:[2,31],32:[2,31],33:[2,31],36:[2,31],38:[2,31]},{18:[2,32],24:[2,32],31:[2,32],32:[2,32],33:[2,32],36:[2,32],38:[2,32]},{18:[2,33],24:[2,33],31:[2,33],32:[2,33],33:[2,33],36:[2,33],38:[2,33]},{18:[2,34],24:[2,34],31:[2,34],32:[2,34],33:[2,34],36:[2,34],38:[2,34]},{18:[2,35],24:[2,35],31:[2,35],32:[2,35],33:[2,35],36:[2,35],38:[2,35]},{18:[2,38],24:[2,38],36:[2,38]},{18:[2,46],24:[2,46],31:[2,46],32:[2,46],33:[2,46],36:[2,46],37:[1,65],38:[2,46],40:[2,46]},{36:[1,66]},{18:[2,43],24:[2,43],31:[2,43],32:[2,43],33:[2,43],36:[2,43],38:[2,43]},{5:[2,10],14:[2,10],15:[2,10],16:[2,10],19:[2,10],20:[2,10],22:[2,10],23:[2,10],25:[2,10]},{21:67,36:[1,28],39:26},{5:[2,11],14:[2,11],15:[2,11],16:[2,11],19:[2,11],20:[2,11],22:[2,11],23:[2,11],25:[2,11]},{14:[2,16],15:[2,16],16:[2,16],19:[2,16],20:[2,16],22:[2,16],23:[2,16],25:[2,16]},{5:[2,19],14:[2,19],15:[2,19],16:[2,19],19:[2,19],20:[2,19],22:[2,19],23:[2,19],25:[2,19]},{5:[2,20],14:[2,20],15:[2,20],16:[2,20],19:[2,20],20:[2,20],22:[2,20],23:[2,20],25:[2,20]},{5:[2,21],14:[2,21],15:[2,21],16:[2,21],19:[2,21],20:[2,21],22:[2,21],23:[2,21],25:[2,21]},{18:[1,68]},{18:[2,24],24:[2,24]},{18:[2,29],24:[2,29],31:[2,29],32:[2,29],33:[2,29],36:[2,29],38:[2,29]},{18:[2,37],24:[2,37],36:[2,37]},{37:[1,65]},{21:44,29:48,30:69,31:[1,45],32:[1,46],33:[1,47],36:[1,28],38:[1,27],39:26},{18:[2,45],24:[2,45],31:[2,45],32:[2,45],33:[2,45],36:[2,45],38:[2,45],40:[2,45]},{18:[1,70]},{5:[2,22],14:[2,22],15:[2,22],16:[2,22],19:[2,22],20:[2,22],22:[2,22],23:[2,22],25:[2,22]},{18:[2,39],24:[2,39],36:[2,39]},{5:[2,18],14:[2,18],15:[2,18],16:[2,18],19:[2,18],20:[2,18],22:[2,18],23:[2,18],25:[2,18]}], defaultActions: {17:[2,1]}, parseError: function parseError(str, hash) { throw new Error(str); diff --git a/src/handlebars.yy b/src/handlebars.yy index 2fec53ddf..6c284f490 100644 --- a/src/handlebars.yy +++ b/src/handlebars.yy @@ -92,11 +92,7 @@ hashSegments ; hashSegment - : ID EQUALS path -> [$1, $3] - | ID EQUALS STRING -> [$1, new yy.StringNode($3)] - | ID EQUALS INTEGER -> [$1, new yy.IntegerNode($3)] - | ID EQUALS BOOLEAN -> [$1, new yy.BooleanNode($3)] - | ID EQUALS dataName -> [$1, $3] + : ID EQUALS param -> [$1, $3] ; partialName From df7a3fb8c06270c90ba28fff3238707f323a3f8e Mon Sep 17 00:00:00 2001 From: kpdecker Date: Wed, 24 Jul 2013 23:37:15 -0500 Subject: [PATCH 042/200] Use ebnf iteration for hash production --- dist/handlebars.js | 26 +++++++++++++------------- src/handlebars.yy | 7 +------ 2 files changed, 14 insertions(+), 19 deletions(-) diff --git a/dist/handlebars.js b/dist/handlebars.js index bd3f10bc9..1416c3dd0 100644 --- a/dist/handlebars.js +++ b/dist/handlebars.js @@ -188,9 +188,9 @@ Handlebars.registerHelper('log', function(context, options) { var handlebars = (function(){ var parser = {trace: function trace() { }, yy: {}, -symbols_: {"error":2,"root":3,"program":4,"EOF":5,"simpleInverse":6,"statements":7,"statement":8,"openInverse":9,"closeBlock":10,"openBlock":11,"mustache":12,"partial":13,"CONTENT":14,"COMMENT":15,"OPEN_BLOCK":16,"inMustache":17,"CLOSE":18,"OPEN_INVERSE":19,"OPEN_ENDBLOCK":20,"path":21,"OPEN":22,"OPEN_UNESCAPED":23,"CLOSE_UNESCAPED":24,"OPEN_PARTIAL":25,"partialName":26,"params":27,"hash":28,"dataName":29,"param":30,"STRING":31,"INTEGER":32,"BOOLEAN":33,"hashSegments":34,"hashSegment":35,"ID":36,"EQUALS":37,"DATA":38,"pathSegments":39,"SEP":40,"$accept":0,"$end":1}, +symbols_: {"error":2,"root":3,"program":4,"EOF":5,"simpleInverse":6,"statements":7,"statement":8,"openInverse":9,"closeBlock":10,"openBlock":11,"mustache":12,"partial":13,"CONTENT":14,"COMMENT":15,"OPEN_BLOCK":16,"inMustache":17,"CLOSE":18,"OPEN_INVERSE":19,"OPEN_ENDBLOCK":20,"path":21,"OPEN":22,"OPEN_UNESCAPED":23,"CLOSE_UNESCAPED":24,"OPEN_PARTIAL":25,"partialName":26,"params":27,"hash":28,"dataName":29,"param":30,"STRING":31,"INTEGER":32,"BOOLEAN":33,"hash_repetition_plus0":34,"hashSegment":35,"ID":36,"EQUALS":37,"DATA":38,"pathSegments":39,"SEP":40,"$accept":0,"$end":1}, terminals_: {2:"error",5:"EOF",14:"CONTENT",15:"COMMENT",16:"OPEN_BLOCK",18:"CLOSE",19:"OPEN_INVERSE",20:"OPEN_ENDBLOCK",22:"OPEN",23:"OPEN_UNESCAPED",24:"CLOSE_UNESCAPED",25:"OPEN_PARTIAL",31:"STRING",32:"INTEGER",33:"BOOLEAN",36:"ID",37:"EQUALS",38:"DATA",40:"SEP"}, -productions_: [0,[3,2],[4,2],[4,3],[4,2],[4,1],[4,1],[4,0],[7,1],[7,2],[8,3],[8,3],[8,1],[8,1],[8,1],[8,1],[11,3],[9,3],[10,3],[12,3],[12,3],[13,3],[13,4],[6,2],[17,3],[17,2],[17,2],[17,1],[17,1],[27,2],[27,1],[30,1],[30,1],[30,1],[30,1],[30,1],[28,1],[34,2],[34,1],[35,3],[26,1],[26,1],[26,1],[29,2],[21,1],[39,3],[39,1]], +productions_: [0,[3,2],[4,2],[4,3],[4,2],[4,1],[4,1],[4,0],[7,1],[7,2],[8,3],[8,3],[8,1],[8,1],[8,1],[8,1],[11,3],[9,3],[10,3],[12,3],[12,3],[13,3],[13,4],[6,2],[17,3],[17,2],[17,2],[17,1],[17,1],[27,2],[27,1],[30,1],[30,1],[30,1],[30,1],[30,1],[28,1],[35,3],[26,1],[26,1],[26,1],[29,2],[21,1],[39,3],[39,1],[34,1],[34,2]], performAction: function anonymous(yytext,yyleng,yylineno,yy,yystate,$$,_$) { var $0 = $$.length - 1; @@ -270,29 +270,29 @@ case 35:this.$ = $$[$0]; break; case 36:this.$ = new yy.HashNode($$[$0]); break; -case 37: $$[$0-1].push($$[$0]); this.$ = $$[$0-1]; +case 37:this.$ = [$$[$0-2], $$[$0]]; break; -case 38:this.$ = [$$[$0]]; +case 38:this.$ = new yy.PartialNameNode($$[$0]); break; -case 39:this.$ = [$$[$0-2], $$[$0]]; +case 39:this.$ = new yy.PartialNameNode(new yy.StringNode($$[$0])); break; -case 40:this.$ = new yy.PartialNameNode($$[$0]); +case 40:this.$ = new yy.PartialNameNode(new yy.IntegerNode($$[$0])); break; -case 41:this.$ = new yy.PartialNameNode(new yy.StringNode($$[$0])); +case 41:this.$ = new yy.DataNode($$[$0]); break; -case 42:this.$ = new yy.PartialNameNode(new yy.IntegerNode($$[$0])); +case 42:this.$ = new yy.IdNode($$[$0]); break; -case 43:this.$ = new yy.DataNode($$[$0]); +case 43: $$[$0-2].push({part: $$[$0], separator: $$[$0-1]}); this.$ = $$[$0-2]; break; -case 44:this.$ = new yy.IdNode($$[$0]); +case 44:this.$ = [{part: $$[$0]}]; break; -case 45: $$[$0-2].push({part: $$[$0], separator: $$[$0-1]}); this.$ = $$[$0-2]; +case 45:this.$ = [$$[$0]]; break; -case 46:this.$ = [{part: $$[$0]}]; +case 46:$$[$0-1].push($$[$0]); break; } }, -table: [{3:1,4:2,5:[2,7],6:3,7:4,8:6,9:7,11:8,12:9,13:10,14:[1,11],15:[1,12],16:[1,13],19:[1,5],22:[1,14],23:[1,15],25:[1,16]},{1:[3]},{5:[1,17]},{5:[2,6],7:18,8:6,9:7,11:8,12:9,13:10,14:[1,11],15:[1,12],16:[1,13],19:[1,19],20:[2,6],22:[1,14],23:[1,15],25:[1,16]},{5:[2,5],6:20,8:21,9:7,11:8,12:9,13:10,14:[1,11],15:[1,12],16:[1,13],19:[1,5],20:[2,5],22:[1,14],23:[1,15],25:[1,16]},{17:23,18:[1,22],21:24,29:25,36:[1,28],38:[1,27],39:26},{5:[2,8],14:[2,8],15:[2,8],16:[2,8],19:[2,8],20:[2,8],22:[2,8],23:[2,8],25:[2,8]},{4:29,6:3,7:4,8:6,9:7,11:8,12:9,13:10,14:[1,11],15:[1,12],16:[1,13],19:[1,5],20:[2,7],22:[1,14],23:[1,15],25:[1,16]},{4:30,6:3,7:4,8:6,9:7,11:8,12:9,13:10,14:[1,11],15:[1,12],16:[1,13],19:[1,5],20:[2,7],22:[1,14],23:[1,15],25:[1,16]},{5:[2,12],14:[2,12],15:[2,12],16:[2,12],19:[2,12],20:[2,12],22:[2,12],23:[2,12],25:[2,12]},{5:[2,13],14:[2,13],15:[2,13],16:[2,13],19:[2,13],20:[2,13],22:[2,13],23:[2,13],25:[2,13]},{5:[2,14],14:[2,14],15:[2,14],16:[2,14],19:[2,14],20:[2,14],22:[2,14],23:[2,14],25:[2,14]},{5:[2,15],14:[2,15],15:[2,15],16:[2,15],19:[2,15],20:[2,15],22:[2,15],23:[2,15],25:[2,15]},{17:31,21:24,29:25,36:[1,28],38:[1,27],39:26},{17:32,21:24,29:25,36:[1,28],38:[1,27],39:26},{17:33,21:24,29:25,36:[1,28],38:[1,27],39:26},{21:35,26:34,31:[1,36],32:[1,37],36:[1,28],39:26},{1:[2,1]},{5:[2,2],8:21,9:7,11:8,12:9,13:10,14:[1,11],15:[1,12],16:[1,13],19:[1,19],20:[2,2],22:[1,14],23:[1,15],25:[1,16]},{17:23,21:24,29:25,36:[1,28],38:[1,27],39:26},{5:[2,4],7:38,8:6,9:7,11:8,12:9,13:10,14:[1,11],15:[1,12],16:[1,13],19:[1,19],20:[2,4],22:[1,14],23:[1,15],25:[1,16]},{5:[2,9],14:[2,9],15:[2,9],16:[2,9],19:[2,9],20:[2,9],22:[2,9],23:[2,9],25:[2,9]},{5:[2,23],14:[2,23],15:[2,23],16:[2,23],19:[2,23],20:[2,23],22:[2,23],23:[2,23],25:[2,23]},{18:[1,39]},{18:[2,27],21:44,24:[2,27],27:40,28:41,29:48,30:42,31:[1,45],32:[1,46],33:[1,47],34:43,35:49,36:[1,50],38:[1,27],39:26},{18:[2,28],24:[2,28]},{18:[2,44],24:[2,44],31:[2,44],32:[2,44],33:[2,44],36:[2,44],38:[2,44],40:[1,51]},{21:52,36:[1,28],39:26},{18:[2,46],24:[2,46],31:[2,46],32:[2,46],33:[2,46],36:[2,46],38:[2,46],40:[2,46]},{10:53,20:[1,54]},{10:55,20:[1,54]},{18:[1,56]},{18:[1,57]},{24:[1,58]},{18:[1,59],21:60,36:[1,28],39:26},{18:[2,40],36:[2,40]},{18:[2,41],36:[2,41]},{18:[2,42],36:[2,42]},{5:[2,3],8:21,9:7,11:8,12:9,13:10,14:[1,11],15:[1,12],16:[1,13],19:[1,19],20:[2,3],22:[1,14],23:[1,15],25:[1,16]},{14:[2,17],15:[2,17],16:[2,17],19:[2,17],20:[2,17],22:[2,17],23:[2,17],25:[2,17]},{18:[2,25],21:44,24:[2,25],28:61,29:48,30:62,31:[1,45],32:[1,46],33:[1,47],34:43,35:49,36:[1,50],38:[1,27],39:26},{18:[2,26],24:[2,26]},{18:[2,30],24:[2,30],31:[2,30],32:[2,30],33:[2,30],36:[2,30],38:[2,30]},{18:[2,36],24:[2,36],35:63,36:[1,64]},{18:[2,31],24:[2,31],31:[2,31],32:[2,31],33:[2,31],36:[2,31],38:[2,31]},{18:[2,32],24:[2,32],31:[2,32],32:[2,32],33:[2,32],36:[2,32],38:[2,32]},{18:[2,33],24:[2,33],31:[2,33],32:[2,33],33:[2,33],36:[2,33],38:[2,33]},{18:[2,34],24:[2,34],31:[2,34],32:[2,34],33:[2,34],36:[2,34],38:[2,34]},{18:[2,35],24:[2,35],31:[2,35],32:[2,35],33:[2,35],36:[2,35],38:[2,35]},{18:[2,38],24:[2,38],36:[2,38]},{18:[2,46],24:[2,46],31:[2,46],32:[2,46],33:[2,46],36:[2,46],37:[1,65],38:[2,46],40:[2,46]},{36:[1,66]},{18:[2,43],24:[2,43],31:[2,43],32:[2,43],33:[2,43],36:[2,43],38:[2,43]},{5:[2,10],14:[2,10],15:[2,10],16:[2,10],19:[2,10],20:[2,10],22:[2,10],23:[2,10],25:[2,10]},{21:67,36:[1,28],39:26},{5:[2,11],14:[2,11],15:[2,11],16:[2,11],19:[2,11],20:[2,11],22:[2,11],23:[2,11],25:[2,11]},{14:[2,16],15:[2,16],16:[2,16],19:[2,16],20:[2,16],22:[2,16],23:[2,16],25:[2,16]},{5:[2,19],14:[2,19],15:[2,19],16:[2,19],19:[2,19],20:[2,19],22:[2,19],23:[2,19],25:[2,19]},{5:[2,20],14:[2,20],15:[2,20],16:[2,20],19:[2,20],20:[2,20],22:[2,20],23:[2,20],25:[2,20]},{5:[2,21],14:[2,21],15:[2,21],16:[2,21],19:[2,21],20:[2,21],22:[2,21],23:[2,21],25:[2,21]},{18:[1,68]},{18:[2,24],24:[2,24]},{18:[2,29],24:[2,29],31:[2,29],32:[2,29],33:[2,29],36:[2,29],38:[2,29]},{18:[2,37],24:[2,37],36:[2,37]},{37:[1,65]},{21:44,29:48,30:69,31:[1,45],32:[1,46],33:[1,47],36:[1,28],38:[1,27],39:26},{18:[2,45],24:[2,45],31:[2,45],32:[2,45],33:[2,45],36:[2,45],38:[2,45],40:[2,45]},{18:[1,70]},{5:[2,22],14:[2,22],15:[2,22],16:[2,22],19:[2,22],20:[2,22],22:[2,22],23:[2,22],25:[2,22]},{18:[2,39],24:[2,39],36:[2,39]},{5:[2,18],14:[2,18],15:[2,18],16:[2,18],19:[2,18],20:[2,18],22:[2,18],23:[2,18],25:[2,18]}], +table: [{3:1,4:2,5:[2,7],6:3,7:4,8:6,9:7,11:8,12:9,13:10,14:[1,11],15:[1,12],16:[1,13],19:[1,5],22:[1,14],23:[1,15],25:[1,16]},{1:[3]},{5:[1,17]},{5:[2,6],7:18,8:6,9:7,11:8,12:9,13:10,14:[1,11],15:[1,12],16:[1,13],19:[1,19],20:[2,6],22:[1,14],23:[1,15],25:[1,16]},{5:[2,5],6:20,8:21,9:7,11:8,12:9,13:10,14:[1,11],15:[1,12],16:[1,13],19:[1,5],20:[2,5],22:[1,14],23:[1,15],25:[1,16]},{17:23,18:[1,22],21:24,29:25,36:[1,28],38:[1,27],39:26},{5:[2,8],14:[2,8],15:[2,8],16:[2,8],19:[2,8],20:[2,8],22:[2,8],23:[2,8],25:[2,8]},{4:29,6:3,7:4,8:6,9:7,11:8,12:9,13:10,14:[1,11],15:[1,12],16:[1,13],19:[1,5],20:[2,7],22:[1,14],23:[1,15],25:[1,16]},{4:30,6:3,7:4,8:6,9:7,11:8,12:9,13:10,14:[1,11],15:[1,12],16:[1,13],19:[1,5],20:[2,7],22:[1,14],23:[1,15],25:[1,16]},{5:[2,12],14:[2,12],15:[2,12],16:[2,12],19:[2,12],20:[2,12],22:[2,12],23:[2,12],25:[2,12]},{5:[2,13],14:[2,13],15:[2,13],16:[2,13],19:[2,13],20:[2,13],22:[2,13],23:[2,13],25:[2,13]},{5:[2,14],14:[2,14],15:[2,14],16:[2,14],19:[2,14],20:[2,14],22:[2,14],23:[2,14],25:[2,14]},{5:[2,15],14:[2,15],15:[2,15],16:[2,15],19:[2,15],20:[2,15],22:[2,15],23:[2,15],25:[2,15]},{17:31,21:24,29:25,36:[1,28],38:[1,27],39:26},{17:32,21:24,29:25,36:[1,28],38:[1,27],39:26},{17:33,21:24,29:25,36:[1,28],38:[1,27],39:26},{21:35,26:34,31:[1,36],32:[1,37],36:[1,28],39:26},{1:[2,1]},{5:[2,2],8:21,9:7,11:8,12:9,13:10,14:[1,11],15:[1,12],16:[1,13],19:[1,19],20:[2,2],22:[1,14],23:[1,15],25:[1,16]},{17:23,21:24,29:25,36:[1,28],38:[1,27],39:26},{5:[2,4],7:38,8:6,9:7,11:8,12:9,13:10,14:[1,11],15:[1,12],16:[1,13],19:[1,19],20:[2,4],22:[1,14],23:[1,15],25:[1,16]},{5:[2,9],14:[2,9],15:[2,9],16:[2,9],19:[2,9],20:[2,9],22:[2,9],23:[2,9],25:[2,9]},{5:[2,23],14:[2,23],15:[2,23],16:[2,23],19:[2,23],20:[2,23],22:[2,23],23:[2,23],25:[2,23]},{18:[1,39]},{18:[2,27],21:44,24:[2,27],27:40,28:41,29:48,30:42,31:[1,45],32:[1,46],33:[1,47],34:43,35:49,36:[1,50],38:[1,27],39:26},{18:[2,28],24:[2,28]},{18:[2,42],24:[2,42],31:[2,42],32:[2,42],33:[2,42],36:[2,42],38:[2,42],40:[1,51]},{21:52,36:[1,28],39:26},{18:[2,44],24:[2,44],31:[2,44],32:[2,44],33:[2,44],36:[2,44],38:[2,44],40:[2,44]},{10:53,20:[1,54]},{10:55,20:[1,54]},{18:[1,56]},{18:[1,57]},{24:[1,58]},{18:[1,59],21:60,36:[1,28],39:26},{18:[2,38],36:[2,38]},{18:[2,39],36:[2,39]},{18:[2,40],36:[2,40]},{5:[2,3],8:21,9:7,11:8,12:9,13:10,14:[1,11],15:[1,12],16:[1,13],19:[1,19],20:[2,3],22:[1,14],23:[1,15],25:[1,16]},{14:[2,17],15:[2,17],16:[2,17],19:[2,17],20:[2,17],22:[2,17],23:[2,17],25:[2,17]},{18:[2,25],21:44,24:[2,25],28:61,29:48,30:62,31:[1,45],32:[1,46],33:[1,47],34:43,35:49,36:[1,50],38:[1,27],39:26},{18:[2,26],24:[2,26]},{18:[2,30],24:[2,30],31:[2,30],32:[2,30],33:[2,30],36:[2,30],38:[2,30]},{18:[2,36],24:[2,36],35:63,36:[1,64]},{18:[2,31],24:[2,31],31:[2,31],32:[2,31],33:[2,31],36:[2,31],38:[2,31]},{18:[2,32],24:[2,32],31:[2,32],32:[2,32],33:[2,32],36:[2,32],38:[2,32]},{18:[2,33],24:[2,33],31:[2,33],32:[2,33],33:[2,33],36:[2,33],38:[2,33]},{18:[2,34],24:[2,34],31:[2,34],32:[2,34],33:[2,34],36:[2,34],38:[2,34]},{18:[2,35],24:[2,35],31:[2,35],32:[2,35],33:[2,35],36:[2,35],38:[2,35]},{18:[2,45],24:[2,45],36:[2,45]},{18:[2,44],24:[2,44],31:[2,44],32:[2,44],33:[2,44],36:[2,44],37:[1,65],38:[2,44],40:[2,44]},{36:[1,66]},{18:[2,41],24:[2,41],31:[2,41],32:[2,41],33:[2,41],36:[2,41],38:[2,41]},{5:[2,10],14:[2,10],15:[2,10],16:[2,10],19:[2,10],20:[2,10],22:[2,10],23:[2,10],25:[2,10]},{21:67,36:[1,28],39:26},{5:[2,11],14:[2,11],15:[2,11],16:[2,11],19:[2,11],20:[2,11],22:[2,11],23:[2,11],25:[2,11]},{14:[2,16],15:[2,16],16:[2,16],19:[2,16],20:[2,16],22:[2,16],23:[2,16],25:[2,16]},{5:[2,19],14:[2,19],15:[2,19],16:[2,19],19:[2,19],20:[2,19],22:[2,19],23:[2,19],25:[2,19]},{5:[2,20],14:[2,20],15:[2,20],16:[2,20],19:[2,20],20:[2,20],22:[2,20],23:[2,20],25:[2,20]},{5:[2,21],14:[2,21],15:[2,21],16:[2,21],19:[2,21],20:[2,21],22:[2,21],23:[2,21],25:[2,21]},{18:[1,68]},{18:[2,24],24:[2,24]},{18:[2,29],24:[2,29],31:[2,29],32:[2,29],33:[2,29],36:[2,29],38:[2,29]},{18:[2,46],24:[2,46],36:[2,46]},{37:[1,65]},{21:44,29:48,30:69,31:[1,45],32:[1,46],33:[1,47],36:[1,28],38:[1,27],39:26},{18:[2,43],24:[2,43],31:[2,43],32:[2,43],33:[2,43],36:[2,43],38:[2,43],40:[2,43]},{18:[1,70]},{5:[2,22],14:[2,22],15:[2,22],16:[2,22],19:[2,22],20:[2,22],22:[2,22],23:[2,22],25:[2,22]},{18:[2,37],24:[2,37],36:[2,37]},{5:[2,18],14:[2,18],15:[2,18],16:[2,18],19:[2,18],20:[2,18],22:[2,18],23:[2,18],25:[2,18]}], defaultActions: {17:[2,1]}, parseError: function parseError(str, hash) { throw new Error(str); diff --git a/src/handlebars.yy b/src/handlebars.yy index 6c284f490..dc79156f9 100644 --- a/src/handlebars.yy +++ b/src/handlebars.yy @@ -83,12 +83,7 @@ param ; hash - : hashSegments -> new yy.HashNode($1) - ; - -hashSegments - : hashSegments hashSegment { $1.push($2); $$ = $1; } - | hashSegment -> [$1] + : hashSegment+ -> new yy.HashNode($1) ; hashSegment From c373d1c6d496218a0d2b863ff9e2cfa221350ae3 Mon Sep 17 00:00:00 2001 From: kpdecker Date: Wed, 24 Jul 2013 23:38:18 -0500 Subject: [PATCH 043/200] Use ebnf optional for partial path --- dist/handlebars.js | 84 ++++++++++++++++++++++------------------------ src/handlebars.yy | 3 +- 2 files changed, 42 insertions(+), 45 deletions(-) diff --git a/dist/handlebars.js b/dist/handlebars.js index 1416c3dd0..ac8ec3e72 100644 --- a/dist/handlebars.js +++ b/dist/handlebars.js @@ -188,9 +188,9 @@ Handlebars.registerHelper('log', function(context, options) { var handlebars = (function(){ var parser = {trace: function trace() { }, yy: {}, -symbols_: {"error":2,"root":3,"program":4,"EOF":5,"simpleInverse":6,"statements":7,"statement":8,"openInverse":9,"closeBlock":10,"openBlock":11,"mustache":12,"partial":13,"CONTENT":14,"COMMENT":15,"OPEN_BLOCK":16,"inMustache":17,"CLOSE":18,"OPEN_INVERSE":19,"OPEN_ENDBLOCK":20,"path":21,"OPEN":22,"OPEN_UNESCAPED":23,"CLOSE_UNESCAPED":24,"OPEN_PARTIAL":25,"partialName":26,"params":27,"hash":28,"dataName":29,"param":30,"STRING":31,"INTEGER":32,"BOOLEAN":33,"hash_repetition_plus0":34,"hashSegment":35,"ID":36,"EQUALS":37,"DATA":38,"pathSegments":39,"SEP":40,"$accept":0,"$end":1}, -terminals_: {2:"error",5:"EOF",14:"CONTENT",15:"COMMENT",16:"OPEN_BLOCK",18:"CLOSE",19:"OPEN_INVERSE",20:"OPEN_ENDBLOCK",22:"OPEN",23:"OPEN_UNESCAPED",24:"CLOSE_UNESCAPED",25:"OPEN_PARTIAL",31:"STRING",32:"INTEGER",33:"BOOLEAN",36:"ID",37:"EQUALS",38:"DATA",40:"SEP"}, -productions_: [0,[3,2],[4,2],[4,3],[4,2],[4,1],[4,1],[4,0],[7,1],[7,2],[8,3],[8,3],[8,1],[8,1],[8,1],[8,1],[11,3],[9,3],[10,3],[12,3],[12,3],[13,3],[13,4],[6,2],[17,3],[17,2],[17,2],[17,1],[17,1],[27,2],[27,1],[30,1],[30,1],[30,1],[30,1],[30,1],[28,1],[35,3],[26,1],[26,1],[26,1],[29,2],[21,1],[39,3],[39,1],[34,1],[34,2]], +symbols_: {"error":2,"root":3,"program":4,"EOF":5,"simpleInverse":6,"statements":7,"statement":8,"openInverse":9,"closeBlock":10,"openBlock":11,"mustache":12,"partial":13,"CONTENT":14,"COMMENT":15,"OPEN_BLOCK":16,"inMustache":17,"CLOSE":18,"OPEN_INVERSE":19,"OPEN_ENDBLOCK":20,"path":21,"OPEN":22,"OPEN_UNESCAPED":23,"CLOSE_UNESCAPED":24,"OPEN_PARTIAL":25,"partialName":26,"partial_option0":27,"params":28,"hash":29,"dataName":30,"param":31,"STRING":32,"INTEGER":33,"BOOLEAN":34,"hash_repetition_plus0":35,"hashSegment":36,"ID":37,"EQUALS":38,"DATA":39,"pathSegments":40,"SEP":41,"$accept":0,"$end":1}, +terminals_: {2:"error",5:"EOF",14:"CONTENT",15:"COMMENT",16:"OPEN_BLOCK",18:"CLOSE",19:"OPEN_INVERSE",20:"OPEN_ENDBLOCK",22:"OPEN",23:"OPEN_UNESCAPED",24:"CLOSE_UNESCAPED",25:"OPEN_PARTIAL",32:"STRING",33:"INTEGER",34:"BOOLEAN",37:"ID",38:"EQUALS",39:"DATA",41:"SEP"}, +productions_: [0,[3,2],[4,2],[4,3],[4,2],[4,1],[4,1],[4,0],[7,1],[7,2],[8,3],[8,3],[8,1],[8,1],[8,1],[8,1],[11,3],[9,3],[10,3],[12,3],[12,3],[13,4],[6,2],[17,3],[17,2],[17,2],[17,1],[17,1],[28,2],[28,1],[31,1],[31,1],[31,1],[31,1],[31,1],[29,1],[36,3],[26,1],[26,1],[26,1],[30,2],[21,1],[40,3],[40,1],[27,0],[27,1],[35,1],[35,2]], performAction: function anonymous(yytext,yyleng,yylineno,yy,yystate,$$,_$) { var $0 = $$.length - 1; @@ -238,62 +238,60 @@ case 19: break; case 20:this.$ = new yy.MustacheNode($$[$0-1][0], $$[$0-1][1], true); break; -case 21:this.$ = new yy.PartialNode($$[$0-1]); +case 21:this.$ = new yy.PartialNode($$[$0-2], $$[$0-1]); break; -case 22:this.$ = new yy.PartialNode($$[$0-2], $$[$0-1]); +case 22: break; -case 23: +case 23:this.$ = [[$$[$0-2]].concat($$[$0-1]), $$[$0]]; break; -case 24:this.$ = [[$$[$0-2]].concat($$[$0-1]), $$[$0]]; +case 24:this.$ = [[$$[$0-1]].concat($$[$0]), null]; break; -case 25:this.$ = [[$$[$0-1]].concat($$[$0]), null]; +case 25:this.$ = [[$$[$0-1]], $$[$0]]; break; -case 26:this.$ = [[$$[$0-1]], $$[$0]]; +case 26:this.$ = [[$$[$0]], null]; break; case 27:this.$ = [[$$[$0]], null]; break; -case 28:this.$ = [[$$[$0]], null]; +case 28: $$[$0-1].push($$[$0]); this.$ = $$[$0-1]; break; -case 29: $$[$0-1].push($$[$0]); this.$ = $$[$0-1]; +case 29:this.$ = [$$[$0]]; break; -case 30:this.$ = [$$[$0]]; +case 30:this.$ = $$[$0]; break; -case 31:this.$ = $$[$0]; +case 31:this.$ = new yy.StringNode($$[$0]); break; -case 32:this.$ = new yy.StringNode($$[$0]); +case 32:this.$ = new yy.IntegerNode($$[$0]); break; -case 33:this.$ = new yy.IntegerNode($$[$0]); +case 33:this.$ = new yy.BooleanNode($$[$0]); break; -case 34:this.$ = new yy.BooleanNode($$[$0]); +case 34:this.$ = $$[$0]; break; -case 35:this.$ = $$[$0]; +case 35:this.$ = new yy.HashNode($$[$0]); break; -case 36:this.$ = new yy.HashNode($$[$0]); +case 36:this.$ = [$$[$0-2], $$[$0]]; break; -case 37:this.$ = [$$[$0-2], $$[$0]]; +case 37:this.$ = new yy.PartialNameNode($$[$0]); break; -case 38:this.$ = new yy.PartialNameNode($$[$0]); +case 38:this.$ = new yy.PartialNameNode(new yy.StringNode($$[$0])); break; -case 39:this.$ = new yy.PartialNameNode(new yy.StringNode($$[$0])); +case 39:this.$ = new yy.PartialNameNode(new yy.IntegerNode($$[$0])); break; -case 40:this.$ = new yy.PartialNameNode(new yy.IntegerNode($$[$0])); +case 40:this.$ = new yy.DataNode($$[$0]); break; -case 41:this.$ = new yy.DataNode($$[$0]); +case 41:this.$ = new yy.IdNode($$[$0]); break; -case 42:this.$ = new yy.IdNode($$[$0]); +case 42: $$[$0-2].push({part: $$[$0], separator: $$[$0-1]}); this.$ = $$[$0-2]; break; -case 43: $$[$0-2].push({part: $$[$0], separator: $$[$0-1]}); this.$ = $$[$0-2]; +case 43:this.$ = [{part: $$[$0]}]; break; -case 44:this.$ = [{part: $$[$0]}]; +case 46:this.$ = [$$[$0]]; break; -case 45:this.$ = [$$[$0]]; -break; -case 46:$$[$0-1].push($$[$0]); +case 47:$$[$0-1].push($$[$0]); break; } }, -table: [{3:1,4:2,5:[2,7],6:3,7:4,8:6,9:7,11:8,12:9,13:10,14:[1,11],15:[1,12],16:[1,13],19:[1,5],22:[1,14],23:[1,15],25:[1,16]},{1:[3]},{5:[1,17]},{5:[2,6],7:18,8:6,9:7,11:8,12:9,13:10,14:[1,11],15:[1,12],16:[1,13],19:[1,19],20:[2,6],22:[1,14],23:[1,15],25:[1,16]},{5:[2,5],6:20,8:21,9:7,11:8,12:9,13:10,14:[1,11],15:[1,12],16:[1,13],19:[1,5],20:[2,5],22:[1,14],23:[1,15],25:[1,16]},{17:23,18:[1,22],21:24,29:25,36:[1,28],38:[1,27],39:26},{5:[2,8],14:[2,8],15:[2,8],16:[2,8],19:[2,8],20:[2,8],22:[2,8],23:[2,8],25:[2,8]},{4:29,6:3,7:4,8:6,9:7,11:8,12:9,13:10,14:[1,11],15:[1,12],16:[1,13],19:[1,5],20:[2,7],22:[1,14],23:[1,15],25:[1,16]},{4:30,6:3,7:4,8:6,9:7,11:8,12:9,13:10,14:[1,11],15:[1,12],16:[1,13],19:[1,5],20:[2,7],22:[1,14],23:[1,15],25:[1,16]},{5:[2,12],14:[2,12],15:[2,12],16:[2,12],19:[2,12],20:[2,12],22:[2,12],23:[2,12],25:[2,12]},{5:[2,13],14:[2,13],15:[2,13],16:[2,13],19:[2,13],20:[2,13],22:[2,13],23:[2,13],25:[2,13]},{5:[2,14],14:[2,14],15:[2,14],16:[2,14],19:[2,14],20:[2,14],22:[2,14],23:[2,14],25:[2,14]},{5:[2,15],14:[2,15],15:[2,15],16:[2,15],19:[2,15],20:[2,15],22:[2,15],23:[2,15],25:[2,15]},{17:31,21:24,29:25,36:[1,28],38:[1,27],39:26},{17:32,21:24,29:25,36:[1,28],38:[1,27],39:26},{17:33,21:24,29:25,36:[1,28],38:[1,27],39:26},{21:35,26:34,31:[1,36],32:[1,37],36:[1,28],39:26},{1:[2,1]},{5:[2,2],8:21,9:7,11:8,12:9,13:10,14:[1,11],15:[1,12],16:[1,13],19:[1,19],20:[2,2],22:[1,14],23:[1,15],25:[1,16]},{17:23,21:24,29:25,36:[1,28],38:[1,27],39:26},{5:[2,4],7:38,8:6,9:7,11:8,12:9,13:10,14:[1,11],15:[1,12],16:[1,13],19:[1,19],20:[2,4],22:[1,14],23:[1,15],25:[1,16]},{5:[2,9],14:[2,9],15:[2,9],16:[2,9],19:[2,9],20:[2,9],22:[2,9],23:[2,9],25:[2,9]},{5:[2,23],14:[2,23],15:[2,23],16:[2,23],19:[2,23],20:[2,23],22:[2,23],23:[2,23],25:[2,23]},{18:[1,39]},{18:[2,27],21:44,24:[2,27],27:40,28:41,29:48,30:42,31:[1,45],32:[1,46],33:[1,47],34:43,35:49,36:[1,50],38:[1,27],39:26},{18:[2,28],24:[2,28]},{18:[2,42],24:[2,42],31:[2,42],32:[2,42],33:[2,42],36:[2,42],38:[2,42],40:[1,51]},{21:52,36:[1,28],39:26},{18:[2,44],24:[2,44],31:[2,44],32:[2,44],33:[2,44],36:[2,44],38:[2,44],40:[2,44]},{10:53,20:[1,54]},{10:55,20:[1,54]},{18:[1,56]},{18:[1,57]},{24:[1,58]},{18:[1,59],21:60,36:[1,28],39:26},{18:[2,38],36:[2,38]},{18:[2,39],36:[2,39]},{18:[2,40],36:[2,40]},{5:[2,3],8:21,9:7,11:8,12:9,13:10,14:[1,11],15:[1,12],16:[1,13],19:[1,19],20:[2,3],22:[1,14],23:[1,15],25:[1,16]},{14:[2,17],15:[2,17],16:[2,17],19:[2,17],20:[2,17],22:[2,17],23:[2,17],25:[2,17]},{18:[2,25],21:44,24:[2,25],28:61,29:48,30:62,31:[1,45],32:[1,46],33:[1,47],34:43,35:49,36:[1,50],38:[1,27],39:26},{18:[2,26],24:[2,26]},{18:[2,30],24:[2,30],31:[2,30],32:[2,30],33:[2,30],36:[2,30],38:[2,30]},{18:[2,36],24:[2,36],35:63,36:[1,64]},{18:[2,31],24:[2,31],31:[2,31],32:[2,31],33:[2,31],36:[2,31],38:[2,31]},{18:[2,32],24:[2,32],31:[2,32],32:[2,32],33:[2,32],36:[2,32],38:[2,32]},{18:[2,33],24:[2,33],31:[2,33],32:[2,33],33:[2,33],36:[2,33],38:[2,33]},{18:[2,34],24:[2,34],31:[2,34],32:[2,34],33:[2,34],36:[2,34],38:[2,34]},{18:[2,35],24:[2,35],31:[2,35],32:[2,35],33:[2,35],36:[2,35],38:[2,35]},{18:[2,45],24:[2,45],36:[2,45]},{18:[2,44],24:[2,44],31:[2,44],32:[2,44],33:[2,44],36:[2,44],37:[1,65],38:[2,44],40:[2,44]},{36:[1,66]},{18:[2,41],24:[2,41],31:[2,41],32:[2,41],33:[2,41],36:[2,41],38:[2,41]},{5:[2,10],14:[2,10],15:[2,10],16:[2,10],19:[2,10],20:[2,10],22:[2,10],23:[2,10],25:[2,10]},{21:67,36:[1,28],39:26},{5:[2,11],14:[2,11],15:[2,11],16:[2,11],19:[2,11],20:[2,11],22:[2,11],23:[2,11],25:[2,11]},{14:[2,16],15:[2,16],16:[2,16],19:[2,16],20:[2,16],22:[2,16],23:[2,16],25:[2,16]},{5:[2,19],14:[2,19],15:[2,19],16:[2,19],19:[2,19],20:[2,19],22:[2,19],23:[2,19],25:[2,19]},{5:[2,20],14:[2,20],15:[2,20],16:[2,20],19:[2,20],20:[2,20],22:[2,20],23:[2,20],25:[2,20]},{5:[2,21],14:[2,21],15:[2,21],16:[2,21],19:[2,21],20:[2,21],22:[2,21],23:[2,21],25:[2,21]},{18:[1,68]},{18:[2,24],24:[2,24]},{18:[2,29],24:[2,29],31:[2,29],32:[2,29],33:[2,29],36:[2,29],38:[2,29]},{18:[2,46],24:[2,46],36:[2,46]},{37:[1,65]},{21:44,29:48,30:69,31:[1,45],32:[1,46],33:[1,47],36:[1,28],38:[1,27],39:26},{18:[2,43],24:[2,43],31:[2,43],32:[2,43],33:[2,43],36:[2,43],38:[2,43],40:[2,43]},{18:[1,70]},{5:[2,22],14:[2,22],15:[2,22],16:[2,22],19:[2,22],20:[2,22],22:[2,22],23:[2,22],25:[2,22]},{18:[2,37],24:[2,37],36:[2,37]},{5:[2,18],14:[2,18],15:[2,18],16:[2,18],19:[2,18],20:[2,18],22:[2,18],23:[2,18],25:[2,18]}], -defaultActions: {17:[2,1]}, +table: [{3:1,4:2,5:[2,7],6:3,7:4,8:6,9:7,11:8,12:9,13:10,14:[1,11],15:[1,12],16:[1,13],19:[1,5],22:[1,14],23:[1,15],25:[1,16]},{1:[3]},{5:[1,17]},{5:[2,6],7:18,8:6,9:7,11:8,12:9,13:10,14:[1,11],15:[1,12],16:[1,13],19:[1,19],20:[2,6],22:[1,14],23:[1,15],25:[1,16]},{5:[2,5],6:20,8:21,9:7,11:8,12:9,13:10,14:[1,11],15:[1,12],16:[1,13],19:[1,5],20:[2,5],22:[1,14],23:[1,15],25:[1,16]},{17:23,18:[1,22],21:24,30:25,37:[1,28],39:[1,27],40:26},{5:[2,8],14:[2,8],15:[2,8],16:[2,8],19:[2,8],20:[2,8],22:[2,8],23:[2,8],25:[2,8]},{4:29,6:3,7:4,8:6,9:7,11:8,12:9,13:10,14:[1,11],15:[1,12],16:[1,13],19:[1,5],20:[2,7],22:[1,14],23:[1,15],25:[1,16]},{4:30,6:3,7:4,8:6,9:7,11:8,12:9,13:10,14:[1,11],15:[1,12],16:[1,13],19:[1,5],20:[2,7],22:[1,14],23:[1,15],25:[1,16]},{5:[2,12],14:[2,12],15:[2,12],16:[2,12],19:[2,12],20:[2,12],22:[2,12],23:[2,12],25:[2,12]},{5:[2,13],14:[2,13],15:[2,13],16:[2,13],19:[2,13],20:[2,13],22:[2,13],23:[2,13],25:[2,13]},{5:[2,14],14:[2,14],15:[2,14],16:[2,14],19:[2,14],20:[2,14],22:[2,14],23:[2,14],25:[2,14]},{5:[2,15],14:[2,15],15:[2,15],16:[2,15],19:[2,15],20:[2,15],22:[2,15],23:[2,15],25:[2,15]},{17:31,21:24,30:25,37:[1,28],39:[1,27],40:26},{17:32,21:24,30:25,37:[1,28],39:[1,27],40:26},{17:33,21:24,30:25,37:[1,28],39:[1,27],40:26},{21:35,26:34,32:[1,36],33:[1,37],37:[1,28],40:26},{1:[2,1]},{5:[2,2],8:21,9:7,11:8,12:9,13:10,14:[1,11],15:[1,12],16:[1,13],19:[1,19],20:[2,2],22:[1,14],23:[1,15],25:[1,16]},{17:23,21:24,30:25,37:[1,28],39:[1,27],40:26},{5:[2,4],7:38,8:6,9:7,11:8,12:9,13:10,14:[1,11],15:[1,12],16:[1,13],19:[1,19],20:[2,4],22:[1,14],23:[1,15],25:[1,16]},{5:[2,9],14:[2,9],15:[2,9],16:[2,9],19:[2,9],20:[2,9],22:[2,9],23:[2,9],25:[2,9]},{5:[2,22],14:[2,22],15:[2,22],16:[2,22],19:[2,22],20:[2,22],22:[2,22],23:[2,22],25:[2,22]},{18:[1,39]},{18:[2,26],21:44,24:[2,26],28:40,29:41,30:48,31:42,32:[1,45],33:[1,46],34:[1,47],35:43,36:49,37:[1,50],39:[1,27],40:26},{18:[2,27],24:[2,27]},{18:[2,41],24:[2,41],32:[2,41],33:[2,41],34:[2,41],37:[2,41],39:[2,41],41:[1,51]},{21:52,37:[1,28],40:26},{18:[2,43],24:[2,43],32:[2,43],33:[2,43],34:[2,43],37:[2,43],39:[2,43],41:[2,43]},{10:53,20:[1,54]},{10:55,20:[1,54]},{18:[1,56]},{18:[1,57]},{24:[1,58]},{18:[2,44],21:60,27:59,37:[1,28],40:26},{18:[2,37],37:[2,37]},{18:[2,38],37:[2,38]},{18:[2,39],37:[2,39]},{5:[2,3],8:21,9:7,11:8,12:9,13:10,14:[1,11],15:[1,12],16:[1,13],19:[1,19],20:[2,3],22:[1,14],23:[1,15],25:[1,16]},{14:[2,17],15:[2,17],16:[2,17],19:[2,17],20:[2,17],22:[2,17],23:[2,17],25:[2,17]},{18:[2,24],21:44,24:[2,24],29:61,30:48,31:62,32:[1,45],33:[1,46],34:[1,47],35:43,36:49,37:[1,50],39:[1,27],40:26},{18:[2,25],24:[2,25]},{18:[2,29],24:[2,29],32:[2,29],33:[2,29],34:[2,29],37:[2,29],39:[2,29]},{18:[2,35],24:[2,35],36:63,37:[1,64]},{18:[2,30],24:[2,30],32:[2,30],33:[2,30],34:[2,30],37:[2,30],39:[2,30]},{18:[2,31],24:[2,31],32:[2,31],33:[2,31],34:[2,31],37:[2,31],39:[2,31]},{18:[2,32],24:[2,32],32:[2,32],33:[2,32],34:[2,32],37:[2,32],39:[2,32]},{18:[2,33],24:[2,33],32:[2,33],33:[2,33],34:[2,33],37:[2,33],39:[2,33]},{18:[2,34],24:[2,34],32:[2,34],33:[2,34],34:[2,34],37:[2,34],39:[2,34]},{18:[2,46],24:[2,46],37:[2,46]},{18:[2,43],24:[2,43],32:[2,43],33:[2,43],34:[2,43],37:[2,43],38:[1,65],39:[2,43],41:[2,43]},{37:[1,66]},{18:[2,40],24:[2,40],32:[2,40],33:[2,40],34:[2,40],37:[2,40],39:[2,40]},{5:[2,10],14:[2,10],15:[2,10],16:[2,10],19:[2,10],20:[2,10],22:[2,10],23:[2,10],25:[2,10]},{21:67,37:[1,28],40:26},{5:[2,11],14:[2,11],15:[2,11],16:[2,11],19:[2,11],20:[2,11],22:[2,11],23:[2,11],25:[2,11]},{14:[2,16],15:[2,16],16:[2,16],19:[2,16],20:[2,16],22:[2,16],23:[2,16],25:[2,16]},{5:[2,19],14:[2,19],15:[2,19],16:[2,19],19:[2,19],20:[2,19],22:[2,19],23:[2,19],25:[2,19]},{5:[2,20],14:[2,20],15:[2,20],16:[2,20],19:[2,20],20:[2,20],22:[2,20],23:[2,20],25:[2,20]},{18:[1,68]},{18:[2,45]},{18:[2,23],24:[2,23]},{18:[2,28],24:[2,28],32:[2,28],33:[2,28],34:[2,28],37:[2,28],39:[2,28]},{18:[2,47],24:[2,47],37:[2,47]},{38:[1,65]},{21:44,30:48,31:69,32:[1,45],33:[1,46],34:[1,47],37:[1,28],39:[1,27],40:26},{18:[2,42],24:[2,42],32:[2,42],33:[2,42],34:[2,42],37:[2,42],39:[2,42],41:[2,42]},{18:[1,70]},{5:[2,21],14:[2,21],15:[2,21],16:[2,21],19:[2,21],20:[2,21],22:[2,21],23:[2,21],25:[2,21]},{18:[2,36],24:[2,36],37:[2,36]},{5:[2,18],14:[2,18],15:[2,18],16:[2,18],19:[2,18],20:[2,18],22:[2,18],23:[2,18],25:[2,18]}], +defaultActions: {17:[2,1],60:[2,45]}, parseError: function parseError(str, hash) { throw new Error(str); }, @@ -612,13 +610,13 @@ case 13: yy_.yytext = yy_.yytext.substr(3,yy_.yyleng-5); this.popState(); return break; case 14: return 22; break; -case 15: return 37; +case 15: return 38; break; -case 16: return 36; +case 16: return 37; break; -case 17: return 36; +case 17: return 37; break; -case 18: return 40; +case 18: return 41; break; case 19: /*ignore whitespace*/ break; @@ -626,21 +624,21 @@ case 20: this.popState(); return 24; break; case 21: this.popState(); return 18; break; -case 22: yy_.yytext = yy_.yytext.substr(1,yy_.yyleng-2).replace(/\\"/g,'"'); return 31; +case 22: yy_.yytext = yy_.yytext.substr(1,yy_.yyleng-2).replace(/\\"/g,'"'); return 32; break; -case 23: yy_.yytext = yy_.yytext.substr(1,yy_.yyleng-2).replace(/\\'/g,"'"); return 31; +case 23: yy_.yytext = yy_.yytext.substr(1,yy_.yyleng-2).replace(/\\'/g,"'"); return 32; break; -case 24: return 38; +case 24: return 39; break; -case 25: return 33; +case 25: return 34; break; -case 26: return 33; +case 26: return 34; break; -case 27: return 32; +case 27: return 33; break; -case 28: return 36; +case 28: return 37; break; -case 29: yy_.yytext = yy_.yytext.substr(1, yy_.yyleng-2); return 36; +case 29: yy_.yytext = yy_.yytext.substr(1, yy_.yyleng-2); return 37; break; case 30: return 'INVALID'; break; diff --git a/src/handlebars.yy b/src/handlebars.yy index dc79156f9..bad05f47e 100644 --- a/src/handlebars.yy +++ b/src/handlebars.yy @@ -53,8 +53,7 @@ mustache partial - : OPEN_PARTIAL partialName CLOSE -> new yy.PartialNode($2) - | OPEN_PARTIAL partialName path CLOSE -> new yy.PartialNode($2, $3) + : OPEN_PARTIAL partialName path? CLOSE -> new yy.PartialNode($2, $3) ; simpleInverse From 117239ca7c05d05ac32c84ec4c4e39688b352ec0 Mon Sep 17 00:00:00 2001 From: kpdecker Date: Wed, 24 Jul 2013 23:39:33 -0500 Subject: [PATCH 044/200] Use ebnf iteration/optional inMustache --- dist/handlebars.js | 68 +++++++++++++++++++++------------------------- src/handlebars.yy | 10 +------ 2 files changed, 32 insertions(+), 46 deletions(-) diff --git a/dist/handlebars.js b/dist/handlebars.js index ac8ec3e72..e05f346bb 100644 --- a/dist/handlebars.js +++ b/dist/handlebars.js @@ -188,9 +188,9 @@ Handlebars.registerHelper('log', function(context, options) { var handlebars = (function(){ var parser = {trace: function trace() { }, yy: {}, -symbols_: {"error":2,"root":3,"program":4,"EOF":5,"simpleInverse":6,"statements":7,"statement":8,"openInverse":9,"closeBlock":10,"openBlock":11,"mustache":12,"partial":13,"CONTENT":14,"COMMENT":15,"OPEN_BLOCK":16,"inMustache":17,"CLOSE":18,"OPEN_INVERSE":19,"OPEN_ENDBLOCK":20,"path":21,"OPEN":22,"OPEN_UNESCAPED":23,"CLOSE_UNESCAPED":24,"OPEN_PARTIAL":25,"partialName":26,"partial_option0":27,"params":28,"hash":29,"dataName":30,"param":31,"STRING":32,"INTEGER":33,"BOOLEAN":34,"hash_repetition_plus0":35,"hashSegment":36,"ID":37,"EQUALS":38,"DATA":39,"pathSegments":40,"SEP":41,"$accept":0,"$end":1}, -terminals_: {2:"error",5:"EOF",14:"CONTENT",15:"COMMENT",16:"OPEN_BLOCK",18:"CLOSE",19:"OPEN_INVERSE",20:"OPEN_ENDBLOCK",22:"OPEN",23:"OPEN_UNESCAPED",24:"CLOSE_UNESCAPED",25:"OPEN_PARTIAL",32:"STRING",33:"INTEGER",34:"BOOLEAN",37:"ID",38:"EQUALS",39:"DATA",41:"SEP"}, -productions_: [0,[3,2],[4,2],[4,3],[4,2],[4,1],[4,1],[4,0],[7,1],[7,2],[8,3],[8,3],[8,1],[8,1],[8,1],[8,1],[11,3],[9,3],[10,3],[12,3],[12,3],[13,4],[6,2],[17,3],[17,2],[17,2],[17,1],[17,1],[28,2],[28,1],[31,1],[31,1],[31,1],[31,1],[31,1],[29,1],[36,3],[26,1],[26,1],[26,1],[30,2],[21,1],[40,3],[40,1],[27,0],[27,1],[35,1],[35,2]], +symbols_: {"error":2,"root":3,"program":4,"EOF":5,"simpleInverse":6,"statements":7,"statement":8,"openInverse":9,"closeBlock":10,"openBlock":11,"mustache":12,"partial":13,"CONTENT":14,"COMMENT":15,"OPEN_BLOCK":16,"inMustache":17,"CLOSE":18,"OPEN_INVERSE":19,"OPEN_ENDBLOCK":20,"path":21,"OPEN":22,"OPEN_UNESCAPED":23,"CLOSE_UNESCAPED":24,"OPEN_PARTIAL":25,"partialName":26,"partial_option0":27,"inMustache_repetition0":28,"inMustache_option0":29,"dataName":30,"param":31,"STRING":32,"INTEGER":33,"BOOLEAN":34,"hash":35,"hash_repetition_plus0":36,"hashSegment":37,"ID":38,"EQUALS":39,"DATA":40,"pathSegments":41,"SEP":42,"$accept":0,"$end":1}, +terminals_: {2:"error",5:"EOF",14:"CONTENT",15:"COMMENT",16:"OPEN_BLOCK",18:"CLOSE",19:"OPEN_INVERSE",20:"OPEN_ENDBLOCK",22:"OPEN",23:"OPEN_UNESCAPED",24:"CLOSE_UNESCAPED",25:"OPEN_PARTIAL",32:"STRING",33:"INTEGER",34:"BOOLEAN",38:"ID",39:"EQUALS",40:"DATA",42:"SEP"}, +productions_: [0,[3,2],[4,2],[4,3],[4,2],[4,1],[4,1],[4,0],[7,1],[7,2],[8,3],[8,3],[8,1],[8,1],[8,1],[8,1],[11,3],[9,3],[10,3],[12,3],[12,3],[13,4],[6,2],[17,3],[17,1],[31,1],[31,1],[31,1],[31,1],[31,1],[35,1],[37,3],[26,1],[26,1],[26,1],[30,2],[21,1],[41,3],[41,1],[27,0],[27,1],[28,0],[28,2],[29,0],[29,1],[36,1],[36,2]], performAction: function anonymous(yytext,yyleng,yylineno,yy,yystate,$$,_$) { var $0 = $$.length - 1; @@ -244,54 +244,48 @@ case 22: break; case 23:this.$ = [[$$[$0-2]].concat($$[$0-1]), $$[$0]]; break; -case 24:this.$ = [[$$[$0-1]].concat($$[$0]), null]; +case 24:this.$ = [[$$[$0]], null]; break; -case 25:this.$ = [[$$[$0-1]], $$[$0]]; +case 25:this.$ = $$[$0]; break; -case 26:this.$ = [[$$[$0]], null]; +case 26:this.$ = new yy.StringNode($$[$0]); break; -case 27:this.$ = [[$$[$0]], null]; +case 27:this.$ = new yy.IntegerNode($$[$0]); break; -case 28: $$[$0-1].push($$[$0]); this.$ = $$[$0-1]; +case 28:this.$ = new yy.BooleanNode($$[$0]); break; -case 29:this.$ = [$$[$0]]; +case 29:this.$ = $$[$0]; break; -case 30:this.$ = $$[$0]; +case 30:this.$ = new yy.HashNode($$[$0]); break; -case 31:this.$ = new yy.StringNode($$[$0]); +case 31:this.$ = [$$[$0-2], $$[$0]]; break; -case 32:this.$ = new yy.IntegerNode($$[$0]); +case 32:this.$ = new yy.PartialNameNode($$[$0]); break; -case 33:this.$ = new yy.BooleanNode($$[$0]); +case 33:this.$ = new yy.PartialNameNode(new yy.StringNode($$[$0])); break; -case 34:this.$ = $$[$0]; +case 34:this.$ = new yy.PartialNameNode(new yy.IntegerNode($$[$0])); break; -case 35:this.$ = new yy.HashNode($$[$0]); +case 35:this.$ = new yy.DataNode($$[$0]); break; -case 36:this.$ = [$$[$0-2], $$[$0]]; +case 36:this.$ = new yy.IdNode($$[$0]); break; -case 37:this.$ = new yy.PartialNameNode($$[$0]); +case 37: $$[$0-2].push({part: $$[$0], separator: $$[$0-1]}); this.$ = $$[$0-2]; break; -case 38:this.$ = new yy.PartialNameNode(new yy.StringNode($$[$0])); +case 38:this.$ = [{part: $$[$0]}]; break; -case 39:this.$ = new yy.PartialNameNode(new yy.IntegerNode($$[$0])); +case 41:this.$ = []; break; -case 40:this.$ = new yy.DataNode($$[$0]); +case 42:$$[$0-1].push($$[$0]); break; -case 41:this.$ = new yy.IdNode($$[$0]); +case 45:this.$ = [$$[$0]]; break; -case 42: $$[$0-2].push({part: $$[$0], separator: $$[$0-1]}); this.$ = $$[$0-2]; -break; -case 43:this.$ = [{part: $$[$0]}]; -break; -case 46:this.$ = [$$[$0]]; -break; -case 47:$$[$0-1].push($$[$0]); +case 46:$$[$0-1].push($$[$0]); break; } }, -table: [{3:1,4:2,5:[2,7],6:3,7:4,8:6,9:7,11:8,12:9,13:10,14:[1,11],15:[1,12],16:[1,13],19:[1,5],22:[1,14],23:[1,15],25:[1,16]},{1:[3]},{5:[1,17]},{5:[2,6],7:18,8:6,9:7,11:8,12:9,13:10,14:[1,11],15:[1,12],16:[1,13],19:[1,19],20:[2,6],22:[1,14],23:[1,15],25:[1,16]},{5:[2,5],6:20,8:21,9:7,11:8,12:9,13:10,14:[1,11],15:[1,12],16:[1,13],19:[1,5],20:[2,5],22:[1,14],23:[1,15],25:[1,16]},{17:23,18:[1,22],21:24,30:25,37:[1,28],39:[1,27],40:26},{5:[2,8],14:[2,8],15:[2,8],16:[2,8],19:[2,8],20:[2,8],22:[2,8],23:[2,8],25:[2,8]},{4:29,6:3,7:4,8:6,9:7,11:8,12:9,13:10,14:[1,11],15:[1,12],16:[1,13],19:[1,5],20:[2,7],22:[1,14],23:[1,15],25:[1,16]},{4:30,6:3,7:4,8:6,9:7,11:8,12:9,13:10,14:[1,11],15:[1,12],16:[1,13],19:[1,5],20:[2,7],22:[1,14],23:[1,15],25:[1,16]},{5:[2,12],14:[2,12],15:[2,12],16:[2,12],19:[2,12],20:[2,12],22:[2,12],23:[2,12],25:[2,12]},{5:[2,13],14:[2,13],15:[2,13],16:[2,13],19:[2,13],20:[2,13],22:[2,13],23:[2,13],25:[2,13]},{5:[2,14],14:[2,14],15:[2,14],16:[2,14],19:[2,14],20:[2,14],22:[2,14],23:[2,14],25:[2,14]},{5:[2,15],14:[2,15],15:[2,15],16:[2,15],19:[2,15],20:[2,15],22:[2,15],23:[2,15],25:[2,15]},{17:31,21:24,30:25,37:[1,28],39:[1,27],40:26},{17:32,21:24,30:25,37:[1,28],39:[1,27],40:26},{17:33,21:24,30:25,37:[1,28],39:[1,27],40:26},{21:35,26:34,32:[1,36],33:[1,37],37:[1,28],40:26},{1:[2,1]},{5:[2,2],8:21,9:7,11:8,12:9,13:10,14:[1,11],15:[1,12],16:[1,13],19:[1,19],20:[2,2],22:[1,14],23:[1,15],25:[1,16]},{17:23,21:24,30:25,37:[1,28],39:[1,27],40:26},{5:[2,4],7:38,8:6,9:7,11:8,12:9,13:10,14:[1,11],15:[1,12],16:[1,13],19:[1,19],20:[2,4],22:[1,14],23:[1,15],25:[1,16]},{5:[2,9],14:[2,9],15:[2,9],16:[2,9],19:[2,9],20:[2,9],22:[2,9],23:[2,9],25:[2,9]},{5:[2,22],14:[2,22],15:[2,22],16:[2,22],19:[2,22],20:[2,22],22:[2,22],23:[2,22],25:[2,22]},{18:[1,39]},{18:[2,26],21:44,24:[2,26],28:40,29:41,30:48,31:42,32:[1,45],33:[1,46],34:[1,47],35:43,36:49,37:[1,50],39:[1,27],40:26},{18:[2,27],24:[2,27]},{18:[2,41],24:[2,41],32:[2,41],33:[2,41],34:[2,41],37:[2,41],39:[2,41],41:[1,51]},{21:52,37:[1,28],40:26},{18:[2,43],24:[2,43],32:[2,43],33:[2,43],34:[2,43],37:[2,43],39:[2,43],41:[2,43]},{10:53,20:[1,54]},{10:55,20:[1,54]},{18:[1,56]},{18:[1,57]},{24:[1,58]},{18:[2,44],21:60,27:59,37:[1,28],40:26},{18:[2,37],37:[2,37]},{18:[2,38],37:[2,38]},{18:[2,39],37:[2,39]},{5:[2,3],8:21,9:7,11:8,12:9,13:10,14:[1,11],15:[1,12],16:[1,13],19:[1,19],20:[2,3],22:[1,14],23:[1,15],25:[1,16]},{14:[2,17],15:[2,17],16:[2,17],19:[2,17],20:[2,17],22:[2,17],23:[2,17],25:[2,17]},{18:[2,24],21:44,24:[2,24],29:61,30:48,31:62,32:[1,45],33:[1,46],34:[1,47],35:43,36:49,37:[1,50],39:[1,27],40:26},{18:[2,25],24:[2,25]},{18:[2,29],24:[2,29],32:[2,29],33:[2,29],34:[2,29],37:[2,29],39:[2,29]},{18:[2,35],24:[2,35],36:63,37:[1,64]},{18:[2,30],24:[2,30],32:[2,30],33:[2,30],34:[2,30],37:[2,30],39:[2,30]},{18:[2,31],24:[2,31],32:[2,31],33:[2,31],34:[2,31],37:[2,31],39:[2,31]},{18:[2,32],24:[2,32],32:[2,32],33:[2,32],34:[2,32],37:[2,32],39:[2,32]},{18:[2,33],24:[2,33],32:[2,33],33:[2,33],34:[2,33],37:[2,33],39:[2,33]},{18:[2,34],24:[2,34],32:[2,34],33:[2,34],34:[2,34],37:[2,34],39:[2,34]},{18:[2,46],24:[2,46],37:[2,46]},{18:[2,43],24:[2,43],32:[2,43],33:[2,43],34:[2,43],37:[2,43],38:[1,65],39:[2,43],41:[2,43]},{37:[1,66]},{18:[2,40],24:[2,40],32:[2,40],33:[2,40],34:[2,40],37:[2,40],39:[2,40]},{5:[2,10],14:[2,10],15:[2,10],16:[2,10],19:[2,10],20:[2,10],22:[2,10],23:[2,10],25:[2,10]},{21:67,37:[1,28],40:26},{5:[2,11],14:[2,11],15:[2,11],16:[2,11],19:[2,11],20:[2,11],22:[2,11],23:[2,11],25:[2,11]},{14:[2,16],15:[2,16],16:[2,16],19:[2,16],20:[2,16],22:[2,16],23:[2,16],25:[2,16]},{5:[2,19],14:[2,19],15:[2,19],16:[2,19],19:[2,19],20:[2,19],22:[2,19],23:[2,19],25:[2,19]},{5:[2,20],14:[2,20],15:[2,20],16:[2,20],19:[2,20],20:[2,20],22:[2,20],23:[2,20],25:[2,20]},{18:[1,68]},{18:[2,45]},{18:[2,23],24:[2,23]},{18:[2,28],24:[2,28],32:[2,28],33:[2,28],34:[2,28],37:[2,28],39:[2,28]},{18:[2,47],24:[2,47],37:[2,47]},{38:[1,65]},{21:44,30:48,31:69,32:[1,45],33:[1,46],34:[1,47],37:[1,28],39:[1,27],40:26},{18:[2,42],24:[2,42],32:[2,42],33:[2,42],34:[2,42],37:[2,42],39:[2,42],41:[2,42]},{18:[1,70]},{5:[2,21],14:[2,21],15:[2,21],16:[2,21],19:[2,21],20:[2,21],22:[2,21],23:[2,21],25:[2,21]},{18:[2,36],24:[2,36],37:[2,36]},{5:[2,18],14:[2,18],15:[2,18],16:[2,18],19:[2,18],20:[2,18],22:[2,18],23:[2,18],25:[2,18]}], -defaultActions: {17:[2,1],60:[2,45]}, +table: [{3:1,4:2,5:[2,7],6:3,7:4,8:6,9:7,11:8,12:9,13:10,14:[1,11],15:[1,12],16:[1,13],19:[1,5],22:[1,14],23:[1,15],25:[1,16]},{1:[3]},{5:[1,17]},{5:[2,6],7:18,8:6,9:7,11:8,12:9,13:10,14:[1,11],15:[1,12],16:[1,13],19:[1,19],20:[2,6],22:[1,14],23:[1,15],25:[1,16]},{5:[2,5],6:20,8:21,9:7,11:8,12:9,13:10,14:[1,11],15:[1,12],16:[1,13],19:[1,5],20:[2,5],22:[1,14],23:[1,15],25:[1,16]},{17:23,18:[1,22],21:24,30:25,38:[1,28],40:[1,27],41:26},{5:[2,8],14:[2,8],15:[2,8],16:[2,8],19:[2,8],20:[2,8],22:[2,8],23:[2,8],25:[2,8]},{4:29,6:3,7:4,8:6,9:7,11:8,12:9,13:10,14:[1,11],15:[1,12],16:[1,13],19:[1,5],20:[2,7],22:[1,14],23:[1,15],25:[1,16]},{4:30,6:3,7:4,8:6,9:7,11:8,12:9,13:10,14:[1,11],15:[1,12],16:[1,13],19:[1,5],20:[2,7],22:[1,14],23:[1,15],25:[1,16]},{5:[2,12],14:[2,12],15:[2,12],16:[2,12],19:[2,12],20:[2,12],22:[2,12],23:[2,12],25:[2,12]},{5:[2,13],14:[2,13],15:[2,13],16:[2,13],19:[2,13],20:[2,13],22:[2,13],23:[2,13],25:[2,13]},{5:[2,14],14:[2,14],15:[2,14],16:[2,14],19:[2,14],20:[2,14],22:[2,14],23:[2,14],25:[2,14]},{5:[2,15],14:[2,15],15:[2,15],16:[2,15],19:[2,15],20:[2,15],22:[2,15],23:[2,15],25:[2,15]},{17:31,21:24,30:25,38:[1,28],40:[1,27],41:26},{17:32,21:24,30:25,38:[1,28],40:[1,27],41:26},{17:33,21:24,30:25,38:[1,28],40:[1,27],41:26},{21:35,26:34,32:[1,36],33:[1,37],38:[1,28],41:26},{1:[2,1]},{5:[2,2],8:21,9:7,11:8,12:9,13:10,14:[1,11],15:[1,12],16:[1,13],19:[1,19],20:[2,2],22:[1,14],23:[1,15],25:[1,16]},{17:23,21:24,30:25,38:[1,28],40:[1,27],41:26},{5:[2,4],7:38,8:6,9:7,11:8,12:9,13:10,14:[1,11],15:[1,12],16:[1,13],19:[1,19],20:[2,4],22:[1,14],23:[1,15],25:[1,16]},{5:[2,9],14:[2,9],15:[2,9],16:[2,9],19:[2,9],20:[2,9],22:[2,9],23:[2,9],25:[2,9]},{5:[2,22],14:[2,22],15:[2,22],16:[2,22],19:[2,22],20:[2,22],22:[2,22],23:[2,22],25:[2,22]},{18:[1,39]},{18:[2,41],24:[2,41],28:40,32:[2,41],33:[2,41],34:[2,41],38:[2,41],40:[2,41]},{18:[2,24],24:[2,24]},{18:[2,36],24:[2,36],32:[2,36],33:[2,36],34:[2,36],38:[2,36],40:[2,36],42:[1,41]},{21:42,38:[1,28],41:26},{18:[2,38],24:[2,38],32:[2,38],33:[2,38],34:[2,38],38:[2,38],40:[2,38],42:[2,38]},{10:43,20:[1,44]},{10:45,20:[1,44]},{18:[1,46]},{18:[1,47]},{24:[1,48]},{18:[2,39],21:50,27:49,38:[1,28],41:26},{18:[2,32],38:[2,32]},{18:[2,33],38:[2,33]},{18:[2,34],38:[2,34]},{5:[2,3],8:21,9:7,11:8,12:9,13:10,14:[1,11],15:[1,12],16:[1,13],19:[1,19],20:[2,3],22:[1,14],23:[1,15],25:[1,16]},{14:[2,17],15:[2,17],16:[2,17],19:[2,17],20:[2,17],22:[2,17],23:[2,17],25:[2,17]},{18:[2,43],21:54,24:[2,43],29:51,30:58,31:52,32:[1,55],33:[1,56],34:[1,57],35:53,36:59,37:60,38:[1,61],40:[1,27],41:26},{38:[1,62]},{18:[2,35],24:[2,35],32:[2,35],33:[2,35],34:[2,35],38:[2,35],40:[2,35]},{5:[2,10],14:[2,10],15:[2,10],16:[2,10],19:[2,10],20:[2,10],22:[2,10],23:[2,10],25:[2,10]},{21:63,38:[1,28],41:26},{5:[2,11],14:[2,11],15:[2,11],16:[2,11],19:[2,11],20:[2,11],22:[2,11],23:[2,11],25:[2,11]},{14:[2,16],15:[2,16],16:[2,16],19:[2,16],20:[2,16],22:[2,16],23:[2,16],25:[2,16]},{5:[2,19],14:[2,19],15:[2,19],16:[2,19],19:[2,19],20:[2,19],22:[2,19],23:[2,19],25:[2,19]},{5:[2,20],14:[2,20],15:[2,20],16:[2,20],19:[2,20],20:[2,20],22:[2,20],23:[2,20],25:[2,20]},{18:[1,64]},{18:[2,40]},{18:[2,23],24:[2,23]},{18:[2,42],24:[2,42],32:[2,42],33:[2,42],34:[2,42],38:[2,42],40:[2,42]},{18:[2,44],24:[2,44]},{18:[2,25],24:[2,25],32:[2,25],33:[2,25],34:[2,25],38:[2,25],40:[2,25]},{18:[2,26],24:[2,26],32:[2,26],33:[2,26],34:[2,26],38:[2,26],40:[2,26]},{18:[2,27],24:[2,27],32:[2,27],33:[2,27],34:[2,27],38:[2,27],40:[2,27]},{18:[2,28],24:[2,28],32:[2,28],33:[2,28],34:[2,28],38:[2,28],40:[2,28]},{18:[2,29],24:[2,29],32:[2,29],33:[2,29],34:[2,29],38:[2,29],40:[2,29]},{18:[2,30],24:[2,30],37:65,38:[1,66]},{18:[2,45],24:[2,45],38:[2,45]},{18:[2,38],24:[2,38],32:[2,38],33:[2,38],34:[2,38],38:[2,38],39:[1,67],40:[2,38],42:[2,38]},{18:[2,37],24:[2,37],32:[2,37],33:[2,37],34:[2,37],38:[2,37],40:[2,37],42:[2,37]},{18:[1,68]},{5:[2,21],14:[2,21],15:[2,21],16:[2,21],19:[2,21],20:[2,21],22:[2,21],23:[2,21],25:[2,21]},{18:[2,46],24:[2,46],38:[2,46]},{39:[1,67]},{21:54,30:58,31:69,32:[1,55],33:[1,56],34:[1,57],38:[1,28],40:[1,27],41:26},{5:[2,18],14:[2,18],15:[2,18],16:[2,18],19:[2,18],20:[2,18],22:[2,18],23:[2,18],25:[2,18]},{18:[2,31],24:[2,31],38:[2,31]}], +defaultActions: {17:[2,1],50:[2,40]}, parseError: function parseError(str, hash) { throw new Error(str); }, @@ -610,13 +604,13 @@ case 13: yy_.yytext = yy_.yytext.substr(3,yy_.yyleng-5); this.popState(); return break; case 14: return 22; break; -case 15: return 38; +case 15: return 39; break; -case 16: return 37; +case 16: return 38; break; -case 17: return 37; +case 17: return 38; break; -case 18: return 41; +case 18: return 42; break; case 19: /*ignore whitespace*/ break; @@ -628,7 +622,7 @@ case 22: yy_.yytext = yy_.yytext.substr(1,yy_.yyleng-2).replace(/\\"/g,'"'); ret break; case 23: yy_.yytext = yy_.yytext.substr(1,yy_.yyleng-2).replace(/\\'/g,"'"); return 32; break; -case 24: return 39; +case 24: return 40; break; case 25: return 34; break; @@ -636,9 +630,9 @@ case 26: return 34; break; case 27: return 33; break; -case 28: return 37; +case 28: return 38; break; -case 29: yy_.yytext = yy_.yytext.substr(1, yy_.yyleng-2); return 37; +case 29: yy_.yytext = yy_.yytext.substr(1, yy_.yyleng-2); return 38; break; case 30: return 'INVALID'; break; diff --git a/src/handlebars.yy b/src/handlebars.yy index bad05f47e..8a89dff7b 100644 --- a/src/handlebars.yy +++ b/src/handlebars.yy @@ -61,18 +61,10 @@ simpleInverse ; inMustache - : path params hash -> [[$1].concat($2), $3] - | path params -> [[$1].concat($2), null] - | path hash -> [[$1], $2] - | path -> [[$1], null] + : path param* hash? -> [[$1].concat($2), $3] | dataName -> [[$1], null] ; -params - : params param { $1.push($2); $$ = $1; } - | param -> [$1] - ; - param : path -> $1 | STRING -> new yy.StringNode($1) From 497515c02fd58d46e38c872a3fbf366be5f876fe Mon Sep 17 00:00:00 2001 From: kpdecker Date: Wed, 24 Jul 2013 23:43:56 -0500 Subject: [PATCH 045/200] Remove braces on single line lex statements Resolves a parsing issue in the 0.4 branch of jison. --- dist/handlebars.js | 60 +++++++++++++++++++++---------------------- src/handlebars.l | 64 +++++++++++++++++++++++----------------------- 2 files changed, 62 insertions(+), 62 deletions(-) diff --git a/dist/handlebars.js b/dist/handlebars.js index e05f346bb..89b723935 100644 --- a/dist/handlebars.js +++ b/dist/handlebars.js @@ -566,7 +566,7 @@ lexer.performAction = function anonymous(yy,yy_,$avoiding_name_collisions,YY_STA var YYSTATE=YY_START switch($avoiding_name_collisions) { -case 0: yy_.yytext = "\\"; return 14; +case 0:yy_.yytext = "\\"; return 14; break; case 1: if(yy_.yytext.slice(-1) !== "\\") this.begin("mu"); @@ -574,7 +574,7 @@ case 1: if(yy_.yytext) return 14; break; -case 2: return 14; +case 2:return 14; break; case 3: if(yy_.yytext.slice(-1) !== "\\") this.popState(); @@ -582,61 +582,61 @@ case 3: return 14; break; -case 4: yy_.yytext = yy_.yytext.substr(0, yy_.yyleng-4); this.popState(); return 15; +case 4:yy_.yytext = yy_.yytext.substr(0, yy_.yyleng-4); this.popState(); return 15; break; -case 5: return 25; +case 5:return 25; break; -case 6: return 16; +case 6:return 16; break; -case 7: return 20; +case 7:return 20; break; -case 8: return 19; +case 8:return 19; break; -case 9: return 19; +case 9:return 19; break; -case 10: return 23; +case 10:return 23; break; -case 11: return 22; +case 11:return 22; break; -case 12: this.popState(); this.begin('com'); +case 12:this.popState(); this.begin('com'); break; -case 13: yy_.yytext = yy_.yytext.substr(3,yy_.yyleng-5); this.popState(); return 15; +case 13:yy_.yytext = yy_.yytext.substr(3,yy_.yyleng-5); this.popState(); return 15; break; -case 14: return 22; +case 14:return 22; break; -case 15: return 39; +case 15:return 39; break; -case 16: return 38; +case 16:return 38; break; -case 17: return 38; +case 17:return 38; break; -case 18: return 42; +case 18:return 42; break; -case 19: /*ignore whitespace*/ +case 19:/*ignore whitespace*/ break; -case 20: this.popState(); return 24; +case 20:this.popState(); return 24; break; -case 21: this.popState(); return 18; +case 21:this.popState(); return 18; break; -case 22: yy_.yytext = yy_.yytext.substr(1,yy_.yyleng-2).replace(/\\"/g,'"'); return 32; +case 22:yy_.yytext = yy_.yytext.substr(1,yy_.yyleng-2).replace(/\\"/g,'"'); return 32; break; -case 23: yy_.yytext = yy_.yytext.substr(1,yy_.yyleng-2).replace(/\\'/g,"'"); return 32; +case 23:yy_.yytext = yy_.yytext.substr(1,yy_.yyleng-2).replace(/\\'/g,"'"); return 32; break; -case 24: return 40; +case 24:return 40; break; -case 25: return 34; +case 25:return 34; break; -case 26: return 34; +case 26:return 34; break; -case 27: return 33; +case 27:return 33; break; -case 28: return 38; +case 28:return 38; break; -case 29: yy_.yytext = yy_.yytext.substr(1, yy_.yyleng-2); return 38; +case 29:yy_.yytext = yy_.yytext.substr(1, yy_.yyleng-2); return 38; break; -case 30: return 'INVALID'; +case 30:return 'INVALID'; break; -case 31: return 5; +case 31:return 5; break; } }; diff --git a/src/handlebars.l b/src/handlebars.l index aa76eabd3..5fd0bcdaf 100644 --- a/src/handlebars.l +++ b/src/handlebars.l @@ -3,14 +3,14 @@ %% -"\\\\"/("{{") { yytext = "\\"; return 'CONTENT'; } +"\\\\"/("{{") yytext = "\\"; return 'CONTENT'; [^\x00]*?/("{{") { if(yytext.slice(-1) !== "\\") this.begin("mu"); if(yytext.slice(-1) === "\\") yytext = yytext.substr(0,yyleng-1), this.begin("emu"); if(yytext) return 'CONTENT'; } -[^\x00]+ { return 'CONTENT'; } +[^\x00]+ return 'CONTENT'; [^\x00]{2,}?/("{{"|<>) { if(yytext.slice(-1) !== "\\") this.popState(); @@ -18,32 +18,32 @@ return 'CONTENT'; } -[\s\S]*?"--}}" { yytext = yytext.substr(0, yyleng-4); this.popState(); return 'COMMENT'; } - -"{{>" { return 'OPEN_PARTIAL'; } -"{{#" { return 'OPEN_BLOCK'; } -"{{/" { return 'OPEN_ENDBLOCK'; } -"{{^" { return 'OPEN_INVERSE'; } -"{{"\s*"else" { return 'OPEN_INVERSE'; } -"{{{" { return 'OPEN_UNESCAPED'; } -"{{&" { return 'OPEN'; } -"{{!--" { this.popState(); this.begin('com'); } -"{{!"[\s\S]*?"}}" { yytext = yytext.substr(3,yyleng-5); this.popState(); return 'COMMENT'; } -"{{" { return 'OPEN'; } - -"=" { return 'EQUALS'; } -"."/[}\/ ] { return 'ID'; } -".." { return 'ID'; } -[\/.] { return 'SEP'; } -\s+ { /*ignore whitespace*/ } -"}}}" { this.popState(); return 'CLOSE_UNESCAPED'; } -"}}" { this.popState(); return 'CLOSE'; } -'"'("\\"["]|[^"])*'"' { yytext = yytext.substr(1,yyleng-2).replace(/\\"/g,'"'); return 'STRING'; } -"'"("\\"[']|[^'])*"'" { yytext = yytext.substr(1,yyleng-2).replace(/\\'/g,"'"); return 'STRING'; } -"@" { return 'DATA'; } -"true"/[}\s] { return 'BOOLEAN'; } -"false"/[}\s] { return 'BOOLEAN'; } -\-?[0-9]+/[}\s] { return 'INTEGER'; } +[\s\S]*?"--}}" yytext = yytext.substr(0, yyleng-4); this.popState(); return 'COMMENT'; + +"{{>" return 'OPEN_PARTIAL'; +"{{#" return 'OPEN_BLOCK'; +"{{/" return 'OPEN_ENDBLOCK'; +"{{^" return 'OPEN_INVERSE'; +"{{"\s*"else" return 'OPEN_INVERSE'; +"{{{" return 'OPEN_UNESCAPED'; +"{{&" return 'OPEN'; +"{{!--" this.popState(); this.begin('com'); +"{{!"[\s\S]*?"}}" yytext = yytext.substr(3,yyleng-5); this.popState(); return 'COMMENT'; +"{{" return 'OPEN'; + +"=" return 'EQUALS'; +"."/[}\/ ] return 'ID'; +".." return 'ID'; +[\/.] return 'SEP'; +\s+ /*ignore whitespace*/ +"}}}" this.popState(); return 'CLOSE_UNESCAPED'; +"}}" this.popState(); return 'CLOSE'; +'"'("\\"["]|[^"])*'"' yytext = yytext.substr(1,yyleng-2).replace(/\\"/g,'"'); return 'STRING'; +"'"("\\"[']|[^'])*"'" yytext = yytext.substr(1,yyleng-2).replace(/\\'/g,"'"); return 'STRING'; +"@" return 'DATA'; +"true"/[}\s] return 'BOOLEAN'; +"false"/[}\s] return 'BOOLEAN'; +\-?[0-9]+/[}\s] return 'INTEGER'; /* ID is the inverse of control characters. @@ -54,10 +54,10 @@ Control characters ranges: [\[-\^`] [, \, ], ^, `, Exceptions in range: _ [\{-~] {, |, }, ~ */ -[^\s!"#%-,\.\/;->@\[-\^`\{-~]+/[=}\s\/.] { return 'ID'; } +[^\s!"#%-,\.\/;->@\[-\^`\{-~]+/[=}\s\/.] return 'ID'; -'['[^\]]*']' { yytext = yytext.substr(1, yyleng-2); return 'ID'; } -. { return 'INVALID'; } +'['[^\]]*']' yytext = yytext.substr(1, yyleng-2); return 'ID'; +. return 'INVALID'; -<> { return 'EOF'; } +<> return 'EOF'; From dcbc3a55a4a7c129cc9aea0ef2003c1c3c86fe29 Mon Sep 17 00:00:00 2001 From: kpdecker Date: Wed, 24 Jul 2013 23:49:16 -0500 Subject: [PATCH 046/200] Prevent nonsensical root {{^}} --- dist/handlebars.js | 10 +++++----- spec/parser.js | 3 +++ src/handlebars.yy | 2 +- 3 files changed, 9 insertions(+), 6 deletions(-) diff --git a/dist/handlebars.js b/dist/handlebars.js index 89b723935..90f20ecdb 100644 --- a/dist/handlebars.js +++ b/dist/handlebars.js @@ -188,14 +188,14 @@ Handlebars.registerHelper('log', function(context, options) { var handlebars = (function(){ var parser = {trace: function trace() { }, yy: {}, -symbols_: {"error":2,"root":3,"program":4,"EOF":5,"simpleInverse":6,"statements":7,"statement":8,"openInverse":9,"closeBlock":10,"openBlock":11,"mustache":12,"partial":13,"CONTENT":14,"COMMENT":15,"OPEN_BLOCK":16,"inMustache":17,"CLOSE":18,"OPEN_INVERSE":19,"OPEN_ENDBLOCK":20,"path":21,"OPEN":22,"OPEN_UNESCAPED":23,"CLOSE_UNESCAPED":24,"OPEN_PARTIAL":25,"partialName":26,"partial_option0":27,"inMustache_repetition0":28,"inMustache_option0":29,"dataName":30,"param":31,"STRING":32,"INTEGER":33,"BOOLEAN":34,"hash":35,"hash_repetition_plus0":36,"hashSegment":37,"ID":38,"EQUALS":39,"DATA":40,"pathSegments":41,"SEP":42,"$accept":0,"$end":1}, +symbols_: {"error":2,"root":3,"statements":4,"EOF":5,"program":6,"simpleInverse":7,"statement":8,"openInverse":9,"closeBlock":10,"openBlock":11,"mustache":12,"partial":13,"CONTENT":14,"COMMENT":15,"OPEN_BLOCK":16,"inMustache":17,"CLOSE":18,"OPEN_INVERSE":19,"OPEN_ENDBLOCK":20,"path":21,"OPEN":22,"OPEN_UNESCAPED":23,"CLOSE_UNESCAPED":24,"OPEN_PARTIAL":25,"partialName":26,"partial_option0":27,"inMustache_repetition0":28,"inMustache_option0":29,"dataName":30,"param":31,"STRING":32,"INTEGER":33,"BOOLEAN":34,"hash":35,"hash_repetition_plus0":36,"hashSegment":37,"ID":38,"EQUALS":39,"DATA":40,"pathSegments":41,"SEP":42,"$accept":0,"$end":1}, terminals_: {2:"error",5:"EOF",14:"CONTENT",15:"COMMENT",16:"OPEN_BLOCK",18:"CLOSE",19:"OPEN_INVERSE",20:"OPEN_ENDBLOCK",22:"OPEN",23:"OPEN_UNESCAPED",24:"CLOSE_UNESCAPED",25:"OPEN_PARTIAL",32:"STRING",33:"INTEGER",34:"BOOLEAN",38:"ID",39:"EQUALS",40:"DATA",42:"SEP"}, -productions_: [0,[3,2],[4,2],[4,3],[4,2],[4,1],[4,1],[4,0],[7,1],[7,2],[8,3],[8,3],[8,1],[8,1],[8,1],[8,1],[11,3],[9,3],[10,3],[12,3],[12,3],[13,4],[6,2],[17,3],[17,1],[31,1],[31,1],[31,1],[31,1],[31,1],[35,1],[37,3],[26,1],[26,1],[26,1],[30,2],[21,1],[41,3],[41,1],[27,0],[27,1],[28,0],[28,2],[29,0],[29,1],[36,1],[36,2]], +productions_: [0,[3,2],[6,2],[6,3],[6,2],[6,1],[6,1],[6,0],[4,1],[4,2],[8,3],[8,3],[8,1],[8,1],[8,1],[8,1],[11,3],[9,3],[10,3],[12,3],[12,3],[13,4],[7,2],[17,3],[17,1],[31,1],[31,1],[31,1],[31,1],[31,1],[35,1],[37,3],[26,1],[26,1],[26,1],[30,2],[21,1],[41,3],[41,1],[27,0],[27,1],[28,0],[28,2],[29,0],[29,1],[36,1],[36,2]], performAction: function anonymous(yytext,yyleng,yylineno,yy,yystate,$$,_$) { var $0 = $$.length - 1; switch (yystate) { -case 1: return $$[$0-1]; +case 1: return new yy.ProgramNode($$[$0-1]); break; case 2:this.$ = new yy.ProgramNode([], $$[$0]); break; @@ -284,8 +284,8 @@ case 46:$$[$0-1].push($$[$0]); break; } }, -table: [{3:1,4:2,5:[2,7],6:3,7:4,8:6,9:7,11:8,12:9,13:10,14:[1,11],15:[1,12],16:[1,13],19:[1,5],22:[1,14],23:[1,15],25:[1,16]},{1:[3]},{5:[1,17]},{5:[2,6],7:18,8:6,9:7,11:8,12:9,13:10,14:[1,11],15:[1,12],16:[1,13],19:[1,19],20:[2,6],22:[1,14],23:[1,15],25:[1,16]},{5:[2,5],6:20,8:21,9:7,11:8,12:9,13:10,14:[1,11],15:[1,12],16:[1,13],19:[1,5],20:[2,5],22:[1,14],23:[1,15],25:[1,16]},{17:23,18:[1,22],21:24,30:25,38:[1,28],40:[1,27],41:26},{5:[2,8],14:[2,8],15:[2,8],16:[2,8],19:[2,8],20:[2,8],22:[2,8],23:[2,8],25:[2,8]},{4:29,6:3,7:4,8:6,9:7,11:8,12:9,13:10,14:[1,11],15:[1,12],16:[1,13],19:[1,5],20:[2,7],22:[1,14],23:[1,15],25:[1,16]},{4:30,6:3,7:4,8:6,9:7,11:8,12:9,13:10,14:[1,11],15:[1,12],16:[1,13],19:[1,5],20:[2,7],22:[1,14],23:[1,15],25:[1,16]},{5:[2,12],14:[2,12],15:[2,12],16:[2,12],19:[2,12],20:[2,12],22:[2,12],23:[2,12],25:[2,12]},{5:[2,13],14:[2,13],15:[2,13],16:[2,13],19:[2,13],20:[2,13],22:[2,13],23:[2,13],25:[2,13]},{5:[2,14],14:[2,14],15:[2,14],16:[2,14],19:[2,14],20:[2,14],22:[2,14],23:[2,14],25:[2,14]},{5:[2,15],14:[2,15],15:[2,15],16:[2,15],19:[2,15],20:[2,15],22:[2,15],23:[2,15],25:[2,15]},{17:31,21:24,30:25,38:[1,28],40:[1,27],41:26},{17:32,21:24,30:25,38:[1,28],40:[1,27],41:26},{17:33,21:24,30:25,38:[1,28],40:[1,27],41:26},{21:35,26:34,32:[1,36],33:[1,37],38:[1,28],41:26},{1:[2,1]},{5:[2,2],8:21,9:7,11:8,12:9,13:10,14:[1,11],15:[1,12],16:[1,13],19:[1,19],20:[2,2],22:[1,14],23:[1,15],25:[1,16]},{17:23,21:24,30:25,38:[1,28],40:[1,27],41:26},{5:[2,4],7:38,8:6,9:7,11:8,12:9,13:10,14:[1,11],15:[1,12],16:[1,13],19:[1,19],20:[2,4],22:[1,14],23:[1,15],25:[1,16]},{5:[2,9],14:[2,9],15:[2,9],16:[2,9],19:[2,9],20:[2,9],22:[2,9],23:[2,9],25:[2,9]},{5:[2,22],14:[2,22],15:[2,22],16:[2,22],19:[2,22],20:[2,22],22:[2,22],23:[2,22],25:[2,22]},{18:[1,39]},{18:[2,41],24:[2,41],28:40,32:[2,41],33:[2,41],34:[2,41],38:[2,41],40:[2,41]},{18:[2,24],24:[2,24]},{18:[2,36],24:[2,36],32:[2,36],33:[2,36],34:[2,36],38:[2,36],40:[2,36],42:[1,41]},{21:42,38:[1,28],41:26},{18:[2,38],24:[2,38],32:[2,38],33:[2,38],34:[2,38],38:[2,38],40:[2,38],42:[2,38]},{10:43,20:[1,44]},{10:45,20:[1,44]},{18:[1,46]},{18:[1,47]},{24:[1,48]},{18:[2,39],21:50,27:49,38:[1,28],41:26},{18:[2,32],38:[2,32]},{18:[2,33],38:[2,33]},{18:[2,34],38:[2,34]},{5:[2,3],8:21,9:7,11:8,12:9,13:10,14:[1,11],15:[1,12],16:[1,13],19:[1,19],20:[2,3],22:[1,14],23:[1,15],25:[1,16]},{14:[2,17],15:[2,17],16:[2,17],19:[2,17],20:[2,17],22:[2,17],23:[2,17],25:[2,17]},{18:[2,43],21:54,24:[2,43],29:51,30:58,31:52,32:[1,55],33:[1,56],34:[1,57],35:53,36:59,37:60,38:[1,61],40:[1,27],41:26},{38:[1,62]},{18:[2,35],24:[2,35],32:[2,35],33:[2,35],34:[2,35],38:[2,35],40:[2,35]},{5:[2,10],14:[2,10],15:[2,10],16:[2,10],19:[2,10],20:[2,10],22:[2,10],23:[2,10],25:[2,10]},{21:63,38:[1,28],41:26},{5:[2,11],14:[2,11],15:[2,11],16:[2,11],19:[2,11],20:[2,11],22:[2,11],23:[2,11],25:[2,11]},{14:[2,16],15:[2,16],16:[2,16],19:[2,16],20:[2,16],22:[2,16],23:[2,16],25:[2,16]},{5:[2,19],14:[2,19],15:[2,19],16:[2,19],19:[2,19],20:[2,19],22:[2,19],23:[2,19],25:[2,19]},{5:[2,20],14:[2,20],15:[2,20],16:[2,20],19:[2,20],20:[2,20],22:[2,20],23:[2,20],25:[2,20]},{18:[1,64]},{18:[2,40]},{18:[2,23],24:[2,23]},{18:[2,42],24:[2,42],32:[2,42],33:[2,42],34:[2,42],38:[2,42],40:[2,42]},{18:[2,44],24:[2,44]},{18:[2,25],24:[2,25],32:[2,25],33:[2,25],34:[2,25],38:[2,25],40:[2,25]},{18:[2,26],24:[2,26],32:[2,26],33:[2,26],34:[2,26],38:[2,26],40:[2,26]},{18:[2,27],24:[2,27],32:[2,27],33:[2,27],34:[2,27],38:[2,27],40:[2,27]},{18:[2,28],24:[2,28],32:[2,28],33:[2,28],34:[2,28],38:[2,28],40:[2,28]},{18:[2,29],24:[2,29],32:[2,29],33:[2,29],34:[2,29],38:[2,29],40:[2,29]},{18:[2,30],24:[2,30],37:65,38:[1,66]},{18:[2,45],24:[2,45],38:[2,45]},{18:[2,38],24:[2,38],32:[2,38],33:[2,38],34:[2,38],38:[2,38],39:[1,67],40:[2,38],42:[2,38]},{18:[2,37],24:[2,37],32:[2,37],33:[2,37],34:[2,37],38:[2,37],40:[2,37],42:[2,37]},{18:[1,68]},{5:[2,21],14:[2,21],15:[2,21],16:[2,21],19:[2,21],20:[2,21],22:[2,21],23:[2,21],25:[2,21]},{18:[2,46],24:[2,46],38:[2,46]},{39:[1,67]},{21:54,30:58,31:69,32:[1,55],33:[1,56],34:[1,57],38:[1,28],40:[1,27],41:26},{5:[2,18],14:[2,18],15:[2,18],16:[2,18],19:[2,18],20:[2,18],22:[2,18],23:[2,18],25:[2,18]},{18:[2,31],24:[2,31],38:[2,31]}], -defaultActions: {17:[2,1],50:[2,40]}, +table: [{3:1,4:2,8:3,9:4,11:5,12:6,13:7,14:[1,8],15:[1,9],16:[1,11],19:[1,10],22:[1,12],23:[1,13],25:[1,14]},{1:[3]},{5:[1,15],8:16,9:4,11:5,12:6,13:7,14:[1,8],15:[1,9],16:[1,11],19:[1,10],22:[1,12],23:[1,13],25:[1,14]},{5:[2,8],14:[2,8],15:[2,8],16:[2,8],19:[2,8],20:[2,8],22:[2,8],23:[2,8],25:[2,8]},{4:19,6:17,7:18,8:3,9:4,11:5,12:6,13:7,14:[1,8],15:[1,9],16:[1,11],19:[1,20],20:[2,7],22:[1,12],23:[1,13],25:[1,14]},{4:19,6:21,7:18,8:3,9:4,11:5,12:6,13:7,14:[1,8],15:[1,9],16:[1,11],19:[1,20],20:[2,7],22:[1,12],23:[1,13],25:[1,14]},{5:[2,12],14:[2,12],15:[2,12],16:[2,12],19:[2,12],20:[2,12],22:[2,12],23:[2,12],25:[2,12]},{5:[2,13],14:[2,13],15:[2,13],16:[2,13],19:[2,13],20:[2,13],22:[2,13],23:[2,13],25:[2,13]},{5:[2,14],14:[2,14],15:[2,14],16:[2,14],19:[2,14],20:[2,14],22:[2,14],23:[2,14],25:[2,14]},{5:[2,15],14:[2,15],15:[2,15],16:[2,15],19:[2,15],20:[2,15],22:[2,15],23:[2,15],25:[2,15]},{17:22,21:23,30:24,38:[1,27],40:[1,26],41:25},{17:28,21:23,30:24,38:[1,27],40:[1,26],41:25},{17:29,21:23,30:24,38:[1,27],40:[1,26],41:25},{17:30,21:23,30:24,38:[1,27],40:[1,26],41:25},{21:32,26:31,32:[1,33],33:[1,34],38:[1,27],41:25},{1:[2,1]},{5:[2,9],14:[2,9],15:[2,9],16:[2,9],19:[2,9],20:[2,9],22:[2,9],23:[2,9],25:[2,9]},{10:35,20:[1,36]},{4:37,8:3,9:4,11:5,12:6,13:7,14:[1,8],15:[1,9],16:[1,11],19:[1,10],20:[2,6],22:[1,12],23:[1,13],25:[1,14]},{7:38,8:16,9:4,11:5,12:6,13:7,14:[1,8],15:[1,9],16:[1,11],19:[1,20],20:[2,5],22:[1,12],23:[1,13],25:[1,14]},{17:22,18:[1,39],21:23,30:24,38:[1,27],40:[1,26],41:25},{10:40,20:[1,36]},{18:[1,41]},{18:[2,41],24:[2,41],28:42,32:[2,41],33:[2,41],34:[2,41],38:[2,41],40:[2,41]},{18:[2,24],24:[2,24]},{18:[2,36],24:[2,36],32:[2,36],33:[2,36],34:[2,36],38:[2,36],40:[2,36],42:[1,43]},{21:44,38:[1,27],41:25},{18:[2,38],24:[2,38],32:[2,38],33:[2,38],34:[2,38],38:[2,38],40:[2,38],42:[2,38]},{18:[1,45]},{18:[1,46]},{24:[1,47]},{18:[2,39],21:49,27:48,38:[1,27],41:25},{18:[2,32],38:[2,32]},{18:[2,33],38:[2,33]},{18:[2,34],38:[2,34]},{5:[2,10],14:[2,10],15:[2,10],16:[2,10],19:[2,10],20:[2,10],22:[2,10],23:[2,10],25:[2,10]},{21:50,38:[1,27],41:25},{8:16,9:4,11:5,12:6,13:7,14:[1,8],15:[1,9],16:[1,11],19:[1,10],20:[2,2],22:[1,12],23:[1,13],25:[1,14]},{4:51,8:3,9:4,11:5,12:6,13:7,14:[1,8],15:[1,9],16:[1,11],19:[1,10],20:[2,4],22:[1,12],23:[1,13],25:[1,14]},{14:[2,22],15:[2,22],16:[2,22],19:[2,22],20:[2,22],22:[2,22],23:[2,22],25:[2,22]},{5:[2,11],14:[2,11],15:[2,11],16:[2,11],19:[2,11],20:[2,11],22:[2,11],23:[2,11],25:[2,11]},{14:[2,17],15:[2,17],16:[2,17],19:[2,17],20:[2,17],22:[2,17],23:[2,17],25:[2,17]},{18:[2,43],21:55,24:[2,43],29:52,30:59,31:53,32:[1,56],33:[1,57],34:[1,58],35:54,36:60,37:61,38:[1,62],40:[1,26],41:25},{38:[1,63]},{18:[2,35],24:[2,35],32:[2,35],33:[2,35],34:[2,35],38:[2,35],40:[2,35]},{14:[2,16],15:[2,16],16:[2,16],19:[2,16],20:[2,16],22:[2,16],23:[2,16],25:[2,16]},{5:[2,19],14:[2,19],15:[2,19],16:[2,19],19:[2,19],20:[2,19],22:[2,19],23:[2,19],25:[2,19]},{5:[2,20],14:[2,20],15:[2,20],16:[2,20],19:[2,20],20:[2,20],22:[2,20],23:[2,20],25:[2,20]},{18:[1,64]},{18:[2,40]},{18:[1,65]},{8:16,9:4,11:5,12:6,13:7,14:[1,8],15:[1,9],16:[1,11],19:[1,10],20:[2,3],22:[1,12],23:[1,13],25:[1,14]},{18:[2,23],24:[2,23]},{18:[2,42],24:[2,42],32:[2,42],33:[2,42],34:[2,42],38:[2,42],40:[2,42]},{18:[2,44],24:[2,44]},{18:[2,25],24:[2,25],32:[2,25],33:[2,25],34:[2,25],38:[2,25],40:[2,25]},{18:[2,26],24:[2,26],32:[2,26],33:[2,26],34:[2,26],38:[2,26],40:[2,26]},{18:[2,27],24:[2,27],32:[2,27],33:[2,27],34:[2,27],38:[2,27],40:[2,27]},{18:[2,28],24:[2,28],32:[2,28],33:[2,28],34:[2,28],38:[2,28],40:[2,28]},{18:[2,29],24:[2,29],32:[2,29],33:[2,29],34:[2,29],38:[2,29],40:[2,29]},{18:[2,30],24:[2,30],37:66,38:[1,67]},{18:[2,45],24:[2,45],38:[2,45]},{18:[2,38],24:[2,38],32:[2,38],33:[2,38],34:[2,38],38:[2,38],39:[1,68],40:[2,38],42:[2,38]},{18:[2,37],24:[2,37],32:[2,37],33:[2,37],34:[2,37],38:[2,37],40:[2,37],42:[2,37]},{5:[2,21],14:[2,21],15:[2,21],16:[2,21],19:[2,21],20:[2,21],22:[2,21],23:[2,21],25:[2,21]},{5:[2,18],14:[2,18],15:[2,18],16:[2,18],19:[2,18],20:[2,18],22:[2,18],23:[2,18],25:[2,18]},{18:[2,46],24:[2,46],38:[2,46]},{39:[1,68]},{21:55,30:59,31:69,32:[1,56],33:[1,57],34:[1,58],38:[1,27],40:[1,26],41:25},{18:[2,31],24:[2,31],38:[2,31]}], +defaultActions: {15:[2,1],49:[2,40]}, parseError: function parseError(str, hash) { throw new Error(str); }, diff --git a/spec/parser.js b/spec/parser.js index 3f2a0127f..06a60db37 100644 --- a/spec/parser.js +++ b/spec/parser.js @@ -136,6 +136,9 @@ describe('parser', function() { }); it("raises if there's a Parse error", function() { + (function() { + ast_for("foo{{^}}bar"); + }).should.throw(/Parse error on line 1/); (function() { ast_for("{{foo}"); }).should.throw(/Parse error on line 1/); diff --git a/src/handlebars.yy b/src/handlebars.yy index 8a89dff7b..5764f6f11 100644 --- a/src/handlebars.yy +++ b/src/handlebars.yy @@ -5,7 +5,7 @@ %% root - : program EOF { return $1; } + : statements EOF { return new yy.ProgramNode($1); } ; program From 5f664dd78b2c558a8a560b3fd0074109d52daf62 Mon Sep 17 00:00:00 2001 From: Yehuda Katz Date: Fri, 26 Jul 2013 16:50:37 +0000 Subject: [PATCH 047/200] Make the Handlebars environment into an object The idea is that the environment wraps up the mutable stuff in Handlebars (like the helpers) and that you could theoretically create a new one at any time and pass it in to Handlebars.template. Every test makes a new environment and uses it in template compilation. --- lib/handlebars.js | 11 ++++- lib/handlebars/base.js | 65 ++++++++++++++--------------- lib/handlebars/compiler/compiler.js | 6 +-- lib/handlebars/runtime.js | 16 ++++--- spec/basic.js | 6 +++ spec/env/node.js | 8 ++-- spec/env/runtime.js | 6 +-- spec/helpers.js | 13 +++--- 8 files changed, 70 insertions(+), 61 deletions(-) diff --git a/lib/handlebars.js b/lib/handlebars.js index b448658f2..346d730d3 100644 --- a/lib/handlebars.js +++ b/lib/handlebars.js @@ -1,4 +1,4 @@ -import { base as handlebars } from "./handlebars/base"; +import { HandlebarsEnvironment } from "./handlebars/base"; // Each of these augment the Handlebars object. No need to setup here. // (This is done to easily share code between commonjs and browse envs) @@ -8,7 +8,14 @@ import { template } from "./handlebars/runtime"; // For compatibility and usage outside of module systems, make the Handlebars object a namespace var create = function() { - var hb = handlebars(); + var hb = {}, + env = new HandlebarsEnvironment(); + + // support new environments in global namespace mode + hb.HandlebarsEnvironment = HandlebarsEnvironment; + + hb.registerHelper = env.registerHelper.bind(env); + hb.registerPartial = env.registerPartial.bind(env); hb.SafeString = SafeString; hb.Exception = Exception; diff --git a/lib/handlebars/base.js b/lib/handlebars/base.js index 0e3383eec..2e0411d9a 100644 --- a/lib/handlebars/base.js +++ b/lib/handlebars/base.js @@ -1,6 +1,6 @@ /*jshint eqnull: true */ -import { Exception, extend } from "./utils"; +import { Exception, extend, isEmpty } from "./utils"; var K = function() { return this; }; @@ -14,40 +14,41 @@ export var REVISION_CHANGES = { 4: '>= 1.0.0' }; -// TODO: Make this a class -export function base(helpers, partials) { +var toString = Object.prototype.toString, + functionType = '[object Function]', + objectType = '[object Object]'; - var exports = {}; +export function HandlebarsEnvironment(helpers, partials) { + this.helpers = helpers || {}; + this.partials = partials || {}; - var helpers = helpers || {}; - var partials = partials || {}; - - exports.helpers = helpers; - exports.partials = partials; + registerDefaultHelpers(this); +} - var toString = Object.prototype.toString, - functionType = '[object Function]', - objectType = '[object Object]'; +HandlebarsEnvironment.prototype = { + constructor: HandlebarsEnvironment, - exports.registerHelper = function(name, fn, inverse) { + registerHelper: function(name, fn, inverse) { if (toString.call(name) === objectType) { if (inverse || fn) { throw new Exception('Arg not supported with multiple helpers'); } - extend(helpers, name); + extend(this.helpers, name); } else { if (inverse) { fn.not = inverse; } - helpers[name] = fn; + this.helpers[name] = fn; } - }; + }, - exports.registerPartial = function(name, str) { + registerPartial: function(name, str) { if (toString.call(name) === objectType) { - extend(partials, name); + extend(this.partials, name); } else { - partials[name] = str; + this.partials[name] = str; } - }; + } +}; - exports.registerHelper('helperMissing', function(arg) { +function registerDefaultHelpers(instance) { + instance.registerHelper('helperMissing', function(arg) { if(arguments.length === 2) { return undefined; } else { @@ -55,7 +56,7 @@ export function base(helpers, partials) { } }); - exports.registerHelper('blockHelperMissing', function(context, options) { + instance.registerHelper('blockHelperMissing', function(context, options) { var inverse = options.inverse || function() {}, fn = options.fn; var type = toString.call(context); @@ -68,7 +69,7 @@ export function base(helpers, partials) { return inverse(this); } else if(type === "[object Array]") { if(context.length > 0) { - return Handlebars.helpers.each(context, options); + return instance.helpers.each(context, options); } else { return inverse(this); } @@ -77,7 +78,7 @@ export function base(helpers, partials) { } }); - exports.registerHelper('each', function(context, options) { + instance.registerHelper('each', function(context, options) { var fn = options.fn, inverse = options.inverse; var i = 0, ret = "", data; @@ -112,34 +113,32 @@ export function base(helpers, partials) { return ret; }); - exports.registerHelper('if', function(conditional, options) { + instance.registerHelper('if', function(conditional, options) { var type = toString.call(conditional); if(type === functionType) { conditional = conditional.call(this); } - if(!conditional || Handlebars.Utils.isEmpty(conditional)) { + if(!conditional || isEmpty(conditional)) { return options.inverse(this); } else { return options.fn(this); } }); - exports.registerHelper('unless', function(conditional, options) { - return Handlebars.helpers['if'].call(this, conditional, {fn: options.inverse, inverse: options.fn}); + instance.registerHelper('unless', function(conditional, options) { + return instance.helpers['if'].call(this, conditional, {fn: options.inverse, inverse: options.fn}); }); - exports.registerHelper('with', function(context, options) { + instance.registerHelper('with', function(context, options) { var type = toString.call(context); if(type === functionType) { context = context.call(this); } - if (!Handlebars.Utils.isEmpty(context)) return options.fn(context); + if (!isEmpty(context)) return options.fn(context); }); - exports.registerHelper('log', function(context, options) { + instance.registerHelper('log', function(context, options) { var level = options.data && options.data.level != null ? parseInt(options.data.level, 10) : 1; Handlebars.log(level, context); }); - - return exports; } var levels = { diff --git a/lib/handlebars/compiler/compiler.js b/lib/handlebars/compiler/compiler.js index b7796171a..6656a3fe9 100644 --- a/lib/handlebars/compiler/compiler.js +++ b/lib/handlebars/compiler/compiler.js @@ -443,17 +443,17 @@ export function compile(input, options) { var compiled; - function compile() { + function compileInput() { var ast = parse(input); var environment = new Compiler().compile(ast, options); var templateSpec = new JavaScriptCompiler().compile(environment, options, undefined, true); - return template(templateSpec); + return template(templateSpec, options.env || Handlebars, compile); } // Template is only compiled on first use and cached after that point. return function(context, options) { if (!compiled) { - compiled = compile(); + compiled = compileInput(); } return compiled.call(this, context, options); }; diff --git a/lib/handlebars/runtime.js b/lib/handlebars/runtime.js index 991bdfdcf..74361e864 100644 --- a/lib/handlebars/runtime.js +++ b/lib/handlebars/runtime.js @@ -3,17 +3,14 @@ import { COMPILER_REVISION, REVISION_CHANGES } from "./base"; // TODO: Remove this line and break up compilePartial -export function template(templateSpec, Hbars) { - // TODO: Make this less global - Hbars = Hbars || Handlebars; - - if (Hbars.compile) { +export function template(templateSpec, Hbars, compile) { + if (compile) { var invokePartialWrapper = function(partial, name, context, helpers, partials, data) { var result = invokePartial.apply(this, arguments); if (result) { return result; } var options = { helpers: helpers, partials: partials, data: data }; - partials[name] = Hbars.compile(partial, { data: data !== undefined }); + partials[name] = compile(partial, { data: data !== undefined }); return partials[name](context, options); }; } else { @@ -24,6 +21,10 @@ export function template(templateSpec, Hbars) { }; } + if (!Hbars) { + throw new Error("YUNO HANDLEBARS"); + } + // Just add water var container = { escapeExpression: escapeExpression, @@ -56,9 +57,6 @@ export function template(templateSpec, Hbars) { return function(context, options) { options = options || {}; - Hbars = Hbars || require("handlebars"); - - // TODO: Why does templateSpec require a reference to the global Handlebars? var result = templateSpec.call(container, Hbars, context, options.helpers, options.partials, options.data); var compilerInfo = container.compilerInfo || [], diff --git a/spec/basic.js b/spec/basic.js index a46636cfd..679cf26ef 100644 --- a/spec/basic.js +++ b/spec/basic.js @@ -1,3 +1,9 @@ +global.handlebarsEnv = null; + +beforeEach(function() { + global.handlebarsEnv = new Handlebars.HandlebarsEnvironment(); +}); + describe("basic context", function() { it("most basic", function() { shouldCompileTo("{{foo}}", { foo: "foo" }, "foo"); diff --git a/spec/env/node.js b/spec/env/node.js index e5349a154..351747973 100644 --- a/spec/env/node.js +++ b/spec/env/node.js @@ -3,12 +3,14 @@ require('./common'); global.Handlebars = require('../../zomg/lib/handlebars'); global.CompilerContext = { - compile: function(template, options) { + compile: function(template, options, env) { + env = env || handlebarsEnv; var templateSpec = Handlebars.precompile(template, options); - console.log(templateSpec); - return Handlebars.template(eval('(' + templateSpec + ')')); + return Handlebars.template(eval('(' + templateSpec + ')'), env); }, compileWithPartial: function(template, options) { + options = options || {}; + options.env = handlebarsEnv; return Handlebars.compile(template, options); } }; diff --git a/spec/env/runtime.js b/spec/env/runtime.js index fb4b342b3..19bd4ed03 100644 --- a/spec/env/runtime.js +++ b/spec/env/runtime.js @@ -5,11 +5,11 @@ global.Handlebars = require('../../dist/handlebars.runtime'); var compiler = require('../../lib/handlebars'); global.CompilerContext = { - compile: function(template, options) { + compile: function(template, options, env) { var templateSpec = compiler.precompile(template, options); - return Handlebars.template(eval('(' + templateSpec + ')')); + return Handlebars.template(eval('(' + templateSpec + ')'), env); }, - compileWithPartial: function(template, options) { + compileWithPartial: function(template, options, env) { return compiler.compile(template, options); } }; diff --git a/spec/helpers.js b/spec/helpers.js index fad6e5aa2..71f09d32d 100644 --- a/spec/helpers.js +++ b/spec/helpers.js @@ -176,15 +176,12 @@ describe('helpers', function() { var helpers = Handlebars.helpers; try { Handlebars.helpers = {}; - Handlebars.registerHelper('if', helpers['if']); - Handlebars.registerHelper('world', function() { return "world!"; }); - Handlebars.registerHelper('test_helper', function() { return 'found it!'; }); - //Handlebars.registerHelper({ - //'if': helpers['if'], - //world: function() { return "world!"; }, - //test_helper: function() { return 'found it!'; } - //}); + Handlebars.registerHelper({ + 'if': helpers['if'], + world: function() { return "world!"; }, + test_helper: function() { return 'found it!'; } + }); shouldCompileTo( "{{test_helper}} {{#if cruel}}Goodbye {{cruel}} {{world}}!{{/if}}", From d33408fcbdc5b23615ebc51b0b25a3d69818de4c Mon Sep 17 00:00:00 2001 From: Yehuda Katz Date: Fri, 26 Jul 2013 17:18:56 +0000 Subject: [PATCH 048/200] Move more testing mutations into the environment --- lib/handlebars.js | 3 ++- spec/builtins.js | 9 ++++++--- spec/data.js | 4 +--- spec/helpers.js | 31 +++++++++++++------------------ spec/partials.js | 4 ++-- 5 files changed, 24 insertions(+), 27 deletions(-) diff --git a/lib/handlebars.js b/lib/handlebars.js index 346d730d3..4b8674b03 100644 --- a/lib/handlebars.js +++ b/lib/handlebars.js @@ -1,4 +1,4 @@ -import { HandlebarsEnvironment } from "./handlebars/base"; +import { HandlebarsEnvironment, createFrame } from "./handlebars/base"; // Each of these augment the Handlebars object. No need to setup here. // (This is done to easily share code between commonjs and browse envs) @@ -23,6 +23,7 @@ var create = function() { hb.compile = compile; hb.precompile = precompile; hb.template = template; + hb.createFrame = createFrame; return hb; }; diff --git a/spec/builtins.js b/spec/builtins.js index 4379725b3..bf77d1a7f 100644 --- a/spec/builtins.js +++ b/spec/builtins.js @@ -47,6 +47,12 @@ describe('builtin helpers', function() { }); describe('#each', function() { + beforeEach(function() { + handlebarsEnv.registerHelper('detectDataInsideEach', function(options) { + return options.data && options.data.exclaim; + }); + }); + it("each", function() { var string = "{{#each goodbyes}}{{text}}! {{/each}}cruel {{world}}!"; var hash = {goodbyes: [{text: "goodbye"}, {text: "Goodbye"}, {text: "GOODBYE"}], world: "world"}; @@ -104,9 +110,6 @@ describe('builtin helpers', function() { equal(result, 'a!b!c!', 'should output data'); }); - Handlebars.registerHelper('detectDataInsideEach', function(options) { - return options.data && options.data.exclaim; - }); }); it("#log", function() { diff --git a/spec/data.js b/spec/data.js index 5af35b7eb..092c1df10 100644 --- a/spec/data.js +++ b/spec/data.js @@ -19,12 +19,10 @@ describe('data', function() { equals("hello", result, "@foo retrieves template data"); }); - var objectCreate = Handlebars.createFrame; - it("deep @foo triggers automatic top-level data", function() { var template = CompilerContext.compile('{{#let world="world"}}{{#if foo}}{{#if foo}}Hello {{@world}}{{/if}}{{/if}}{{/let}}'); - var helpers = objectCreate(Handlebars.helpers); + var helpers = Handlebars.createFrame(handlebarsEnv.helpers); helpers.let = function(options) { var frame = Handlebars.createFrame(options.data); diff --git a/spec/helpers.js b/spec/helpers.js index 71f09d32d..c5ea574bc 100644 --- a/spec/helpers.js +++ b/spec/helpers.js @@ -161,7 +161,7 @@ describe('helpers', function() { }); it("the helper hash should augment the global hash", function() { - Handlebars.registerHelper('test_helper', function() { return 'found it!'; }); + handlebarsEnv.registerHelper('test_helper', function() { return 'found it!'; }); shouldCompileTo( "{{test_helper}} {{#if cruel}}Goodbye {{cruel}} {{world}}!{{/if}}", [ @@ -173,26 +173,21 @@ describe('helpers', function() { }); it("Multiple global helper registration", function() { - var helpers = Handlebars.helpers; - try { - Handlebars.helpers = {}; + var helpers = handlebarsEnv.helpers; + handlebarsEnv.helpers = {}; - Handlebars.registerHelper({ - 'if': helpers['if'], - world: function() { return "world!"; }, - test_helper: function() { return 'found it!'; } - }); + handlebarsEnv.registerHelper({ + 'if': helpers['if'], + world: function() { return "world!"; }, + test_helper: function() { return 'found it!'; } + }); - shouldCompileTo( - "{{test_helper}} {{#if cruel}}Goodbye {{cruel}} {{world}}!{{/if}}", - [{cruel: "cruel"}], - "found it! Goodbye cruel world!!"); - } finally { - if (helpers) { - Handlebars.helpers = helpers; - } - } + shouldCompileTo( + "{{test_helper}} {{#if cruel}}Goodbye {{cruel}} {{world}}!{{/if}}", + [{cruel: "cruel"}], + "found it! Goodbye cruel world!!"); }); + it("negative number literals work", function() { var string = 'Message: {{hello -12}}'; var hash = {}; diff --git a/spec/partials.js b/spec/partials.js index d919eede1..fa2fa56d4 100644 --- a/spec/partials.js +++ b/spec/partials.js @@ -70,7 +70,7 @@ describe('partials', function() { }); it("Global Partials", function() { - Handlebars.registerPartial('global_test', '{{another_dude}}'); + handlebarsEnv.registerPartial('global_test', '{{another_dude}}'); var string = "Dudes: {{> shared/dude}} {{> global_test}}"; var dude = "{{name}}"; @@ -79,7 +79,7 @@ describe('partials', function() { }); it("Multiple partial registration", function() { - Handlebars.registerPartial({ + handlebarsEnv.registerPartial({ 'shared/dude': '{{name}}', global_test: '{{another_dude}}' }); From 6f580c9beb370c98ed5cec3f6b1d23eb3302d5d5 Mon Sep 17 00:00:00 2001 From: Yehuda Katz Date: Fri, 26 Jul 2013 18:08:32 +0000 Subject: [PATCH 049/200] Global Handlebars.compile uses its env The basic strategy is that there will be a global Handlebars object for the browser build, and that object will have a `compile` on it which uses its environment in the compiler. It will also be possible to glue things together manually by using the AMD build and passing the environment to `compile` directly. Some of these details are TBD. --- lib/handlebars.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/lib/handlebars.js b/lib/handlebars.js index 4b8674b03..7ca80a7a1 100644 --- a/lib/handlebars.js +++ b/lib/handlebars.js @@ -20,7 +20,11 @@ var create = function() { hb.SafeString = SafeString; hb.Exception = Exception; hb.Utils = { extend: extend, escapeExpression: escapeExpression, isEmpty: isEmpty }; - hb.compile = compile; + hb.compile = function(input, options) { + options = options || {}; + options.env = options.env || env; + return compile(input, options); + }; hb.precompile = precompile; hb.template = template; hb.createFrame = createFrame; From 3bc7d7b9989a504148f875f4ef6bd786066f4299 Mon Sep 17 00:00:00 2001 From: kpdecker Date: Mon, 29 Jul 2013 21:30:42 -0500 Subject: [PATCH 050/200] ID lexer control class --- dist/handlebars.js | 2 +- src/handlebars.l | 22 ++++++++++++---------- 2 files changed, 13 insertions(+), 11 deletions(-) diff --git a/dist/handlebars.js b/dist/handlebars.js index 90f20ecdb..f8eb1f2ae 100644 --- a/dist/handlebars.js +++ b/dist/handlebars.js @@ -640,7 +640,7 @@ case 31:return 5; break; } }; -lexer.rules = [/^(?:\\\\(?=(\{\{)))/,/^(?:[^\x00]*?(?=(\{\{)))/,/^(?:[^\x00]+)/,/^(?:[^\x00]{2,}?(?=(\{\{|$)))/,/^(?:[\s\S]*?--\}\})/,/^(?:\{\{>)/,/^(?:\{\{#)/,/^(?:\{\{\/)/,/^(?:\{\{\^)/,/^(?:\{\{\s*else\b)/,/^(?:\{\{\{)/,/^(?:\{\{&)/,/^(?:\{\{!--)/,/^(?:\{\{![\s\S]*?\}\})/,/^(?:\{\{)/,/^(?:=)/,/^(?:\.(?=[}\/ ]))/,/^(?:\.\.)/,/^(?:[\/.])/,/^(?:\s+)/,/^(?:\}\}\})/,/^(?:\}\})/,/^(?:"(\\["]|[^"])*")/,/^(?:'(\\[']|[^'])*')/,/^(?:@)/,/^(?:true(?=[}\s]))/,/^(?:false(?=[}\s]))/,/^(?:-?[0-9]+(?=[}\s]))/,/^(?:[^\s!"#%-,\.\/;->@\[-\^`\{-~]+(?=[=}\s\/.]))/,/^(?:\[[^\]]*\])/,/^(?:.)/,/^(?:$)/]; +lexer.rules = [/^(?:\\\\(?=(\{\{)))/,/^(?:[^\x00]*?(?=(\{\{)))/,/^(?:[^\x00]+)/,/^(?:[^\x00]{2,}?(?=(\{\{|$)))/,/^(?:[\s\S]*?--\}\})/,/^(?:\{\{>)/,/^(?:\{\{#)/,/^(?:\{\{\/)/,/^(?:\{\{\^)/,/^(?:\{\{\s*else\b)/,/^(?:\{\{\{)/,/^(?:\{\{&)/,/^(?:\{\{!--)/,/^(?:\{\{![\s\S]*?\}\})/,/^(?:\{\{)/,/^(?:=)/,/^(?:\.(?=[}\/ ]))/,/^(?:\.\.)/,/^(?:[\/.])/,/^(?:\s+)/,/^(?:\}\}\})/,/^(?:\}\})/,/^(?:"(\\["]|[^"])*")/,/^(?:'(\\[']|[^'])*')/,/^(?:@)/,/^(?:true(?=[}\s]))/,/^(?:false(?=[}\s]))/,/^(?:-?[0-9]+(?=[}\s]))/,/^(?:([^\s!"#%-,\.\/;->@\[-\^`\{-~]+(?=[=}\s\/.])))/,/^(?:\[[^\]]*\])/,/^(?:.)/,/^(?:$)/]; lexer.conditions = {"mu":{"rules":[5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31],"inclusive":false},"emu":{"rules":[3],"inclusive":false},"com":{"rules":[4],"inclusive":false},"INITIAL":{"rules":[0,1,2,31],"inclusive":true}}; return lexer;})() parser.lexer = lexer; diff --git a/src/handlebars.l b/src/handlebars.l index 5fd0bcdaf..d13e111af 100644 --- a/src/handlebars.l +++ b/src/handlebars.l @@ -1,6 +1,17 @@ %x mu emu com +/* +ID is the inverse of control characters. +Control characters ranges: + [\s] Whitespace + [!"#%-,\./] !, ", #, %, &, ', (, ), *, +, ,, ., /, Exceptions in range: $, - + [;->@] ;, <, =, >, @, Exceptions in range: :, ? + [\[-\^`] [, \, ], ^, `, Exceptions in range: _ + [\{-~] {, |, }, ~ +*/ +ID [^\s!"#%-,\.\/;->@\[-\^`\{-~]+/[=}\s\/.] + %% "\\\\"/("{{") yytext = "\\"; return 'CONTENT'; @@ -45,16 +56,7 @@ "false"/[}\s] return 'BOOLEAN'; \-?[0-9]+/[}\s] return 'INTEGER'; -/* -ID is the inverse of control characters. -Control characters ranges: - [\s] Whitespace - [!"#%-,\./] !, ", #, %, &, ', (, ), *, +, ,, ., /, Exceptions in range: $, - - [;->@] ;, <, =, >, @, Exceptions in range: :, ? - [\[-\^`] [, \, ], ^, `, Exceptions in range: _ - [\{-~] {, |, }, ~ -*/ -[^\s!"#%-,\.\/;->@\[-\^`\{-~]+/[=}\s\/.] return 'ID'; +{ID} return 'ID'; '['[^\]]*']' yytext = yytext.substr(1, yyleng-2); return 'ID'; . return 'INVALID'; From 03310df95e91880ea647f38805dc9694a3258a65 Mon Sep 17 00:00:00 2001 From: kpdecker Date: Mon, 29 Jul 2013 21:37:04 -0500 Subject: [PATCH 051/200] Add strip lex helper method --- dist/handlebars.js | 20 +++++++++++++------- src/handlebars.l | 24 ++++++++++++++++-------- 2 files changed, 29 insertions(+), 15 deletions(-) diff --git a/dist/handlebars.js b/dist/handlebars.js index f8eb1f2ae..be599eb40 100644 --- a/dist/handlebars.js +++ b/dist/handlebars.js @@ -564,13 +564,19 @@ pushState:function begin(condition) { lexer.options = {}; lexer.performAction = function anonymous(yy,yy_,$avoiding_name_collisions,YY_START) { + +function strip(start, end) { + return yy_.yytext = yy_.yytext.substr(start, yy_.yyleng-end); +} + + var YYSTATE=YY_START switch($avoiding_name_collisions) { case 0:yy_.yytext = "\\"; return 14; break; case 1: if(yy_.yytext.slice(-1) !== "\\") this.begin("mu"); - if(yy_.yytext.slice(-1) === "\\") yy_.yytext = yy_.yytext.substr(0,yy_.yyleng-1), this.begin("emu"); + if(yy_.yytext.slice(-1) === "\\") strip(0,1), this.begin("emu"); if(yy_.yytext) return 14; break; @@ -578,11 +584,11 @@ case 2:return 14; break; case 3: if(yy_.yytext.slice(-1) !== "\\") this.popState(); - if(yy_.yytext.slice(-1) === "\\") yy_.yytext = yy_.yytext.substr(0,yy_.yyleng-1); + if(yy_.yytext.slice(-1) === "\\") strip(0,1); return 14; break; -case 4:yy_.yytext = yy_.yytext.substr(0, yy_.yyleng-4); this.popState(); return 15; +case 4:strip(0,4); this.popState(); return 15; break; case 5:return 25; break; @@ -600,7 +606,7 @@ case 11:return 22; break; case 12:this.popState(); this.begin('com'); break; -case 13:yy_.yytext = yy_.yytext.substr(3,yy_.yyleng-5); this.popState(); return 15; +case 13:strip(3,5); this.popState(); return 15; break; case 14:return 22; break; @@ -618,9 +624,9 @@ case 20:this.popState(); return 24; break; case 21:this.popState(); return 18; break; -case 22:yy_.yytext = yy_.yytext.substr(1,yy_.yyleng-2).replace(/\\"/g,'"'); return 32; +case 22:yy_.yytext = strip(1,2).replace(/\\"/g,'"'); return 32; break; -case 23:yy_.yytext = yy_.yytext.substr(1,yy_.yyleng-2).replace(/\\'/g,"'"); return 32; +case 23:yy_.yytext = strip(1,2).replace(/\\'/g,"'"); return 32; break; case 24:return 40; break; @@ -632,7 +638,7 @@ case 27:return 33; break; case 28:return 38; break; -case 29:yy_.yytext = yy_.yytext.substr(1, yy_.yyleng-2); return 38; +case 29:yy_.yytext = strip(1,2); return 38; break; case 30:return 'INVALID'; break; diff --git a/src/handlebars.l b/src/handlebars.l index d13e111af..018096b01 100644 --- a/src/handlebars.l +++ b/src/handlebars.l @@ -1,6 +1,15 @@ %x mu emu com +%{ + +function strip(start, end) { + return yytext = yytext.substr(start, yyleng-end); +} + +%} + + /* ID is the inverse of control characters. Control characters ranges: @@ -17,7 +26,7 @@ ID [^\s!"#%-,\.\/;->@\[-\^`\{-~]+/[=}\s\/.] "\\\\"/("{{") yytext = "\\"; return 'CONTENT'; [^\x00]*?/("{{") { if(yytext.slice(-1) !== "\\") this.begin("mu"); - if(yytext.slice(-1) === "\\") yytext = yytext.substr(0,yyleng-1), this.begin("emu"); + if(yytext.slice(-1) === "\\") strip(0,1), this.begin("emu"); if(yytext) return 'CONTENT'; } @@ -25,11 +34,11 @@ ID [^\s!"#%-,\.\/;->@\[-\^`\{-~]+/[=}\s\/.] [^\x00]{2,}?/("{{"|<>) { if(yytext.slice(-1) !== "\\") this.popState(); - if(yytext.slice(-1) === "\\") yytext = yytext.substr(0,yyleng-1); + if(yytext.slice(-1) === "\\") strip(0,1); return 'CONTENT'; } -[\s\S]*?"--}}" yytext = yytext.substr(0, yyleng-4); this.popState(); return 'COMMENT'; +[\s\S]*?"--}}" strip(0,4); this.popState(); return 'COMMENT'; "{{>" return 'OPEN_PARTIAL'; "{{#" return 'OPEN_BLOCK'; @@ -39,7 +48,7 @@ ID [^\s!"#%-,\.\/;->@\[-\^`\{-~]+/[=}\s\/.] "{{{" return 'OPEN_UNESCAPED'; "{{&" return 'OPEN'; "{{!--" this.popState(); this.begin('com'); -"{{!"[\s\S]*?"}}" yytext = yytext.substr(3,yyleng-5); this.popState(); return 'COMMENT'; +"{{!"[\s\S]*?"}}" strip(3,5); this.popState(); return 'COMMENT'; "{{" return 'OPEN'; "=" return 'EQUALS'; @@ -49,8 +58,8 @@ ID [^\s!"#%-,\.\/;->@\[-\^`\{-~]+/[=}\s\/.] \s+ /*ignore whitespace*/ "}}}" this.popState(); return 'CLOSE_UNESCAPED'; "}}" this.popState(); return 'CLOSE'; -'"'("\\"["]|[^"])*'"' yytext = yytext.substr(1,yyleng-2).replace(/\\"/g,'"'); return 'STRING'; -"'"("\\"[']|[^'])*"'" yytext = yytext.substr(1,yyleng-2).replace(/\\'/g,"'"); return 'STRING'; +'"'("\\"["]|[^"])*'"' yytext = strip(1,2).replace(/\\"/g,'"'); return 'STRING'; +"'"("\\"[']|[^'])*"'" yytext = strip(1,2).replace(/\\'/g,"'"); return 'STRING'; "@" return 'DATA'; "true"/[}\s] return 'BOOLEAN'; "false"/[}\s] return 'BOOLEAN'; @@ -58,8 +67,7 @@ ID [^\s!"#%-,\.\/;->@\[-\^`\{-~]+/[=}\s\/.] {ID} return 'ID'; -'['[^\]]*']' yytext = yytext.substr(1, yyleng-2); return 'ID'; +'['[^\]]*']' yytext = strip(1,2); return 'ID'; . return 'INVALID'; <> return 'EOF'; - From 9ca4f9c606cf562372d1123def592f27ba1a4928 Mon Sep 17 00:00:00 2001 From: kpdecker Date: Tue, 30 Jul 2013 11:07:41 -0500 Subject: [PATCH 052/200] Remove unused var --- dist/handlebars.js | 6 +++--- lib/handlebars/compiler/javascript-compiler.js | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/dist/handlebars.js b/dist/handlebars.js index be599eb40..4f1b1e164 100644 --- a/dist/handlebars.js +++ b/dist/handlebars.js @@ -1667,10 +1667,10 @@ JavaScriptCompiler.prototype = { // [lookupData] // // On stack, before: ... - // On stack, after: data[id], ... + // On stack, after: data, ... // - // Push the result of looking up `id` on the current data - lookupData: function(id) { + // Push the data lookup operator + lookupData: function() { this.push('data'); }, diff --git a/lib/handlebars/compiler/javascript-compiler.js b/lib/handlebars/compiler/javascript-compiler.js index 4548c6aeb..0bb181697 100644 --- a/lib/handlebars/compiler/javascript-compiler.js +++ b/lib/handlebars/compiler/javascript-compiler.js @@ -350,10 +350,10 @@ JavaScriptCompiler.prototype = { // [lookupData] // // On stack, before: ... - // On stack, after: data[id], ... + // On stack, after: data, ... // - // Push the result of looking up `id` on the current data - lookupData: function(id) { + // Push the data lookup operator + lookupData: function() { this.push('data'); }, From 4f5c05ffe9117617b652c37e313b462b9f6f12ae Mon Sep 17 00:00:00 2001 From: kpdecker Date: Tue, 30 Jul 2013 11:08:46 -0500 Subject: [PATCH 053/200] Simplify inverse only block case --- dist/handlebars.js | 2 +- spec/parser.js | 4 ++-- src/handlebars.yy | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/dist/handlebars.js b/dist/handlebars.js index 4f1b1e164..e0a836818 100644 --- a/dist/handlebars.js +++ b/dist/handlebars.js @@ -205,7 +205,7 @@ case 4:this.$ = new yy.ProgramNode($$[$0-1], []); break; case 5:this.$ = new yy.ProgramNode($$[$0]); break; -case 6:this.$ = new yy.ProgramNode([], []); +case 6:this.$ = new yy.ProgramNode([]); break; case 7:this.$ = new yy.ProgramNode([]); break; diff --git a/spec/parser.js b/spec/parser.js index 06a60db37..3397105fd 100644 --- a/spec/parser.js +++ b/spec/parser.js @@ -108,11 +108,11 @@ describe('parser', function() { }); it('parses empty blocks with empty inverse section', function() { - ast_for("{{#foo}}{{^}}{{/foo}}").should.equal("BLOCK:\n {{ ID:foo [] }}\n PROGRAM:\n {{^}}\n"); + ast_for("{{#foo}}{{^}}{{/foo}}").should.equal("BLOCK:\n {{ ID:foo [] }}\n PROGRAM:\n"); }); it('parses empty blocks with empty inverse (else-style) section', function() { - ast_for("{{#foo}}{{else}}{{/foo}}").should.equal("BLOCK:\n {{ ID:foo [] }}\n PROGRAM:\n {{^}}\n"); + ast_for("{{#foo}}{{else}}{{/foo}}").should.equal("BLOCK:\n {{ ID:foo [] }}\n PROGRAM:\n"); }); it('parses non-empty blocks with empty inverse section', function() { diff --git a/src/handlebars.yy b/src/handlebars.yy index 5764f6f11..d2f24c45d 100644 --- a/src/handlebars.yy +++ b/src/handlebars.yy @@ -13,7 +13,7 @@ program | statements simpleInverse statements -> new yy.ProgramNode($1, $3) | statements simpleInverse -> new yy.ProgramNode($1, []) | statements -> new yy.ProgramNode($1) - | simpleInverse -> new yy.ProgramNode([], []) + | simpleInverse -> new yy.ProgramNode([]) | "" -> new yy.ProgramNode([]) ; From e62999f9ece7d9218b9768a908f8df9c11d7e920 Mon Sep 17 00:00:00 2001 From: kpdecker Date: Tue, 30 Jul 2013 11:08:58 -0500 Subject: [PATCH 054/200] Improve uglify compression --- Rakefile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Rakefile b/Rakefile index 30187d743..d1b703a2d 100644 --- a/Rakefile +++ b/Rakefile @@ -79,8 +79,8 @@ task :build => [:compile] do |task| Rake::Task["dist/handlebars.js"].execute Rake::Task["dist/handlebars.runtime.js"].execute - system "./node_modules/.bin/uglifyjs --comments -o dist/handlebars.min.js dist/handlebars.js" - system "./node_modules/.bin/uglifyjs --comments -o dist/handlebars.runtime.min.js dist/handlebars.runtime.js" + system "./node_modules/.bin/uglifyjs -m -c --comments -o dist/handlebars.min.js dist/handlebars.js" + system "./node_modules/.bin/uglifyjs -m -c --comments -o dist/handlebars.runtime.min.js dist/handlebars.runtime.js" end # Updates the various version numbers. From 7c1ce99ebac314cf6e9c06d2ac47f62b187cac04 Mon Sep 17 00:00:00 2001 From: Jon Schlinkert Date: Tue, 6 Aug 2013 19:20:31 -0400 Subject: [PATCH 055/200] adds Assemble and handlebars-helpers projects to "Handlebars in the Wild" section of README. Bullets are now ordered alphabetically --- README.markdown | 33 +++++++++++++++++++++++---------- 1 file changed, 23 insertions(+), 10 deletions(-) diff --git a/README.markdown b/README.markdown index a279c1acc..f593a689e 100644 --- a/README.markdown +++ b/README.markdown @@ -355,28 +355,41 @@ Known Issues * Using a variable, helper, or partial named `class` causes errors in IE browsers. (Instead, use `className`) Handlebars in the Wild ------------------ +---------------------- + +* [Assemble](http://assemble.io), by [@jonschlinkert](https://github.com/jonschlinkert) + and [@doowb](https://github.com/doowb), is a static site generator that uses Handlebars.js + as its template engine. +* [Ember.js](http://www.emberjs.com) makes Handlebars.js the primary way to + structure your views, also with automatic data binding support. +* [handlebars_assets](http://github.com/leshill/handlebars_assets): A Rails Asset Pipeline gem + from Les Hill (@leshill). +* [handlebars-helpers](https://github.com/assemble/handlebars-helpers) is an extensive library + with 100+ handlebars helpers. +* [hbs](http://github.com/donpark/hbs): An Express.js view engine adapter for Handlebars.js, + from Don Park. * [jblotus](http://github.com/jblotus) created [http://tryhandlebarsjs.com](http://tryhandlebarsjs.com) for anyone who would like to try out Handlebars.js in their browser. -* Don Park wrote an Express.js view engine adapter for Handlebars.js called - [hbs](http://github.com/donpark/hbs). +* [jQuery plugin](http://71104.github.io/jquery-handlebars/): allows you to use + Handlebars.js with [jQuery](http://jquery.com/). +* [Lumbar](walmartlabs.github.io/lumbar) provides easy module-based template management for + handlebars projects. * [sammy.js](http://github.com/quirkey/sammy) by Aaron Quint, a.k.a. quirkey, supports Handlebars.js as one of its template plugins. * [SproutCore](http://www.sproutcore.com) uses Handlebars.js as its main templating engine, extending it with automatic data binding support. -* [Ember.js](http://www.emberjs.com) makes Handlebars.js the primary way to - structure your views, also with automatic data binding support. -* Les Hill (@leshill) wrote a Rails Asset Pipeline gem named - [handlebars_assets](http://github.com/leshill/handlebars_assets). -* [Gist about Synchronous and asynchronous loading of external handlebars templates](https://gist.github.com/2287070) -* [Lumbar](walmartlabs.github.io/lumbar) provides easy module-based template management for handlebars projects. * [YUI](http://yuilibrary.com/yui/docs/handlebars/) implements a port of handlebars -* This [jQuery plugin](http://71104.github.io/jquery-handlebars/) allows you to use Handlebars.js with [jQuery](http://jquery.com/). + +External Resources +------------------ + +* [Gist about Synchronous and asynchronous loading of external handlebars templates](https://gist.github.com/2287070) Have a project using Handlebars? Send us a [pull request](https://github.com/wycats/handlebars.js/pull/new/master)! Helping Out ----------- + To build Handlebars.js you'll need a few things installed. * Node.js From 860853ddb4ed4cce3a54bbf070bdfb2e07320633 Mon Sep 17 00:00:00 2001 From: Kevin Decker Date: Wed, 14 Aug 2013 13:11:01 -0400 Subject: [PATCH 056/200] Update external url --- README.markdown | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.markdown b/README.markdown index f593a689e..781113d07 100644 --- a/README.markdown +++ b/README.markdown @@ -372,7 +372,7 @@ Handlebars in the Wild for anyone who would like to try out Handlebars.js in their browser. * [jQuery plugin](http://71104.github.io/jquery-handlebars/): allows you to use Handlebars.js with [jQuery](http://jquery.com/). -* [Lumbar](walmartlabs.github.io/lumbar) provides easy module-based template management for +* [Lumbar](http://walmartlabs.github.io/lumbar) provides easy module-based template management for handlebars projects. * [sammy.js](http://github.com/quirkey/sammy) by Aaron Quint, a.k.a. quirkey, supports Handlebars.js as one of its template plugins. From db47593e349ca5226b70a61405d6177b63c19119 Mon Sep 17 00:00:00 2001 From: Tuomas Palenius Date: Thu, 15 Aug 2013 06:05:11 +0300 Subject: [PATCH 057/200] Fix #597. If-helper doesn't anymore consider 0 as falsy value. --- lib/handlebars/base.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/handlebars/base.js b/lib/handlebars/base.js index 44a369c5e..b8e11eb2f 100644 --- a/lib/handlebars/base.js +++ b/lib/handlebars/base.js @@ -137,7 +137,7 @@ Handlebars.registerHelper('if', function(conditional, options) { var type = toString.call(conditional); if(type === functionType) { conditional = conditional.call(this); } - if(!conditional || Handlebars.Utils.isEmpty(conditional)) { + if(Handlebars.Utils.isEmpty(conditional)) { return options.inverse(this); } else { return options.fn(this); From d02c90c0fbc984523a7a8950e15435938979acbe Mon Sep 17 00:00:00 2001 From: Parker Selbert Date: Thu, 23 May 2013 16:18:53 -0500 Subject: [PATCH 058/200] Use the ('' + string) form of string coercion Using string.toString() will throw errors in current versions of Safari (6.0.5 currently) for some values. The error is a particularly cryptic "Type Error: type error", which no indication as to the value that caused the error. By using the '' + string form of coercion the error doesn't seem to occur. Depending on the browser used there is a sizable performance increase in using the concatenation form of coercion. In instances where there is not a performance improvement (i.e. Firefox), the speed difference is entirely negligable. See: http://jsperf.com/convert-to-string-bj/3 --- lib/handlebars/utils.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/handlebars/utils.js b/lib/handlebars/utils.js index 1e0e4c902..e104dcf7c 100644 --- a/lib/handlebars/utils.js +++ b/lib/handlebars/utils.js @@ -21,7 +21,7 @@ Handlebars.SafeString = function(string) { this.string = string; }; Handlebars.SafeString.prototype.toString = function() { - return this.string.toString(); + return "" + this.string; }; var escape = { @@ -60,7 +60,7 @@ Handlebars.Utils = { // Force a string conversion as this will be done by the append regardless and // the regex test will do this transparently behind the scenes, causing issues if // an object's to string has escaped characters in it. - string = string.toString(); + string = "" + string; if(!possible.test(string)) { return string; } return string.replace(badChars, escapeChar); From 534d0ebcc3c242b539b2c37a54dbae8d81255eca Mon Sep 17 00:00:00 2001 From: kpdecker Date: Thu, 15 Aug 2013 10:29:37 -0500 Subject: [PATCH 059/200] Add test case --- spec/builtins.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/spec/builtins.js b/spec/builtins.js index 4379725b3..a29616f98 100644 --- a/spec/builtins.js +++ b/spec/builtins.js @@ -20,6 +20,8 @@ describe('builtin helpers', function() { "if with non-empty array shows the contents"); shouldCompileTo(string, {goodbye: [], world: "world"}, "cruel world!", "if with empty array does not show the contents"); + shouldCompileTo(string, {goodbye: 0, world: "world"}, "GOODBYE cruel world!", + "if with zero does show the contents"); }); it("if with function argument", function() { From 0b92db7b0ccd5c347cfe2f14571f998b84185a2e Mon Sep 17 00:00:00 2001 From: kpdecker Date: Thu, 15 Aug 2013 10:30:15 -0500 Subject: [PATCH 060/200] Rebuild --- dist/handlebars.js | 6 +++--- dist/handlebars.runtime.js | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/dist/handlebars.js b/dist/handlebars.js index e0a836818..1942eb533 100644 --- a/dist/handlebars.js +++ b/dist/handlebars.js @@ -160,7 +160,7 @@ Handlebars.registerHelper('if', function(conditional, options) { var type = toString.call(conditional); if(type === functionType) { conditional = conditional.call(this); } - if(!conditional || Handlebars.Utils.isEmpty(conditional)) { + if(Handlebars.Utils.isEmpty(conditional)) { return options.inverse(this); } else { return options.fn(this); @@ -813,7 +813,7 @@ Handlebars.SafeString = function(string) { this.string = string; }; Handlebars.SafeString.prototype.toString = function() { - return this.string.toString(); + return "" + this.string; }; var escape = { @@ -852,7 +852,7 @@ Handlebars.Utils = { // Force a string conversion as this will be done by the append regardless and // the regex test will do this transparently behind the scenes, causing issues if // an object's to string has escaped characters in it. - string = string.toString(); + string = "" + string; if(!possible.test(string)) { return string; } return string.replace(badChars, escapeChar); diff --git a/dist/handlebars.runtime.js b/dist/handlebars.runtime.js index fac2cc5a4..947b8f076 100644 --- a/dist/handlebars.runtime.js +++ b/dist/handlebars.runtime.js @@ -160,7 +160,7 @@ Handlebars.registerHelper('if', function(conditional, options) { var type = toString.call(conditional); if(type === functionType) { conditional = conditional.call(this); } - if(!conditional || Handlebars.Utils.isEmpty(conditional)) { + if(Handlebars.Utils.isEmpty(conditional)) { return options.inverse(this); } else { return options.fn(this); @@ -202,7 +202,7 @@ Handlebars.SafeString = function(string) { this.string = string; }; Handlebars.SafeString.prototype.toString = function() { - return this.string.toString(); + return "" + this.string; }; var escape = { @@ -241,7 +241,7 @@ Handlebars.Utils = { // Force a string conversion as this will be done by the append regardless and // the regex test will do this transparently behind the scenes, causing issues if // an object's to string has escaped characters in it. - string = string.toString(); + string = "" + string; if(!possible.test(string)) { return string; } return string.replace(badChars, escapeChar); From 182ba044bb8ff02b9ad36e533e6d769efabe031d Mon Sep 17 00:00:00 2001 From: kpdecker Date: Thu, 15 Aug 2013 10:33:45 -0500 Subject: [PATCH 061/200] Update release notes --- release-notes.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/release-notes.md b/release-notes.md index d2800f925..e7f5ca0f6 100644 --- a/release-notes.md +++ b/release-notes.md @@ -2,6 +2,8 @@ ## Development +- [#602](https://github.com/wycats/handlebars.js/pull/602) - Handle zero uniformly between mustache conditionals and the if helper ([@artiee](https://github.com/artiee)) +- [#535](https://github.com/wycats/handlebars.js/pull/535) - Fix for probably JIT error under Safari. ([@sorentwo](https://github.com/sorentwo)) - [#537](https://github.com/wycats/handlebars.js/issues/537) - Add CommonJS and AMD loader support ([@spikebrehm](https://github.com/spikebrehm)) - [#544](https://github.com/wycats/handlebars.js/issues/544) - Push travis builds to build server ([@fivetanley](https://github.com/fivetanley)) From 87b5d4ee61605b026506e92c9e8873d867c5f150 Mon Sep 17 00:00:00 2001 From: kpdecker Date: Sat, 17 Aug 2013 09:21:25 -0500 Subject: [PATCH 062/200] Update npmignore for new files --- .npmignore | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/.npmignore b/.npmignore index 2f42c9f44..a43e7ecf4 100644 --- a/.npmignore +++ b/.npmignore @@ -1,9 +1,14 @@ .DS_Store .gitignore .rvmrc +.jshintrc +.travis.yml +.rspec Gemfile Gemfile.lock Rakefile +*.gemspec +*.nuspec bench/* spec/* src/* From 3d77d172ecba90a09e4e66518b425ac17b35fe51 Mon Sep 17 00:00:00 2001 From: kpdecker Date: Sat, 17 Aug 2013 11:57:50 -0500 Subject: [PATCH 063/200] Move dist dir generation into Grunt --- Gruntfile.js | 67 ++++++++++++++++++++++++++++++++++++++ README.markdown | 1 + Rakefile | 47 +------------------------- dist/handlebars.js | 5 +-- dist/handlebars.runtime.js | 5 +-- package.json | 3 ++ 6 files changed, 78 insertions(+), 50 deletions(-) create mode 100644 Gruntfile.js diff --git a/Gruntfile.js b/Gruntfile.js new file mode 100644 index 000000000..6d6f7adb6 --- /dev/null +++ b/Gruntfile.js @@ -0,0 +1,67 @@ +module.exports = function(grunt) { + grunt.initConfig({ + pkg: grunt.file.readJSON('package.json'), + + concat: { + options: { + banner: '/*!\n\n <%= pkg.name %> v<%= pkg.version %>\n\n<%= grunt.file.read("LICENSE") %>\n@license\n*/\n', + process: function(src, name) { + var match = /\/\/ BEGIN\(BROWSER\)\n((?:.|\n)*)\n\/\/ END\(BROWSER\)/.exec(src); + return '\n// ' + name + '\n' + (match ? match[1] : src); + }, + separator: ';' + }, + dist: { + src: [ + 'lib/handlebars/browser-prefix.js', + 'lib/handlebars/base.js', + 'lib/handlebars/compiler/parser.js', + 'lib/handlebars/compiler/base.js', + 'lib/handlebars/compiler/ast.js', + 'lib/handlebars/utils.js', + 'lib/handlebars/compiler/compiler.js', + 'lib/handlebars/compiler/javascript-compiler.js', + 'lib/handlebars/runtime.js', + 'lib/handlebars/browser-suffix.js' + ], + dest: 'dist/handlebars.js' + }, + runtime: { + src: [ + 'lib/handlebars/browser-prefix.js', + 'lib/handlebars/base.js', + 'lib/handlebars/utils.js', + 'lib/handlebars/runtime.js', + 'lib/handlebars/browser-suffix.js' + ], + dest: 'dist/handlebars.runtime.js' + } + }, + uglify: { + options: { + mangle: true, + compress: true, + preserveComments: 'some' + }, + dist: { + src: 'dist/<%= pkg.name %>.js', + dest: 'dist/<%= pkg.name %>.min.js' + }, + runtime: { + src: 'dist/<%= pkg.name %>.runtime.js', + dest: 'dist/<%= pkg.name %>.runtime.min.js' + } + } + }); + + grunt.loadNpmTasks('grunt-contrib-concat'); + grunt.loadNpmTasks('grunt-contrib-uglify'); + + grunt.registerTask('dist-dir', function() { + grunt.file.delete('dist'); + grunt.file.mkdir('dist'); + }); + + + grunt.registerTask('default', ['dist-dir', 'concat', 'uglify']); +}; diff --git a/README.markdown b/README.markdown index 781113d07..a56278e31 100644 --- a/README.markdown +++ b/README.markdown @@ -393,6 +393,7 @@ Helping Out To build Handlebars.js you'll need a few things installed. * Node.js +* [Grunt](http://gruntjs.com/getting-started) * Ruby * Rake diff --git a/Rakefile b/Rakefile index d1b703a2d..731d53baa 100644 --- a/Rakefile +++ b/Rakefile @@ -34,53 +34,8 @@ end task :default => [:compile, :spec] -def remove_exports(string) - match = string.match(%r{^// BEGIN\(BROWSER\)\n(.*)\n^// END\(BROWSER\)}m) - match ? match[1] : string -end - -minimal_deps = %w(browser-prefix base compiler/parser compiler/base compiler/ast utils compiler/compiler compiler/javascript-compiler runtime browser-suffix).map do |file| - "lib/handlebars/#{file}.js" -end - -runtime_deps = %w(browser-prefix base utils runtime browser-suffix).map do |file| - "lib/handlebars/#{file}.js" -end - -directory "dist" - -minimal_deps.unshift "dist" - -def build_for_task(task) - FileUtils.rm_rf("dist/*") if File.directory?("dist") - FileUtils.mkdir_p("dist") - - contents = ["/*\n\n" + File.read('LICENSE') + "\n@license\n*/\n"] - task.prerequisites.each do |filename| - next if filename == "dist" - - contents << "// #{filename}\n" + remove_exports(File.read(filename)) + ";" - end - - File.open(task.name, "w") do |file| - file.puts contents.join("\n") - end -end - -file "dist/handlebars.js" => minimal_deps do |task| - build_for_task(task) -end - -file "dist/handlebars.runtime.js" => runtime_deps do |task| - build_for_task(task) -end - task :build => [:compile] do |task| - Rake::Task["dist/handlebars.js"].execute - Rake::Task["dist/handlebars.runtime.js"].execute - - system "./node_modules/.bin/uglifyjs -m -c --comments -o dist/handlebars.min.js dist/handlebars.js" - system "./node_modules/.bin/uglifyjs -m -c --comments -o dist/handlebars.runtime.min.js dist/handlebars.runtime.js" + system "grunt" end # Updates the various version numbers. diff --git a/dist/handlebars.js b/dist/handlebars.js index 1942eb533..2fba979ca 100644 --- a/dist/handlebars.js +++ b/dist/handlebars.js @@ -1,4 +1,6 @@ -/* +/*! + + handlebars v1.0.12 Copyright (C) 2011 by Yehuda Katz @@ -2279,4 +2281,3 @@ Handlebars.template = Handlebars.VM.template; this.Handlebars = Handlebars; } }).call(this); -; diff --git a/dist/handlebars.runtime.js b/dist/handlebars.runtime.js index 947b8f076..c87b9e314 100644 --- a/dist/handlebars.runtime.js +++ b/dist/handlebars.runtime.js @@ -1,4 +1,6 @@ -/* +/*! + + handlebars v1.0.12 Copyright (C) 2011 by Yehuda Katz @@ -371,4 +373,3 @@ Handlebars.template = Handlebars.VM.template; this.Handlebars = Handlebars; } }).call(this); -; diff --git a/package.json b/package.json index 13fdc74ee..b44054f2d 100644 --- a/package.json +++ b/package.json @@ -23,6 +23,9 @@ "devDependencies": { "benchmark": "~1.0", "dust": "~0.3", + "grunt": "~0.4.1", + "grunt-contrib-concat": "~0.3.0", + "grunt-contrib-uglify": "~0.2.2", "jison": "~0.3", "mocha": "*", "mustache": "~0.7.2", From 6492fe8c2300d39d71d11cc6837cf2e63d238cd1 Mon Sep 17 00:00:00 2001 From: kpdecker Date: Sat, 17 Aug 2013 12:16:48 -0500 Subject: [PATCH 064/200] Add jshint to grunt exec --- .jshintrc | 1 - Gruntfile.js | 11 +++++++++++ dist/handlebars.js | 2 ++ dist/handlebars.runtime.js | 2 ++ lib/handlebars.js | 2 ++ lib/handlebars/utils.js | 2 ++ package.json | 1 + 7 files changed, 20 insertions(+), 1 deletion(-) diff --git a/.jshintrc b/.jshintrc index d6bd57bba..3a42ab1f7 100644 --- a/.jshintrc +++ b/.jshintrc @@ -29,7 +29,6 @@ ], "node" : true, - "es5" : true, "browser" : true, "boss" : true, diff --git a/Gruntfile.js b/Gruntfile.js index 6d6f7adb6..bfcc02085 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -2,6 +2,16 @@ module.exports = function(grunt) { grunt.initConfig({ pkg: grunt.file.readJSON('package.json'), + jshint: { + options: { + jshintrc: '.jshintrc', + force: true + }, + files: [ + 'lib/**/!(parser|browser-prefix|browser-suffix).js' + ] + }, + concat: { options: { banner: '/*!\n\n <%= pkg.name %> v<%= pkg.version %>\n\n<%= grunt.file.read("LICENSE") %>\n@license\n*/\n', @@ -55,6 +65,7 @@ module.exports = function(grunt) { }); grunt.loadNpmTasks('grunt-contrib-concat'); + grunt.loadNpmTasks('grunt-contrib-jshint'); grunt.loadNpmTasks('grunt-contrib-uglify'); grunt.registerTask('dist-dir', function() { diff --git a/dist/handlebars.js b/dist/handlebars.js index 2fba979ca..24f82aa51 100644 --- a/dist/handlebars.js +++ b/dist/handlebars.js @@ -844,6 +844,8 @@ Handlebars.Utils = { }, escapeExpression: function(string) { + /*jshint eqnull: true */ + // don't escape SafeStrings, since they're already safe if (string instanceof Handlebars.SafeString) { return string.toString(); diff --git a/dist/handlebars.runtime.js b/dist/handlebars.runtime.js index c87b9e314..02834f35f 100644 --- a/dist/handlebars.runtime.js +++ b/dist/handlebars.runtime.js @@ -233,6 +233,8 @@ Handlebars.Utils = { }, escapeExpression: function(string) { + /*jshint eqnull: true */ + // don't escape SafeStrings, since they're already safe if (string instanceof Handlebars.SafeString) { return string.toString(); diff --git a/lib/handlebars.js b/lib/handlebars.js index f82ec3bad..c6130d153 100644 --- a/lib/handlebars.js +++ b/lib/handlebars.js @@ -1,3 +1,5 @@ +/*global Handlebars: true */ + var handlebars = require("./handlebars/base"), // Each of these augment the Handlebars object. No need to setup here. diff --git a/lib/handlebars/utils.js b/lib/handlebars/utils.js index e104dcf7c..9a4ff7724 100644 --- a/lib/handlebars/utils.js +++ b/lib/handlebars/utils.js @@ -50,6 +50,8 @@ Handlebars.Utils = { }, escapeExpression: function(string) { + /*jshint eqnull: true */ + // don't escape SafeStrings, since they're already safe if (string instanceof Handlebars.SafeString) { return string.toString(); diff --git a/package.json b/package.json index b44054f2d..78691e4fd 100644 --- a/package.json +++ b/package.json @@ -25,6 +25,7 @@ "dust": "~0.3", "grunt": "~0.4.1", "grunt-contrib-concat": "~0.3.0", + "grunt-contrib-jshint": "~0.6.3", "grunt-contrib-uglify": "~0.2.2", "jison": "~0.3", "mocha": "*", From b7c62d8cc525a2ae1105a4722c72d9e2f7d3d3ee Mon Sep 17 00:00:00 2001 From: kpdecker Date: Sat, 17 Aug 2013 12:19:13 -0500 Subject: [PATCH 065/200] Move test runner into grunt --- Gruntfile.js | 15 ++++++++++++++- README.markdown | 2 +- Rakefile | 9 +-------- 3 files changed, 16 insertions(+), 10 deletions(-) diff --git a/Gruntfile.js b/Gruntfile.js index bfcc02085..0e56e00cc 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -1,3 +1,5 @@ +var childProcess = require('child_process'); + module.exports = function(grunt) { grunt.initConfig({ pkg: grunt.file.readJSON('package.json'), @@ -68,11 +70,22 @@ module.exports = function(grunt) { grunt.loadNpmTasks('grunt-contrib-jshint'); grunt.loadNpmTasks('grunt-contrib-uglify'); + grunt.registerTask('dist-dir', function() { grunt.file.delete('dist'); grunt.file.mkdir('dist'); }); + grunt.registerTask('test', function() { + var done = this.async(); + var runner = childProcess.fork('./spec/env/runner', [], {stdio: 'inherit'}); + runner.on('close', function(code) { + if (code != 0) { + grunt.fatal(code + ' tests failed'); + } + done(); + }); + }); - grunt.registerTask('default', ['dist-dir', 'concat', 'uglify']); + grunt.registerTask('default', ['jshint', 'dist-dir', 'concat', 'uglify', 'test']); }; diff --git a/README.markdown b/README.markdown index a56278e31..2ea39a8aa 100644 --- a/README.markdown +++ b/README.markdown @@ -402,7 +402,7 @@ if you've got bundler installed. To build Handlebars.js from scratch, you'll want to run `rake build` in the root of the project. That will build Handlebars and output the -results to the dist/ folder. To run tests, run `rake spec` or `npm test`. +results to the dist/ folder. To run tests, run `grunt test` or `npm test`. You can also run our set of benchmarks with `rake bench`. If you notice any problems, please report them to the GitHub issue tracker at diff --git a/Rakefile b/Rakefile index 731d53baa..21a4cc8a8 100644 --- a/Rakefile +++ b/Rakefile @@ -26,13 +26,7 @@ end task :compile => "lib/handlebars/compiler/parser.js" -desc "run the spec suite" -task :spec => [:build] do - rc = system "npm test" - fail "npm test failed with exit code #{$?.exitstatus}" if (rc.nil? || ! rc || $?.exitstatus != 0) -end - -task :default => [:compile, :spec] +task :default => [:build] task :build => [:compile] do |task| system "grunt" @@ -65,7 +59,6 @@ task :version, [:version] => [] do |task, args| end Rake::Task[:build].invoke - Rake::Task[:spec].invoke # TODO : Make sure that all of these files are updated properly in git then run npm version end From 2f1a454467a3c0d0aae3d87a86535cea4a5c7da7 Mon Sep 17 00:00:00 2001 From: kpdecker Date: Sat, 17 Aug 2013 12:41:04 -0500 Subject: [PATCH 066/200] Move parser generation into grunt file --- Gruntfile.js | 3 ++- README.markdown | 2 +- Rakefile | 27 +-------------------------- tasks/parser.js | 23 +++++++++++++++++++++++ 4 files changed, 27 insertions(+), 28 deletions(-) create mode 100644 tasks/parser.js diff --git a/Gruntfile.js b/Gruntfile.js index 0e56e00cc..8fed7427b 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -70,6 +70,7 @@ module.exports = function(grunt) { grunt.loadNpmTasks('grunt-contrib-jshint'); grunt.loadNpmTasks('grunt-contrib-uglify'); + grunt.loadTasks('tasks'); grunt.registerTask('dist-dir', function() { grunt.file.delete('dist'); @@ -87,5 +88,5 @@ module.exports = function(grunt) { }); }); - grunt.registerTask('default', ['jshint', 'dist-dir', 'concat', 'uglify', 'test']); + grunt.registerTask('default', ['jshint', 'parser', 'dist-dir', 'concat', 'uglify', 'test']); }; diff --git a/README.markdown b/README.markdown index 2ea39a8aa..b3904aa36 100644 --- a/README.markdown +++ b/README.markdown @@ -400,7 +400,7 @@ To build Handlebars.js you'll need a few things installed. There's a Gemfile in the repo, so you can run `bundle` to install rake if you've got bundler installed. -To build Handlebars.js from scratch, you'll want to run `rake build` +To build Handlebars.js from scratch, you'll want to run `grunt` in the root of the project. That will build Handlebars and output the results to the dist/ folder. To run tests, run `grunt test` or `npm test`. You can also run our set of benchmarks with `rake bench`. diff --git a/Rakefile b/Rakefile index 21a4cc8a8..429c545f9 100644 --- a/Rakefile +++ b/Rakefile @@ -1,34 +1,9 @@ require "rubygems" require "bundler/setup" -def compile_parser - system "./node_modules/.bin/jison -m js src/handlebars.yy src/handlebars.l" - if $?.success? - File.open("lib/handlebars/compiler/parser.js", "w") do |file| - file.puts File.read("src/parser-prefix.js") + File.read("handlebars.js") + File.read("src/parser-suffix.js") - end - - sh "rm handlebars.js" - else - fail "Failed to run Jison." - end -end - -file "lib/handlebars/compiler/parser.js" => ["src/handlebars.yy","src/handlebars.l"] do - if File.exists?('./node_modules/jison') - compile_parser - else - puts "Jison is not installed. Trying `npm install jison`." - sh "npm install" - compile_parser - end -end - -task :compile => "lib/handlebars/compiler/parser.js" - task :default => [:build] -task :build => [:compile] do |task| +task :build do |task| system "grunt" end diff --git a/tasks/parser.js b/tasks/parser.js new file mode 100644 index 000000000..f6a72c617 --- /dev/null +++ b/tasks/parser.js @@ -0,0 +1,23 @@ +var childProcess = require('child_process'); + +module.exports = function(grunt) { + grunt.registerTask('parser', 'Generate jison parser.', function() { + var done = this.async(); + + var child = childProcess.spawn('./node_modules/.bin/jison', ['-m', 'js', 'src/handlebars.yy', 'src/handlebars.l'], {stdio: 'inherit'}); + child.on('exit', function(code) { + if (code != 0) { + grunt.fatal('Jison failure: ' + code); + done(); + return; + } + + var src = ['src/parser-prefix.js', 'handlebars.js', 'src/parser-suffix.js'].map(grunt.file.read).join(''); + grunt.file.delete('handlebars.js'); + + grunt.file.write('lib/handlebars/compiler/parser.js', src); + grunt.log.writeln('Parser "lib/handlebars/compiler/parser.js" created.'); + done(); + }); + }); +}; From 3b8a061200a56195cb694c55a5abae5568265944 Mon Sep 17 00:00:00 2001 From: Elving Rodriguez Date: Sat, 24 Aug 2013 09:32:01 -0700 Subject: [PATCH 067/200] Add Swag to the "Handlebars in the Wild" list. --- README.markdown | 1 + 1 file changed, 1 insertion(+) diff --git a/README.markdown b/README.markdown index 781113d07..d82b9fc42 100644 --- a/README.markdown +++ b/README.markdown @@ -379,6 +379,7 @@ Handlebars in the Wild * [SproutCore](http://www.sproutcore.com) uses Handlebars.js as its main templating engine, extending it with automatic data binding support. * [YUI](http://yuilibrary.com/yui/docs/handlebars/) implements a port of handlebars +* [Swag](https://github.com/elving/swag) by [@elving](https://github.com/elving) is a growing collection of helpers for handlebars.js. Give your handlebars.js templates some swag son! External Resources ------------------ From 94e5ab8593b878e45833abd50c3242953f6d1901 Mon Sep 17 00:00:00 2001 From: kpdecker Date: Sat, 24 Aug 2013 12:05:43 -0500 Subject: [PATCH 068/200] Add additional @data tests --- spec/builtins.js | 10 ++++++++++ spec/data.js | 9 +++++---- 2 files changed, 15 insertions(+), 4 deletions(-) diff --git a/spec/builtins.js b/spec/builtins.js index a29616f98..f7aa84d37 100644 --- a/spec/builtins.js +++ b/spec/builtins.js @@ -84,6 +84,16 @@ describe('builtin helpers', function() { equal(result, "0. goodbye! 1. Goodbye! 2. GOODBYE! cruel world!", "The @index variable is used"); }); + it("each with nested @index", function() { + var string = "{{#each goodbyes}}{{@index}}. {{text}}! {{#each ../goodbyes}}{{@index}} {{/each}}After {{@index}} {{/each}}{{@index}}cruel {{world}}!"; + var hash = {goodbyes: [{text: "goodbye"}, {text: "Goodbye"}, {text: "GOODBYE"}], world: "world"}; + + var template = CompilerContext.compile(string); + var result = template(hash); + + equal(result, "0. goodbye! 0 1 2 After 0 1. Goodbye! 0 1 2 After 1 2. GOODBYE! 0 1 2 After 2 cruel world!", "The @index variable is used"); + }); + it("each with function argument", function() { var string = "{{#each goodbyes}}{{text}}! {{/each}}cruel {{world}}!"; var hash = {goodbyes: function () { return [{text: "goodbye"}, {text: "Goodbye"}, {text: "GOODBYE"}];}, world: "world"}; diff --git a/spec/data.js b/spec/data.js index 5af35b7eb..0680364f6 100644 --- a/spec/data.js +++ b/spec/data.js @@ -112,18 +112,19 @@ describe('data', function() { }); it("data is inherited downstream", function() { - var template = CompilerContext.compile("{{#let foo=bar.baz}}{{@foo}}{{/let}}", { data: true }); + var template = CompilerContext.compile("{{#let foo=1 bar=2}}{{#let foo=bar.baz}}{{@bar}}{{@foo}}{{/let}}{{@foo}}{{/let}}", { data: true }); var helpers = { let: function(options) { + var frame = Handlebars.createFrame(options.data); for (var prop in options.hash) { - options.data[prop] = options.hash[prop]; + frame[prop] = options.hash[prop]; } - return options.fn(this); + return options.fn(this, {data: frame}); } }; var result = template({ bar: { baz: "hello world" } }, { helpers: helpers, data: {} }); - equals("hello world", result, "data variables are inherited downstream"); + equals("2hello world1", result, "data variables are inherited downstream"); }); it("passing in data to a compiled function that expects data - works with helpers in partials", function() { From eb1cda6fdc975b704d2eda211419f552931417ba Mon Sep 17 00:00:00 2001 From: kpdecker Date: Sat, 24 Aug 2013 12:06:25 -0500 Subject: [PATCH 069/200] jshint --- lib/handlebars.js | 2 ++ lib/handlebars/utils.js | 2 ++ 2 files changed, 4 insertions(+) diff --git a/lib/handlebars.js b/lib/handlebars.js index f82ec3bad..c6130d153 100644 --- a/lib/handlebars.js +++ b/lib/handlebars.js @@ -1,3 +1,5 @@ +/*global Handlebars: true */ + var handlebars = require("./handlebars/base"), // Each of these augment the Handlebars object. No need to setup here. diff --git a/lib/handlebars/utils.js b/lib/handlebars/utils.js index e104dcf7c..9a4ff7724 100644 --- a/lib/handlebars/utils.js +++ b/lib/handlebars/utils.js @@ -50,6 +50,8 @@ Handlebars.Utils = { }, escapeExpression: function(string) { + /*jshint eqnull: true */ + // don't escape SafeStrings, since they're already safe if (string instanceof Handlebars.SafeString) { return string.toString(); From 0af54b114232ba7ac1362a7164fefea78e13ef97 Mon Sep 17 00:00:00 2001 From: kpdecker Date: Sat, 24 Aug 2013 12:10:10 -0500 Subject: [PATCH 070/200] Use extend rather than prototype for createFrame Using prototype has a large performance impact for the common case of a sparse set of private variable data points. Rather than incurring the overhead of creating and walking the prototype tree for this, performing an extend by copy. --- lib/handlebars/base.js | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/lib/handlebars/base.js b/lib/handlebars/base.js index b8e11eb2f..4cf454316 100644 --- a/lib/handlebars/base.js +++ b/lib/handlebars/base.js @@ -73,10 +73,9 @@ Handlebars.registerHelper('blockHelperMissing', function(context, options) { Handlebars.K = function() {}; -Handlebars.createFrame = Object.create || function(object) { - Handlebars.K.prototype = object; - var obj = new Handlebars.K(); - Handlebars.K.prototype = null; +Handlebars.createFrame = function(object) { + var obj = {}; + Handlebars.Utils.extend(obj, object); return obj; }; From 7e16d6cb0d5f433b1a8b3efda82b9762af0ba8b0 Mon Sep 17 00:00:00 2001 From: kpdecker Date: Sat, 24 Aug 2013 12:11:26 -0500 Subject: [PATCH 071/200] Rebuild --- dist/handlebars.js | 9 +++++---- dist/handlebars.runtime.js | 9 +++++---- 2 files changed, 10 insertions(+), 8 deletions(-) diff --git a/dist/handlebars.js b/dist/handlebars.js index 1942eb533..4695f1c64 100644 --- a/dist/handlebars.js +++ b/dist/handlebars.js @@ -96,10 +96,9 @@ Handlebars.registerHelper('blockHelperMissing', function(context, options) { Handlebars.K = function() {}; -Handlebars.createFrame = Object.create || function(object) { - Handlebars.K.prototype = object; - var obj = new Handlebars.K(); - Handlebars.K.prototype = null; +Handlebars.createFrame = function(object) { + var obj = {}; + Handlebars.Utils.extend(obj, object); return obj; }; @@ -842,6 +841,8 @@ Handlebars.Utils = { }, escapeExpression: function(string) { + /*jshint eqnull: true */ + // don't escape SafeStrings, since they're already safe if (string instanceof Handlebars.SafeString) { return string.toString(); diff --git a/dist/handlebars.runtime.js b/dist/handlebars.runtime.js index 947b8f076..2372cb5b7 100644 --- a/dist/handlebars.runtime.js +++ b/dist/handlebars.runtime.js @@ -96,10 +96,9 @@ Handlebars.registerHelper('blockHelperMissing', function(context, options) { Handlebars.K = function() {}; -Handlebars.createFrame = Object.create || function(object) { - Handlebars.K.prototype = object; - var obj = new Handlebars.K(); - Handlebars.K.prototype = null; +Handlebars.createFrame = function(object) { + var obj = {}; + Handlebars.Utils.extend(obj, object); return obj; }; @@ -231,6 +230,8 @@ Handlebars.Utils = { }, escapeExpression: function(string) { + /*jshint eqnull: true */ + // don't escape SafeStrings, since they're already safe if (string instanceof Handlebars.SafeString) { return string.toString(); From 7efa75684e471726101a5724cb5c48d5dc4e1eb0 Mon Sep 17 00:00:00 2001 From: kpdecker Date: Sat, 24 Aug 2013 13:10:51 -0500 Subject: [PATCH 072/200] Version update task --- Gruntfile.js | 3 +- Rakefile | 32 ------------------ package.json | 5 ++- tasks/release.js | 62 +++++++++++++++++++++++++++++++++++ tasks/util/git.js | 83 +++++++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 151 insertions(+), 34 deletions(-) create mode 100644 tasks/release.js create mode 100644 tasks/util/git.js diff --git a/Gruntfile.js b/Gruntfile.js index 8fed7427b..a34760078 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -88,5 +88,6 @@ module.exports = function(grunt) { }); }); - grunt.registerTask('default', ['jshint', 'parser', 'dist-dir', 'concat', 'uglify', 'test']); + grunt.registerTask('build', ['jshint', 'parser', 'dist-dir', 'concat', 'uglify', 'test']); + grunt.registerTask('default', 'build'); }; diff --git a/Rakefile b/Rakefile index 429c545f9..fd5cf947b 100644 --- a/Rakefile +++ b/Rakefile @@ -7,38 +7,6 @@ task :build do |task| system "grunt" end -# Updates the various version numbers. -desc "Updates the current release version" -task :version, [:version] => [] do |task, args| - version = args.version - fail "Must provide a version number" unless version - - changed = %x{git diff-index --name-only HEAD --} - fail "The repository must be clean" unless $?.success? && changed.empty? - - puts "Updating to version #{version}" - - content = File.read("lib/handlebars/base.js") - File.open("lib/handlebars/base.js", "w") do | file| - file.puts content.gsub(/Handlebars.VERSION = "(.*)";/, "Handlebars.VERSION = \"#{version}\";") - end - - content = File.read("bower.json") - File.open("bower.json", "w") do |file| - file.puts content.gsub(/"version":.*/, "\"version\": \"#{version}\",") - end - - content = File.read("handlebars.js.nuspec") - File.open("handlebars.js.nuspec", "w") do |file| - file.puts content.gsub(/.*<\/version>/, "#{version}") - end - - Rake::Task[:build].invoke - - # TODO : Make sure that all of these files are updated properly in git then run npm version -end - - directory "vendor" desc "benchmark against dust.js and mustache.js" diff --git a/package.json b/package.json index 78691e4fd..d67f68ea7 100644 --- a/package.json +++ b/package.json @@ -21,6 +21,7 @@ "uglify-js": "~2.3" }, "devDependencies": { + "async": "~0.2.9", "benchmark": "~1.0", "dust": "~0.3", "grunt": "~0.4.1", @@ -30,7 +31,9 @@ "jison": "~0.3", "mocha": "*", "mustache": "~0.7.2", - "should": "~1.2.2" + "semver": "~2.1.0", + "should": "~1.2.2", + "underscore": "~1.5.1" }, "main": "lib/handlebars.js", "bin": { diff --git a/tasks/release.js b/tasks/release.js new file mode 100644 index 000000000..636670e1c --- /dev/null +++ b/tasks/release.js @@ -0,0 +1,62 @@ +var async = require('async'), + git = require('./util/git'), + semver = require('semver'); + +module.exports = function(grunt) { + grunt.registerTask('version', 'Updates the current release version', function() { + var done = this.async(), + pkg = grunt.config('pkg'), + version = grunt.option('ver'); + + if (version === 'major' || version === 'minor' || version === 'patch' || version === 'prerelease') { + version = semver.inc(pkg.version, version); + } + if (!semver.valid(version)) { + throw new Error('Must provide a version number (Ex: --ver=patch):\n\t' + version + '\n\n'); + } + + pkg.version = version; + grunt.config('pkg', pkg); + + git.clean(function(err, clean) { + if (err || !clean) { + throw new Error('The repository must be clean'); + } + + grunt.log.write('Updating to version ' + version); + + grunt.task.run(['build', 'tag']); + + async.each([ + ['lib/handlebars/base.js', /Handlebars.VERSION = "(.*)";/, 'Handlebars.VERSION = "' + version + '";'], + ['package.json', /"version":.*/, '"version": "' + version + '",'], + ['bower.json', /"version":.*/, '"version": "' + version + '",'], + ['handlebars.js.nuspec', /.*<\/version>/, '' + version + ''] + ], + function(args, callback) { + replace.apply(undefined, args); + git.add(args[0], callback); + }, + done); + }); + }); + + grunt.registerTask('tag', 'Tags the current release version', function() { + var done = this.async(), + name = 'v' + grunt.config('pkg').version; + + async.series([ + function(callback) { git.add('dist/handlebars.js', callback); }, + function(callback) { git.add('dist/handlebars.runtime.js', callback); }, + function(callback) { git.commit(name, callback); }, + function(callback) { git.tag(name, callback); } + ], + done); + }); + + function replace(path, regex, replace) { + var content = grunt.file.read(path); + content = content.replace(regex, replace); + grunt.file.write(path, content); + } +}; diff --git a/tasks/util/git.js b/tasks/util/git.js new file mode 100644 index 000000000..53e0237db --- /dev/null +++ b/tasks/util/git.js @@ -0,0 +1,83 @@ +var childProcess = require('child_process'); + +module.exports = { + clean: function(callback) { + childProcess.exec('git diff-index --name-only HEAD --', {}, function(err, stdout) { + callback(undefined, !err && !stdout); + }); + }, + + commitInfo: function(callback) { + module.exports.head(function(err, headSha) { + module.exports.master(function(err, masterSha) { + module.exports.tagName(function(err, tagName) { + callback(undefined, { + head: headSha, + master: masterSha, + tagName: tagName, + isMaster: headSha === masterSha + }); + }); + }); + }); + }, + + head: function(callback) { + childProcess.exec('git rev-parse --short HEAD', {}, function(err, stdout) { + if (err) { + throw err; + } + + callback(undefined, stdout.trim()); + }); + }, + master: function(callback) { + childProcess.exec('git rev-parse --short origin/master', {}, function(err, stdout) { + if (err) { + throw err; + } + + callback(undefined, stdout.trim()); + }); + }, + + add: function(path, callback) { + childProcess.exec('git add -f ' + path, {}, function(err, stdout) { + if (err) { + throw err; + } + + callback(); + }); + }, + commit: function(name, callback) { + childProcess.exec('git commit --message=' + name, {}, function(err, stdout) { + if (err) { + throw err; + } + + callback(); + }); + }, + tag: function(name, callback) { + childProcess.exec('git tag -a --message=' + name + ' ' + name, {}, function(err, stdout, stderr) { + console.log(name, stdout, stderr); + if (err) { + throw err; + } + + callback(); + }); + }, + tagName: function(callback) { + childProcess.exec('git tag -l --points-at HEAD', {}, function(err, stdout) { + if (err) { + throw err; + } + + var tags = stdout.trim().split(/\n/), + versionTags = tags.filter(function(tag) { return /^v/.test(tag); }); + callback(undefined, versionTags[0] || tags[0]); + }); + } +}; From e77c961d40d7830809067a545e77cc82313ba55b Mon Sep 17 00:00:00 2001 From: kpdecker Date: Sat, 24 Aug 2013 16:01:37 -0500 Subject: [PATCH 073/200] Add grunt bench command --- Gruntfile.js | 11 +++++++++++ Rakefile | 21 --------------------- 2 files changed, 11 insertions(+), 21 deletions(-) diff --git a/Gruntfile.js b/Gruntfile.js index a34760078..25e24c5be 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -87,6 +87,17 @@ module.exports = function(grunt) { done(); }); }); + grunt.registerTask('bench', function() { + var done = this.async(); + + var runner = childProcess.fork('./bench/handlebars', [], {stdio: 'inherit'}); + runner.on('close', function(code) { + if (code != 0) { + grunt.fatal(code + ' tests failed'); + } + done(); + }); + }); grunt.registerTask('build', ['jshint', 'parser', 'dist-dir', 'concat', 'uglify', 'test']); grunt.registerTask('default', 'build'); diff --git a/Rakefile b/Rakefile index fd5cf947b..e9d38ae77 100644 --- a/Rakefile +++ b/Rakefile @@ -7,27 +7,6 @@ task :build do |task| system "grunt" end -directory "vendor" - -desc "benchmark against dust.js and mustache.js" -task :bench => "vendor" do - require "open-uri" - - #if File.directory?("vendor/coffee") - #system "cd vendor/coffee && git pull" - #else - #system "git clone git://github.com/jashkenas/coffee-script.git vendor/coffee" - #end - - #if File.directory?("vendor/eco") - #system "cd vendor/eco && git pull && npm update" - #else - #system "git clone git://github.com/sstephenson/eco.git vendor/eco && cd vendor/eco && npm update" - #end - - system "node bench/handlebars.js" -end - def dist_files(&block) map = {} From b700d91a700eab20e696691fea6203b36e7bcb11 Mon Sep 17 00:00:00 2001 From: kpdecker Date: Sat, 24 Aug 2013 16:03:06 -0500 Subject: [PATCH 074/200] Fix benchmark dependencies --- bench/handlebars.js | 4 ++-- package.json | 3 ++- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/bench/handlebars.js b/bench/handlebars.js index c7f6c1058..900bd63c4 100644 --- a/bench/handlebars.js +++ b/bench/handlebars.js @@ -4,7 +4,7 @@ Handlebars = require("../lib/handlebars"); var dust, Mustache, eco; try { - dust = require("dust"); + dust = require("dustjs-linkedin"); } catch (err) { /* NOP */ } try { @@ -15,7 +15,7 @@ try { var ecoExports = require("eco"); eco = function(str) { return ecoExports(str); - } + }; } catch (err) { /* NOP */ } var benchDetails = { diff --git a/package.json b/package.json index 13fdc74ee..36edc084e 100644 --- a/package.json +++ b/package.json @@ -22,7 +22,8 @@ }, "devDependencies": { "benchmark": "~1.0", - "dust": "~0.3", + "dustjs-linkedin": "~2.0.2", + "eco": "~1.1.0-rc-3", "jison": "~0.3", "mocha": "*", "mustache": "~0.7.2", From 94ba2b95d01b669323a6862768a52605b53e8f11 Mon Sep 17 00:00:00 2001 From: kpdecker Date: Sat, 24 Aug 2013 16:03:28 -0500 Subject: [PATCH 075/200] Fix fastest test lookup --- bench/benchwarmer.js | 20 +------------------- 1 file changed, 1 insertion(+), 19 deletions(-) diff --git a/bench/benchwarmer.js b/bench/benchwarmer.js index 25a250619..6ee3e561f 100644 --- a/bench/benchwarmer.js +++ b/bench/benchwarmer.js @@ -12,25 +12,7 @@ var print = require("sys").print; BenchWarmer.prototype = { winners: function(benches) { - var result = Benchmark.filter(benches, function(bench) { return bench.cycles; }); - - if (result.length > 1) { - result.sort(function(a, b) { return b.compare(a); }); - first = result[0]; - last = result[result.length - 1]; - - var winners = []; - - Benchmark.each(result, function(bench) { - if (bench.compare(first) === 0) { - winners.push(bench); - } - }); - - return winners; - } else { - return result; - } + return Benchmark.filter(benches, 'fastest'); }, suite: function(suite, fn) { this.suiteName = suite; From a74aed15e9a30df81f1ba0a97dd4cbba50833b25 Mon Sep 17 00:00:00 2001 From: kpdecker Date: Sat, 24 Aug 2013 16:03:55 -0500 Subject: [PATCH 076/200] Handle edge case in benchmark layouts --- bench/benchwarmer.js | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/bench/benchwarmer.js b/bench/benchwarmer.js index 6ee3e561f..b6dbc74e0 100644 --- a/bench/benchwarmer.js +++ b/bench/benchwarmer.js @@ -61,7 +61,7 @@ BenchWarmer.prototype = { var horSize = 0; this.startLine("ops/msec"); - horSize = horSize + "ops/msec ".length; + horSize = horSize + this.benchSize; for(i=0, l=names.length; i Date: Sat, 24 Aug 2013 16:04:22 -0500 Subject: [PATCH 077/200] Differentiate between failures and skipped tests --- bench/benchwarmer.js | 21 +++++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) diff --git a/bench/benchwarmer.js b/bench/benchwarmer.js index b6dbc74e0..72bfb0191 100644 --- a/bench/benchwarmer.js +++ b/bench/benchwarmer.js @@ -32,9 +32,7 @@ BenchWarmer.prototype = { var first = this.first, suiteName = this.suiteName, self = this; this.first = false; - var bench = new Benchmark(function() { - fn(); - }, { + var bench = new Benchmark(fn, { name: this.suiteName + ": " + name, onComplete: function() { if(first) { self.startLine(suiteName); } @@ -78,12 +76,19 @@ BenchWarmer.prototype = { self.startLine(''); var errors = false, prop, bench; - for(prop in self.errors) { if(self.errors.hasOwnProperty(prop)) { errors = true; break; } } + for(prop in self.errors) { + if (self.errors.hasOwnProperty(prop) + && self.errors[prop].error.message !== 'EWOT') { + errors = true; + break; + } + } if(errors) { print("\n\nErrors:\n"); for(prop in self.errors) { - if(self.errors.hasOwnProperty(prop)) { + if (self.errors.hasOwnProperty(prop) + && self.errors[prop].error.message !== 'EWOT') { bench = self.errors[prop]; print("\n" + bench.name + ":\n"); print(bench.error.message); @@ -121,7 +126,11 @@ BenchWarmer.prototype = { out = Math.round(count / 1000) + " ±" + Math.round(moe / 1000) + " (" + bench.cycles + ")"; } else { - out = "E"; + if (bench.error.message === 'EWOT') { + out = 'NA'; + } else { + out = 'E'; + } } var padding = this.benchSize - out.length + 1; From f7b2bd8039222b31769169e42341a7060e010663 Mon Sep 17 00:00:00 2001 From: kpdecker Date: Sat, 24 Aug 2013 16:05:39 -0500 Subject: [PATCH 078/200] Better skipped test handling --- bench/handlebars.js | 37 ++++++++++++++++++++++++------------- 1 file changed, 24 insertions(+), 13 deletions(-) diff --git a/bench/handlebars.js b/bench/handlebars.js index 900bd63c4..5e822fb7a 100644 --- a/bench/handlebars.js +++ b/bench/handlebars.js @@ -115,16 +115,20 @@ var makeSuite = function(name) { var error = function() { throw new Error("EWOT"); }; - if (dust) { - bench("dust", function() { - dust.render(templateName, context, function(err, out) { }); - }); - } - bench("handlebars", function() { handlebarsTemplates[templateName](context); }); + if (dust) { + if (details.dust) { + bench("dust", function() { + dust.render(templateName, context, function(err, out) { }); + }); + } else { + bench('dust', error); + } + } + if (eco) { if(ecoTemplates[templateName]) { bench("eco", function() { @@ -135,21 +139,28 @@ var makeSuite = function(name) { } } - if (Mustache && mustacheSource) { - bench("mustache", function() { - Mustache.to_html(mustacheSource, context, mustachePartials); - }); - } else { - bench("mustache", error); + if (Mustache) { + if (mustacheSource) { + bench("mustache", function() { + Mustache.to_html(mustacheSource, context, mustachePartials); + }); + } else { + bench("mustache", error); + } } }); } for(var name in benchDetails) { if(benchDetails.hasOwnProperty(name)) { - if (dust) { + if (!benchDetails[name].handlebars) { + continue; + } + + if (dust && benchDetails[name].dust) { dust.loadSource(dust.compile(benchDetails[name].dust, name)); } + handlebarsTemplates[name] = Handlebars.compile(benchDetails[name].handlebars); if (eco && benchDetails[name].eco) { From 84420c1eb4e8c8b07999fc406c68ae439dc943da Mon Sep 17 00:00:00 2001 From: kpdecker Date: Sat, 24 Aug 2013 16:05:51 -0500 Subject: [PATCH 079/200] Code style --- bench/handlebars.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/bench/handlebars.js b/bench/handlebars.js index 5e822fb7a..f362d9601 100644 --- a/bench/handlebars.js +++ b/bench/handlebars.js @@ -100,12 +100,12 @@ var benchDetails = { }; -handlebarsTemplates = {}; -ecoTemplates = {}; +var handlebarsTemplates = {}, + ecoTemplates = {}; var warmer = new BenchWarmer(); -var makeSuite = function(name) { +function makeSuite(name) { warmer.suite(name, function(bench) { var templateName = name; var details = benchDetails[templateName]; From a532d28d941f7ce37c11b95cf68864ef80c8d067 Mon Sep 17 00:00:00 2001 From: kpdecker Date: Sat, 24 Aug 2013 16:06:09 -0500 Subject: [PATCH 080/200] Allow custom helpers for benchmark templates --- bench/handlebars.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/bench/handlebars.js b/bench/handlebars.js index f362d9601..11414a5ff 100644 --- a/bench/handlebars.js +++ b/bench/handlebars.js @@ -112,11 +112,12 @@ function makeSuite(name) { var mustachePartials = details.partials && details.partials.mustache; var mustacheSource = details.mustache; var context = details.context; + var options = {helpers: details.helpers}; var error = function() { throw new Error("EWOT"); }; bench("handlebars", function() { - handlebarsTemplates[templateName](context); + handlebarsTemplates[templateName](context, options); }); if (dust) { From 4c02d3027c3b610f3f2a27b7ddbff091f8e53188 Mon Sep 17 00:00:00 2001 From: kpdecker Date: Sat, 24 Aug 2013 19:52:22 -0500 Subject: [PATCH 081/200] Optimize partial helper/partial merge handling --- lib/handlebars/runtime.js | 56 ++++++++++++++++++++++++++------------- 1 file changed, 37 insertions(+), 19 deletions(-) diff --git a/lib/handlebars/runtime.js b/lib/handlebars/runtime.js index 2e845c4cc..81e8aef42 100644 --- a/lib/handlebars/runtime.js +++ b/lib/handlebars/runtime.js @@ -2,6 +2,24 @@ exports.attach = function(Handlebars) { // BEGIN(BROWSER) +function checkRevision(compilerInfo) { + var compilerRevision = compilerInfo && compilerInfo[0] || 1, + currentRevision = Handlebars.COMPILER_REVISION; + + if (compilerRevision !== currentRevision) { + if (compilerRevision < currentRevision) { + var runtimeVersions = Handlebars.REVISION_CHANGES[currentRevision], + compilerVersions = Handlebars.REVISION_CHANGES[compilerRevision]; + throw "Template was precompiled with an older version of Handlebars than the current runtime. "+ + "Please update your precompiler to a newer version ("+runtimeVersions+") or downgrade your runtime to an older version ("+compilerVersions+")."; + } else { + // Use the embedded version info since the runtime doesn't know about this revision yet + throw "Template was precompiled with a newer version of Handlebars than the current runtime. "+ + "Please update your runtime to a newer version ("+compilerInfo[1]+")."; + } + } +} + Handlebars.VM = { template: function(templateSpec) { // Just add water @@ -21,7 +39,7 @@ Handlebars.VM = { merge: function(param, common) { var ret = param || common; - if (param && common) { + if (param && common && (param !== common)) { ret = {}; Handlebars.Utils.extend(ret, common); Handlebars.Utils.extend(ret, param); @@ -35,23 +53,23 @@ Handlebars.VM = { return function(context, options) { options = options || {}; - var result = templateSpec.call(container, Handlebars, context, options.helpers, options.partials, options.data); - - var compilerInfo = container.compilerInfo || [], - compilerRevision = compilerInfo[0] || 1, - currentRevision = Handlebars.COMPILER_REVISION; - - if (compilerRevision !== currentRevision) { - if (compilerRevision < currentRevision) { - var runtimeVersions = Handlebars.REVISION_CHANGES[currentRevision], - compilerVersions = Handlebars.REVISION_CHANGES[compilerRevision]; - throw "Template was precompiled with an older version of Handlebars than the current runtime. "+ - "Please update your precompiler to a newer version ("+runtimeVersions+") or downgrade your runtime to an older version ("+compilerVersions+")."; - } else { - // Use the embedded version info since the runtime doesn't know about this revision yet - throw "Template was precompiled with a newer version of Handlebars than the current runtime. "+ - "Please update your runtime to a newer version ("+compilerInfo[1]+")."; - } + var namespace = options.partial ? options : Handlebars, + helpers, + partials; + + if (!options.partial) { + helpers = options.helpers; + partials = options.partials; + } + var result = templateSpec.call( + container, + namespace, context, + helpers, + partials, + options.data); + + if (!options.partial) { + checkRevision(container.compilerInfo); } return result; @@ -82,7 +100,7 @@ Handlebars.VM = { }, noop: function() { return ""; }, invokePartial: function(partial, name, context, helpers, partials, data) { - var options = { helpers: helpers, partials: partials, data: data }; + var options = { partial: true, helpers: helpers, partials: partials, data: data }; if(partial === undefined) { throw new Handlebars.Exception("The partial " + name + " could not be found"); From 6e6acaac0d676581ea45a1a4810907abee65b852 Mon Sep 17 00:00:00 2001 From: kpdecker Date: Sat, 24 Aug 2013 22:13:37 -0500 Subject: [PATCH 082/200] Unify isFunction/isArray handling --- lib/handlebars/base.js | 36 ++++++++++++++++++++++-------------- lib/handlebars/utils.js | 5 +++-- 2 files changed, 25 insertions(+), 16 deletions(-) diff --git a/lib/handlebars/base.js b/lib/handlebars/base.js index 4cf454316..1decfd299 100644 --- a/lib/handlebars/base.js +++ b/lib/handlebars/base.js @@ -20,9 +20,24 @@ Handlebars.helpers = {}; Handlebars.partials = {}; var toString = Object.prototype.toString, - functionType = '[object Function]', objectType = '[object Object]'; +// Sourced from lodash +// https://github.com/bestiejs/lodash/blob/master/LICENSE.txt +function isFunction(value) { + return typeof value === 'function'; +} +// fallback for older versions of Chrome and Safari +if (isFunction(/x/)) { + isFunction = function(value) { + return typeof value === 'function' && toString.call(value) === '[object Function]'; + }; +} + +function isArray(value) { + return (value && typeof value === 'object') ? toString.call(value) === '[object Array]' : false; +}; + Handlebars.registerHelper = function(name, fn, inverse) { if (toString.call(name) === objectType) { if (inverse || fn) { throw new Handlebars.Exception('Arg not supported with multiple helpers'); } @@ -52,15 +67,13 @@ Handlebars.registerHelper('helperMissing', function(arg) { Handlebars.registerHelper('blockHelperMissing', function(context, options) { var inverse = options.inverse || function() {}, fn = options.fn; - var type = toString.call(context); - - if(type === functionType) { context = context.call(this); } + if (isFunction(context)) { context = context.call(this); } if(context === true) { return fn(this); } else if(context === false || context == null) { return inverse(this); - } else if(type === "[object Array]") { + } else if (isArray(context)) { if(context.length > 0) { return Handlebars.helpers.each(context, options); } else { @@ -71,8 +84,6 @@ Handlebars.registerHelper('blockHelperMissing', function(context, options) { } }); -Handlebars.K = function() {}; - Handlebars.createFrame = function(object) { var obj = {}; Handlebars.Utils.extend(obj, object); @@ -101,15 +112,14 @@ Handlebars.registerHelper('each', function(context, options) { var fn = options.fn, inverse = options.inverse; var i = 0, ret = "", data; - var type = toString.call(context); - if(type === functionType) { context = context.call(this); } + if (isFunction(context)) { context = context.call(this); } if (options.data) { data = Handlebars.createFrame(options.data); } if(context && typeof context === 'object') { - if(context instanceof Array){ + if (isArray(context)) { for(var j = context.length; i Date: Sat, 24 Aug 2013 22:20:23 -0500 Subject: [PATCH 083/200] Simplify falsy handling --- lib/handlebars/utils.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/handlebars/utils.js b/lib/handlebars/utils.js index 0887f8512..59947ca9d 100644 --- a/lib/handlebars/utils.js +++ b/lib/handlebars/utils.js @@ -56,7 +56,7 @@ Handlebars.Utils = { // don't escape SafeStrings, since they're already safe if (string instanceof Handlebars.SafeString) { return string.toString(); - } else if (string == null || string === false) { + } else if (!string && string !== 0) { return ""; } From c207c4e0482d1dda8231fd99941be423b3cb0d3d Mon Sep 17 00:00:00 2001 From: kpdecker Date: Sat, 24 Aug 2013 22:20:33 -0500 Subject: [PATCH 084/200] Rebuild --- dist/handlebars.js | 96 ++++++++++++++++++++++++-------------- dist/handlebars.runtime.js | 96 ++++++++++++++++++++++++-------------- 2 files changed, 122 insertions(+), 70 deletions(-) diff --git a/dist/handlebars.js b/dist/handlebars.js index 4695f1c64..d54d4474f 100644 --- a/dist/handlebars.js +++ b/dist/handlebars.js @@ -43,9 +43,24 @@ Handlebars.helpers = {}; Handlebars.partials = {}; var toString = Object.prototype.toString, - functionType = '[object Function]', objectType = '[object Object]'; +// Sourced from lodash +// https://github.com/bestiejs/lodash/blob/master/LICENSE.txt +function isFunction(value) { + return typeof value === 'function'; +} +// fallback for older versions of Chrome and Safari +if (isFunction(/x/)) { + isFunction = function(value) { + return typeof value === 'function' && toString.call(value) === '[object Function]'; + }; +} + +function isArray(value) { + return (value && typeof value === 'object') ? toString.call(value) === '[object Array]' : false; +}; + Handlebars.registerHelper = function(name, fn, inverse) { if (toString.call(name) === objectType) { if (inverse || fn) { throw new Handlebars.Exception('Arg not supported with multiple helpers'); } @@ -75,15 +90,13 @@ Handlebars.registerHelper('helperMissing', function(arg) { Handlebars.registerHelper('blockHelperMissing', function(context, options) { var inverse = options.inverse || function() {}, fn = options.fn; - var type = toString.call(context); - - if(type === functionType) { context = context.call(this); } + if (isFunction(context)) { context = context.call(this); } if(context === true) { return fn(this); } else if(context === false || context == null) { return inverse(this); - } else if(type === "[object Array]") { + } else if (isArray(context)) { if(context.length > 0) { return Handlebars.helpers.each(context, options); } else { @@ -94,8 +107,6 @@ Handlebars.registerHelper('blockHelperMissing', function(context, options) { } }); -Handlebars.K = function() {}; - Handlebars.createFrame = function(object) { var obj = {}; Handlebars.Utils.extend(obj, object); @@ -124,15 +135,14 @@ Handlebars.registerHelper('each', function(context, options) { var fn = options.fn, inverse = options.inverse; var i = 0, ret = "", data; - var type = toString.call(context); - if(type === functionType) { context = context.call(this); } + if (isFunction(context)) { context = context.call(this); } if (options.data) { data = Handlebars.createFrame(options.data); } if(context && typeof context === 'object') { - if(context instanceof Array){ + if (isArray(context)) { for(var j = context.length; i 0) { return Handlebars.helpers.each(context, options); } else { @@ -94,8 +107,6 @@ Handlebars.registerHelper('blockHelperMissing', function(context, options) { } }); -Handlebars.K = function() {}; - Handlebars.createFrame = function(object) { var obj = {}; Handlebars.Utils.extend(obj, object); @@ -124,15 +135,14 @@ Handlebars.registerHelper('each', function(context, options) { var fn = options.fn, inverse = options.inverse; var i = 0, ret = "", data; - var type = toString.call(context); - if(type === functionType) { context = context.call(this); } + if (isFunction(context)) { context = context.call(this); } if (options.data) { data = Handlebars.createFrame(options.data); } if(context && typeof context === 'object') { - if(context instanceof Array){ + if (isArray(context)) { for(var j = context.length; i Date: Sun, 25 Aug 2013 12:51:17 -0500 Subject: [PATCH 085/200] AWS publish tasks --- package.json | 1 + tasks/publish.js | 69 ++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 70 insertions(+) create mode 100644 tasks/publish.js diff --git a/package.json b/package.json index ba389fb3f..2aff973b8 100644 --- a/package.json +++ b/package.json @@ -22,6 +22,7 @@ }, "devDependencies": { "async": "~0.2.9", + "aws-sdk": "~1.5.0", "benchmark": "~1.0", "dustjs-linkedin": "~2.0.2", "eco": "~1.1.0-rc-3", diff --git a/tasks/publish.js b/tasks/publish.js new file mode 100644 index 000000000..9c3d1d636 --- /dev/null +++ b/tasks/publish.js @@ -0,0 +1,69 @@ +var _ = require('underscore'), + async = require('async'), + AWS = require('aws-sdk'), + git = require('./util/git'); + +module.exports = function(grunt) { + grunt.registerTask('publish', function() { + var done = this.async(); + initSDK(); + + git.commitInfo(function(err, info) { + if (info.isMaster) { + publish(fileMap(['-latest', '-' + info.head]), done); + } else { + // Silently ignore for branches + done(); + } + }); + }); + grunt.registerTask('publish:version', function() { + var done = this.async(); + initSDK(); + + git.commitInfo(function(err, info) { + if (!info.tagName) { + throw new Error('The current commit must be tagged'); + } + publish(fileMap(['-' + info.tagName]), done); + }); + }); + + function initSDK() { + var bucket = process.env.S3_BUCKET_NAME, + key = process.env.S3_ACCESS_KEY_ID, + secret = process.env.S3_SECRET_ACCESS_KEY; + + if (!bucket || !key || !secret) { + throw new Error('Missing S3 config values'); + } + + AWS.config.update({accessKeyId: key, secretAccessKey: secret}); + } + function publish(files, callback) { + var s3 = new AWS.S3(), + bucket = process.env.S3_BUCKET_NAME; + + async.forEach(_.keys(files), function(file, callback) { + var params = {Bucket: bucket, Key: file, Body: grunt.file.read(files[file])}; + s3.putObject(params, function(err, data) { + if (err) { + throw err; + } else { + grunt.log.writeln('Published ' + file + ' to build server.'); + callback(); + } + }); + }, + callback); + } + function fileMap(suffixes) { + var map = {}; + _.each(['handlebars.js', 'handlebars.min.js', 'handlebars.runtime.js', 'handlebars.runtime.min.js'], function(file) { + _.each(suffixes, function(suffix) { + map[file.replace(/\.js$/, suffix + '.js')] = 'dist/' + file; + }); + }); + return map; + } +}; From b773a7608dce6ab77bbffef469c0363706f15d46 Mon Sep 17 00:00:00 2001 From: kpdecker Date: Sun, 25 Aug 2013 12:51:34 -0500 Subject: [PATCH 086/200] Use grunt for travis exec --- .travis.yml | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index 9fb0d4afc..6eace317e 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,5 +1,17 @@ ---- -after_success: bundle exec rake publish +language: node_js +node_js: + - "0.8" + - "0.10" + +before_script: + - npm install -g grunt-cli + +script: + - grunt + +after_success: + - grunt publish + email: on_failure: change on_success: never From 9e09fe18260680f0de7aa3e4a02dd0730e603213 Mon Sep 17 00:00:00 2001 From: kpdecker Date: Sun, 25 Aug 2013 12:51:44 -0500 Subject: [PATCH 087/200] npm ignore grunt files --- .npmignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.npmignore b/.npmignore index a43e7ecf4..233b2cc89 100644 --- a/.npmignore +++ b/.npmignore @@ -12,4 +12,5 @@ Rakefile bench/* spec/* src/* +tasks/* vendor/* From e880bd464e299835b6deaf0fcecaa8bfa968781f Mon Sep 17 00:00:00 2001 From: kpdecker Date: Sun, 25 Aug 2013 12:57:25 -0500 Subject: [PATCH 088/200] Remove rake build assets --- Gemfile | 7 ------ Gemfile.lock | 21 ---------------- README.markdown | 12 +++------ Rakefile | 66 ------------------------------------------------- 4 files changed, 4 insertions(+), 102 deletions(-) delete mode 100644 Gemfile delete mode 100644 Gemfile.lock delete mode 100644 Rakefile diff --git a/Gemfile b/Gemfile deleted file mode 100644 index 96c9dd253..000000000 --- a/Gemfile +++ /dev/null @@ -1,7 +0,0 @@ -source "http://rubygems.org" - -gem "rake" -gem "json" -gem "nokogiri" -gem "aws-sdk" -gem "uuidtools" diff --git a/Gemfile.lock b/Gemfile.lock deleted file mode 100644 index 1a8fc1300..000000000 --- a/Gemfile.lock +++ /dev/null @@ -1,21 +0,0 @@ -GEM - remote: http://rubygems.org/ - specs: - aws-sdk (1.10.0) - json (~> 1.4) - nokogiri (>= 1.4.4) - uuidtools (~> 2.1) - json (1.8.0) - nokogiri (1.5.9) - rake (10.0.3) - uuidtools (2.1.4) - -PLATFORMS - ruby - -DEPENDENCIES - aws-sdk - json - nokogiri - rake - uuidtools diff --git a/README.markdown b/README.markdown index b3904aa36..68f94a3d9 100644 --- a/README.markdown +++ b/README.markdown @@ -340,8 +340,7 @@ and we will have some benchmarks in the near future. Building -------- -To build handlebars, just run `rake build`, and you will get two files -in the `dist` directory. +To build handlebars, just run `grunt build`, and the build will output to the `dist` directory. Upgrading @@ -394,16 +393,13 @@ To build Handlebars.js you'll need a few things installed. * Node.js * [Grunt](http://gruntjs.com/getting-started) -* Ruby -* Rake -There's a Gemfile in the repo, so you can run `bundle` to install rake -if you've got bundler installed. +Project dependencies may be installed via `npm install`. To build Handlebars.js from scratch, you'll want to run `grunt` in the root of the project. That will build Handlebars and output the -results to the dist/ folder. To run tests, run `grunt test` or `npm test`. -You can also run our set of benchmarks with `rake bench`. +results to the dist/ folder. To re-run tests, run `grunt test` or `npm test`. +You can also run our set of benchmarks with `grunt bench`. If you notice any problems, please report them to the GitHub issue tracker at [http://github.com/wycats/handlebars.js/issues](http://github.com/wycats/handlebars.js/issues). diff --git a/Rakefile b/Rakefile deleted file mode 100644 index e9d38ae77..000000000 --- a/Rakefile +++ /dev/null @@ -1,66 +0,0 @@ -require "rubygems" -require "bundler/setup" - -task :default => [:build] - -task :build do |task| - system "grunt" -end - -def dist_files(&block) - map = {} - - root = File.expand_path(File.dirname(__FILE__)) + '/dist/' - - files = ['handlebars.js', 'handlebars.min.js', 'handlebars.runtime.js', 'handlebars.runtime.min.js'].map { |file| root + file } - files = files.map do |file| - basename = Pathname.new(file).basename.sub_ext('') - map[file] = yield basename - end - map -end - -def publish_s3(files) - access_key_id = ENV['S3_ACCESS_KEY_ID'] - secret_access_key = ENV['S3_SECRET_ACCESS_KEY'] - bucket_name = ENV['S3_BUCKET_NAME'] - - if files && access_key_id && secret_access_key && bucket_name - require 'aws-sdk' - s3 = AWS::S3.new(access_key_id: access_key_id,secret_access_key: secret_access_key) - bucket = s3.buckets[bucket_name] - files.each do |source, outputs| - s3_objs = outputs.map do |file| - bucket.objects[file] - end - s3_objs.each { |obj| obj.write(Pathname.new(source)) } - end - else - puts "Not uploading any files to S3!" - end -end - -task :publish do - rev = `git rev-parse --short HEAD`.to_s.strip - master_rev = `git rev-parse --short origin/master`.to_s.strip - - if rev == master_rev - files = dist_files do |basename| - ["#{basename}-latest.js", "#{basename}-#{rev}.js"] - end - end - - publish_s3 files -end - -task :publish_version do - tag = `git tag -l --points-at HEAD`.to_s.strip.split(/\n/) - fail "The current commit must be tagged." if tag.empty? - fail "Multiple tags, aborting: #{tag}" if tag.length > 1 - tag = tag.first - - files = dist_files do |basename| - ["#{basename}-#{tag}.js"] - end - publish_s3 files -end From df0734ae5eeb66ba02ba8b3bdfc1192b9b70c4ef Mon Sep 17 00:00:00 2001 From: kpdecker Date: Sun, 25 Aug 2013 12:59:08 -0500 Subject: [PATCH 089/200] Move benchwarmer file into util directory --- bench/handlebars.js | 2 +- bench/{ => util}/benchwarmer.js | 0 2 files changed, 1 insertion(+), 1 deletion(-) rename bench/{ => util}/benchwarmer.js (100%) diff --git a/bench/handlebars.js b/bench/handlebars.js index 11414a5ff..36efdddbb 100644 --- a/bench/handlebars.js +++ b/bench/handlebars.js @@ -1,4 +1,4 @@ -var BenchWarmer = require("./benchwarmer"); +var BenchWarmer = require("./util/benchwarmer"); Handlebars = require("../lib/handlebars"); var dust, Mustache, eco; diff --git a/bench/benchwarmer.js b/bench/util/benchwarmer.js similarity index 100% rename from bench/benchwarmer.js rename to bench/util/benchwarmer.js From 86694fd122fe2cde5151ccd8e7f6cf9a9b5d5235 Mon Sep 17 00:00:00 2001 From: kpdecker Date: Sun, 25 Aug 2013 13:25:27 -0500 Subject: [PATCH 090/200] Move templates into standalone lib --- bench/handlebars.js | 82 +--------------------------- bench/templates/arguments.js | 12 ++++ bench/templates/array-each.js | 7 +++ bench/templates/array-mustache.js | 4 ++ bench/templates/complex.handlebars | 14 +++++ bench/templates/complex.js | 30 ++++++++++ bench/templates/index.js | 9 +++ bench/templates/object-mustache.js | 6 ++ bench/templates/object.js | 6 ++ bench/templates/partial-recursion.js | 10 ++++ bench/templates/partial.js | 11 ++++ bench/templates/string.js | 7 +++ bench/templates/variables.js | 8 +++ 13 files changed, 125 insertions(+), 81 deletions(-) create mode 100644 bench/templates/arguments.js create mode 100644 bench/templates/array-each.js create mode 100644 bench/templates/array-mustache.js create mode 100644 bench/templates/complex.handlebars create mode 100644 bench/templates/complex.js create mode 100644 bench/templates/index.js create mode 100644 bench/templates/object-mustache.js create mode 100644 bench/templates/object.js create mode 100644 bench/templates/partial-recursion.js create mode 100644 bench/templates/partial.js create mode 100644 bench/templates/string.js create mode 100644 bench/templates/variables.js diff --git a/bench/handlebars.js b/bench/handlebars.js index 36efdddbb..9ccffacc3 100644 --- a/bench/handlebars.js +++ b/bench/handlebars.js @@ -18,87 +18,7 @@ try { }; } catch (err) { /* NOP */ } -var benchDetails = { - string: { - context: {}, - handlebars: "Hello world", - dust: "Hello world", - mustache: "Hello world", - eco: "Hello world" - }, - variables: { - context: {name: "Mick", count: 30}, - handlebars: "Hello {{name}}! You have {{count}} new messages.", - dust: "Hello {name}! You have {count} new messages.", - mustache: "Hello {{name}}! You have {{count}} new messages.", - eco: "Hello <%= @name %>! You have <%= @count %> new messages." - }, - object: { - context: { person: { name: "Larry", age: 45 } }, - handlebars: "{{#with person}}{{name}}{{age}}{{/with}}", - dust: "{#person}{name}{age}{/person}", - mustache: "{{#person}}{{name}}{{age}}{{/person}}" - }, - array: { - context: { names: [{name: "Moe"}, {name: "Larry"}, {name: "Curly"}, {name: "Shemp"}] }, - handlebars: "{{#each names}}{{name}}{{/each}}", - dust: "{#names}{name}{/names}", - mustache: "{{#names}}{{name}}{{/names}}", - eco: "<% for item in @names: %><%= item.name %><% end %>" - }, - partial: { - context: { peeps: [{name: "Moe", count: 15}, {name: "Larry", count: 5}, {name: "Curly", count: 1}] }, - partials: { - mustache: { variables: "Hello {{name}}! You have {{count}} new messages." }, - handlebars: { variables: "Hello {{name}}! You have {{count}} new messages." } - }, - handlebars: "{{#each peeps}}{{>variables}}{{/each}}", - dust: "{#peeps}{>variables/}{/peeps}", - mustache: "{{#peeps}}{{>variables}}{{/peeps}}" - }, - recursion: { - context: { name: '1', kids: [{ name: '1.1', kids: [{name: '1.1.1', kids: []}] }] }, - partials: { - mustache: { recursion: "{{name}}{{#kids}}{{>recursion}}{{/kids}}" }, - handlebars: { recursion: "{{name}}{{#each kids}}{{>recursion}}{{/each}}" } - }, - handlebars: "{{name}}{{#each kids}}{{>recursion}}{{/each}}", - dust: "{name}{#kids}{>recursion:./}{/kids}", - mustache: "{{name}}{{#kids}}{{>recursion}}{{/kids}}" - }, - complex: { - handlebars: "

{{header}}

{{#if items}}
    {{#each items}}{{#if current}}" + - "
  • {{name}}
  • {{^}}" + - "
  • {{name}}
  • {{/if}}" + - "{{/each}}
{{^}}

The list is empty.

{{/if}}", - - dust: "

{header}

\n" + - "{?items}\n" + - "
    \n" + - " {#items}\n" + - " {#current}\n" + - "
  • {name}
  • \n" + - " {:else}\n" + - "
  • {name}
  • \n" + - " {/current}\n" + - " {/items}\n" + - "
\n" + - "{:else}\n" + - "

The list is empty.

\n" + - "{/items}", - context: { - header: function() { - return "Colors"; - }, - items: [ - {name: "red", current: true, url: "#Red"}, - {name: "green", current: false, url: "#Green"}, - {name: "blue", current: false, url: "#Blue"} - ] - } - } - -}; +var benchDetails = require('./templates'); var handlebarsTemplates = {}, ecoTemplates = {}; diff --git a/bench/templates/arguments.js b/bench/templates/arguments.js new file mode 100644 index 000000000..5480c8d8e --- /dev/null +++ b/bench/templates/arguments.js @@ -0,0 +1,12 @@ +module.exports = { + helpers: { + foo: function(options) { + return ''; + } + }, + context: { + bar: true + }, + + handlebars: '{{foo person "person" 1 true foo=bar foo="person" foo=1 foo=true}}' +}; diff --git a/bench/templates/array-each.js b/bench/templates/array-each.js new file mode 100644 index 000000000..f1eb1e8e9 --- /dev/null +++ b/bench/templates/array-each.js @@ -0,0 +1,7 @@ +module.exports = { + context: { names: [{name: "Moe"}, {name: "Larry"}, {name: "Curly"}, {name: "Shemp"}] }, + handlebars: "{{#each names}}{{name}}{{/each}}", + dust: "{#names}{name}{/names}", + mustache: "{{#names}}{{name}}{{/names}}", + eco: "<% for item in @names: %><%= item.name %><% end %>" +}; diff --git a/bench/templates/array-mustache.js b/bench/templates/array-mustache.js new file mode 100644 index 000000000..908f805ea --- /dev/null +++ b/bench/templates/array-mustache.js @@ -0,0 +1,4 @@ +module.exports = { + context: { names: [{name: "Moe"}, {name: "Larry"}, {name: "Curly"}, {name: "Shemp"}] }, + handlebars: "{{#names}}{{name}}{{/names}}" +} diff --git a/bench/templates/complex.handlebars b/bench/templates/complex.handlebars new file mode 100644 index 000000000..9e1a364e3 --- /dev/null +++ b/bench/templates/complex.handlebars @@ -0,0 +1,14 @@ +

{{header}}

+{{#if items}} +
    + {{#each items}} + {{#if current}} +
  • {{name}}
  • + {{^}} +
  • {{name}}
  • + {{/if}} + {{/each}} +
+{{^}} +

The list is empty.

+{{/if}} diff --git a/bench/templates/complex.js b/bench/templates/complex.js new file mode 100644 index 000000000..ecfbd16df --- /dev/null +++ b/bench/templates/complex.js @@ -0,0 +1,30 @@ +var fs = require('fs'); + +module.exports = { + context: { + header: function() { + return "Colors"; + }, + items: [ + {name: "red", current: true, url: "#Red"}, + {name: "green", current: false, url: "#Green"}, + {name: "blue", current: false, url: "#Blue"} + ] + }, + + handlebars: fs.readFileSync(__dirname + '/complex.handlebars').toString(), + dust: "

{header}

\n" + + "{?items}\n" + + "
    \n" + + " {#items}\n" + + " {#current}\n" + + "
  • {name}
  • \n" + + " {:else}\n" + + "
  • {name}
  • \n" + + " {/current}\n" + + " {/items}\n" + + "
\n" + + "{:else}\n" + + "

The list is empty.

\n" + + "{/items}" +}; diff --git a/bench/templates/index.js b/bench/templates/index.js new file mode 100644 index 000000000..a718ea388 --- /dev/null +++ b/bench/templates/index.js @@ -0,0 +1,9 @@ +var fs = require('fs'); + +var templates = fs.readdirSync(__dirname); +templates.forEach(function(template) { + if (template === 'index.js' || !/(.*)\.js$/.test(template)) { + return; + } + module.exports[RegExp.$1] = require('./' + RegExp.$1); +}); diff --git a/bench/templates/object-mustache.js b/bench/templates/object-mustache.js new file mode 100644 index 000000000..350571902 --- /dev/null +++ b/bench/templates/object-mustache.js @@ -0,0 +1,6 @@ +module.exports = { + context: { person: { name: "Larry", age: 45 } }, + handlebars: "{{#person}}{{name}}{{age}}{{/person}}", + dust: "{#person}{name}{age}{/person}", + mustache: "{{#person}}{{name}}{{age}}{{/person}}" +}; diff --git a/bench/templates/object.js b/bench/templates/object.js new file mode 100644 index 000000000..9bb91819a --- /dev/null +++ b/bench/templates/object.js @@ -0,0 +1,6 @@ +module.exports = { + context: { person: { name: "Larry", age: 45 } }, + handlebars: "{{#with person}}{{name}}{{age}}{{/with}}", + dust: "{#person}{name}{age}{/person}", + mustache: "{{#person}}{{name}}{{age}}{{/person}}" +}; diff --git a/bench/templates/partial-recursion.js b/bench/templates/partial-recursion.js new file mode 100644 index 000000000..9d604fd02 --- /dev/null +++ b/bench/templates/partial-recursion.js @@ -0,0 +1,10 @@ +module.exports = { + context: { name: '1', kids: [{ name: '1.1', kids: [{name: '1.1.1', kids: []}] }] }, + partials: { + mustache: { recursion: "{{name}}{{#kids}}{{>recursion}}{{/kids}}" }, + handlebars: { recursion: "{{name}}{{#each kids}}{{>recursion}}{{/each}}" } + }, + handlebars: "{{name}}{{#each kids}}{{>recursion}}{{/each}}", + dust: "{name}{#kids}{>recursion:./}{/kids}", + mustache: "{{name}}{{#kids}}{{>recursion}}{{/kids}}" +}; diff --git a/bench/templates/partial.js b/bench/templates/partial.js new file mode 100644 index 000000000..a6e663156 --- /dev/null +++ b/bench/templates/partial.js @@ -0,0 +1,11 @@ +module.exports = { + context: { peeps: [{name: "Moe", count: 15}, {name: "Larry", count: 5}, {name: "Curly", count: 1}] }, + partials: { + mustache: { variables: "Hello {{name}}! You have {{count}} new messages." }, + handlebars: { variables: "Hello {{name}}! You have {{count}} new messages." } + }, + + handlebars: "{{#each peeps}}{{>variables}}{{/each}}", + dust: "{#peeps}{>variables/}{/peeps}", + mustache: "{{#peeps}}{{>variables}}{{/peeps}}" +}; diff --git a/bench/templates/string.js b/bench/templates/string.js new file mode 100644 index 000000000..335e37cf9 --- /dev/null +++ b/bench/templates/string.js @@ -0,0 +1,7 @@ +module.exports = { + context: {}, + handlebars: "Hello world", + dust: "Hello world", + mustache: "Hello world", + eco: "Hello world" +}; diff --git a/bench/templates/variables.js b/bench/templates/variables.js new file mode 100644 index 000000000..d354238b1 --- /dev/null +++ b/bench/templates/variables.js @@ -0,0 +1,8 @@ +module.exports = { + context: {name: "Mick", count: 30}, + handlebars: "Hello {{name}}! You have {{count}} new messages.", + dust: "Hello {name}! You have {count} new messages.", + mustache: "Hello {{name}}! You have {{count}} new messages.", + eco: "Hello <%= @name %>! You have <%= @count %> new messages." +}; + From 57a0ee3d596f73db0a854f3a042ee94e3bae8bf0 Mon Sep 17 00:00:00 2001 From: kpdecker Date: Sun, 25 Aug 2013 13:46:45 -0500 Subject: [PATCH 091/200] Remove duplicated tests --- bench/templates/object-mustache.js | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/bench/templates/object-mustache.js b/bench/templates/object-mustache.js index 350571902..52dbc26e3 100644 --- a/bench/templates/object-mustache.js +++ b/bench/templates/object-mustache.js @@ -1,6 +1,4 @@ module.exports = { context: { person: { name: "Larry", age: 45 } }, - handlebars: "{{#person}}{{name}}{{age}}{{/person}}", - dust: "{#person}{name}{age}{/person}", - mustache: "{{#person}}{{name}}{{age}}{{/person}}" + handlebars: "{{#person}}{{name}}{{age}}{{/person}}" }; From 74369f724c1cf5d658736bd581348677f464f76c Mon Sep 17 00:00:00 2001 From: kpdecker Date: Sun, 25 Aug 2013 14:13:40 -0500 Subject: [PATCH 092/200] Track benchwarmer times --- bench/util/benchwarmer.js | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/bench/util/benchwarmer.js b/bench/util/benchwarmer.js index 72bfb0191..72ba3ea16 100644 --- a/bench/util/benchwarmer.js +++ b/bench/util/benchwarmer.js @@ -5,6 +5,7 @@ var BenchWarmer = function(names) { this.benchmarks = []; this.currentBenches = []; this.names = []; + this.times = {}; this.errors = {}; }; @@ -16,6 +17,7 @@ BenchWarmer.prototype = { }, suite: function(suite, fn) { this.suiteName = suite; + this.times[suite] = {}; this.first = true; var self = this; @@ -42,10 +44,12 @@ BenchWarmer.prototype = { self.errors[this.name] = this; } }); + bench.suiteName = this.suiteName; + bench.benchName = name; this.benchmarks.push(bench); }, - bench: function() { + bench: function(callback) { var benchSize = 0, names = this.names, self = this, i, l; for(i=0, l=names.length; i Date: Sun, 25 Aug 2013 14:14:33 -0500 Subject: [PATCH 093/200] Metrics collection framework --- .travis.yml | 1 + Gruntfile.js | 12 +-------- bench/index.js | 14 +++++++++++ tasks/metrics.js | 63 ++++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 79 insertions(+), 11 deletions(-) create mode 100644 bench/index.js create mode 100644 tasks/metrics.js diff --git a/.travis.yml b/.travis.yml index 6eace317e..5c62e3023 100644 --- a/.travis.yml +++ b/.travis.yml @@ -11,6 +11,7 @@ script: after_success: - grunt publish + - grunt metrics email: on_failure: change diff --git a/Gruntfile.js b/Gruntfile.js index 25e24c5be..a3327167c 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -87,17 +87,7 @@ module.exports = function(grunt) { done(); }); }); - grunt.registerTask('bench', function() { - var done = this.async(); - - var runner = childProcess.fork('./bench/handlebars', [], {stdio: 'inherit'}); - runner.on('close', function(code) { - if (code != 0) { - grunt.fatal(code + ' tests failed'); - } - done(); - }); - }); + grunt.registerTask('bench', ['metrics']); grunt.registerTask('build', ['jshint', 'parser', 'dist-dir', 'concat', 'uglify', 'test']); grunt.registerTask('default', 'build'); diff --git a/bench/index.js b/bench/index.js new file mode 100644 index 000000000..462b046f5 --- /dev/null +++ b/bench/index.js @@ -0,0 +1,14 @@ +var fs = require('fs'); + +var metrics = fs.readdirSync(__dirname); +metrics.forEach(function(metric) { + if (metric === 'index.js' || !/(.*)\.js$/.test(metric)) { + return; + } + + var name = RegExp.$1; + metric = require('./' + name); + if (metric instanceof Function) { + module.exports[name] = metric; + } +}); diff --git a/tasks/metrics.js b/tasks/metrics.js new file mode 100644 index 000000000..c4a202b15 --- /dev/null +++ b/tasks/metrics.js @@ -0,0 +1,63 @@ +var _ = require('underscore'), + async = require('async'), + git = require('./util/git'), + Keen = require('keen.io'), + metrics = require('../bench'); + +module.exports = function(grunt) { + grunt.registerTask('metrics', function() { + var done = this.async(), + execName = grunt.option('name'), + events = {}, + + projectId = process.env.KEEN_PROJECTID, + writeKey = process.env.KEEN_WRITEKEY, + keen; + + if (!execName && projectId && writeKey) { + keen = Keen.configure({ + projectId: projectId, + writeKey: writeKey + }); + } + + async.each(_.keys(metrics), function(name, complete) { + if (/^_/.test(name) || (execName && name !== execName)) { + return complete(); + } + + metrics[name](grunt, function(data) { + events[name] = data; + complete(); + }); + }, + function() { + if (!keen) { + return done(); + } + + emit(keen, events, function(err, res) { + if (err) { + throw err; + } + + grunt.log.writeln('Metrics recorded.'); + done(); + }); + }); + }); +}; +function emit(keen, collections, callback) { + git.commitInfo(function(err, info) { + _.each(collections, function(collection) { + _.each(collection, function(event) { + if (info.tagName) { + event.tag = info.tagName; + } + event.sha = info.head; + }); + }); + + keen.addEvents(collections, callback); + }); +} From ab64fec1a8036e165c64dbee45c5547fcdadcd47 Mon Sep 17 00:00:00 2001 From: kpdecker Date: Sun, 25 Aug 2013 14:14:47 -0500 Subject: [PATCH 094/200] Collect size metrics --- bench/dist-size.js | 15 +++++++++++++++ bench/precompile-size.js | 19 +++++++++++++++++++ 2 files changed, 34 insertions(+) create mode 100644 bench/dist-size.js create mode 100644 bench/precompile-size.js diff --git a/bench/dist-size.js b/bench/dist-size.js new file mode 100644 index 000000000..b063beec1 --- /dev/null +++ b/bench/dist-size.js @@ -0,0 +1,15 @@ +var _ = require('underscore'), + fs = require('fs'); + +module.exports = function(grunt, callback) { + var distFiles = fs.readdirSync('dist'), + distSizes = {}; + + _.each(distFiles, function(file) { + var stat = fs.statSync('dist/' + file); + distSizes[file.replace(/\.js/, '').replace(/\./g, '_')] = stat.size; + }); + + grunt.log.writeln('Distribution sizes: ' + JSON.stringify(distSizes, undefined, 2)); + callback([distSizes]); +}; diff --git a/bench/precompile-size.js b/bench/precompile-size.js new file mode 100644 index 000000000..12fb0dc0c --- /dev/null +++ b/bench/precompile-size.js @@ -0,0 +1,19 @@ +var _ = require('underscore'), + templates = require('./templates'); + +module.exports = function(grunt, callback) { + // Deferring to here in case we have a build for parser, etc as part of this grunt exec + var Handlebars = require('../lib/handlebars'); + + var templateSizes = {}; + _.each(templates, function(info, template) { + var src = info.handlebars, + compiled = Handlebars.precompile(src, {}), + knownHelpers = Handlebars.precompile(src, {knownHelpersOnly: true, knownHelpers: info.helpers}); + + templateSizes[template] = compiled.length; + templateSizes['knownOnly_' + template] = knownHelpers.length; + }); + grunt.log.writeln('Precompiled sizes: ' + JSON.stringify(templateSizes, undefined, 2)); + callback([templateSizes]); +}; From b488af39f1e6564202822612e121a967f6a86417 Mon Sep 17 00:00:00 2001 From: kpdecker Date: Sun, 25 Aug 2013 14:16:12 -0500 Subject: [PATCH 095/200] Throughput metric tracking --- bench/handlebars.js | 104 -------------------------------------------- bench/throughput.js | 97 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 97 insertions(+), 104 deletions(-) delete mode 100644 bench/handlebars.js create mode 100644 bench/throughput.js diff --git a/bench/handlebars.js b/bench/handlebars.js deleted file mode 100644 index 9ccffacc3..000000000 --- a/bench/handlebars.js +++ /dev/null @@ -1,104 +0,0 @@ -var BenchWarmer = require("./util/benchwarmer"); -Handlebars = require("../lib/handlebars"); - -var dust, Mustache, eco; - -try { - dust = require("dustjs-linkedin"); -} catch (err) { /* NOP */ } - -try { - Mustache = require("mustache"); -} catch (err) { /* NOP */ } - -try { - var ecoExports = require("eco"); - eco = function(str) { - return ecoExports(str); - }; -} catch (err) { /* NOP */ } - -var benchDetails = require('./templates'); - -var handlebarsTemplates = {}, - ecoTemplates = {}; - -var warmer = new BenchWarmer(); - -function makeSuite(name) { - warmer.suite(name, function(bench) { - var templateName = name; - var details = benchDetails[templateName]; - var mustachePartials = details.partials && details.partials.mustache; - var mustacheSource = details.mustache; - var context = details.context; - var options = {helpers: details.helpers}; - - var error = function() { throw new Error("EWOT"); }; - - bench("handlebars", function() { - handlebarsTemplates[templateName](context, options); - }); - - if (dust) { - if (details.dust) { - bench("dust", function() { - dust.render(templateName, context, function(err, out) { }); - }); - } else { - bench('dust', error); - } - } - - if (eco) { - if(ecoTemplates[templateName]) { - bench("eco", function() { - ecoTemplates[templateName](context); - }); - } else { - bench("eco", error); - } - } - - if (Mustache) { - if (mustacheSource) { - bench("mustache", function() { - Mustache.to_html(mustacheSource, context, mustachePartials); - }); - } else { - bench("mustache", error); - } - } - }); -} - -for(var name in benchDetails) { - if(benchDetails.hasOwnProperty(name)) { - if (!benchDetails[name].handlebars) { - continue; - } - - if (dust && benchDetails[name].dust) { - dust.loadSource(dust.compile(benchDetails[name].dust, name)); - } - - handlebarsTemplates[name] = Handlebars.compile(benchDetails[name].handlebars); - - if (eco && benchDetails[name].eco) { - ecoTemplates[name] = eco(benchDetails[name].eco); - } - - var partials = benchDetails[name].partials; - if(partials) { - for(var partialName in partials.handlebars) { - if(partials.handlebars.hasOwnProperty(partialName)) { - Handlebars.registerPartial(partialName, partials.handlebars[partialName]); - } - } - } - - makeSuite(name); - } -} - -warmer.bench(); diff --git a/bench/throughput.js b/bench/throughput.js new file mode 100644 index 000000000..3a018cd6a --- /dev/null +++ b/bench/throughput.js @@ -0,0 +1,97 @@ +var _ = require('underscore'), + BenchWarmer = require('./util/benchwarmer'), + templates = require('./templates'), + + eco, dust, Handlebars, Mustache, eco; + +try { + dust = require("dustjs-linkedin"); +} catch (err) { /* NOP */ } + +try { + Mustache = require("mustache"); +} catch (err) { /* NOP */ } + +try { + eco = require("eco"); +} catch (err) { /* NOP */ } + +function error() { + throw new Error("EWOT"); +} + +function makeSuite(warmer, name, template) { + warmer.suite(name, function(bench) { + // Create aliases to minimize any impact from having to walk up the closure tree. + var templateName = name, + + context = template.context, + partials = template.partials; + + var handlebar = Handlebars.compile(template.handlebars), + options = {helpers: template.helpers}; + _.each(template.partials && template.partials.handlebars, function(partial, name) { + Handlebars.registerPartial(name, partial); + }); + + bench("handlebars", function() { + handlebar(context, options); + }); + + if (dust) { + if (template.dust) { + dust.loadSource(dust.compile(template.dust, templateName)); + + bench("dust", function() { + dust.render(templateName, context, function(err, out) { }); + }); + } else { + bench('dust', error); + } + } + + if (eco) { + if (template.eco) { + var ecoTemplate = eco(template.eco); + + bench("eco", function() { + ecoTemplate(context); + }); + } else { + bench("eco", error); + } + } + + if (Mustache) { + var mustacheSource = template.mustache, + mustachePartials = partials && partials.mustache; + + if (mustacheSource) { + bench("mustache", function() { + Mustache.to_html(mustacheSource, context, mustachePartials); + }); + } else { + bench("mustache", error); + } + } + }); +} + +module.exports = function(grunt, callback) { + // Deferring load incase we are being run inline with the grunt build + Handlebars = require('../lib/handlebars'); + + var warmer = new BenchWarmer(); + + _.each(templates, function(template, name) { + if (!template.handlebars) { + return; + } + + makeSuite(warmer, name, template); + }); + + warmer.bench(function() { + callback && callback(warmer.times); + }); +}; From c03b2f4dfad0ed168bfd7a86565c9077c37d872a Mon Sep 17 00:00:00 2001 From: kpdecker Date: Sun, 25 Aug 2013 14:17:14 -0500 Subject: [PATCH 096/200] Import keen lib --- package.json | 1 + 1 file changed, 1 insertion(+) diff --git a/package.json b/package.json index 2aff973b8..741c0c0ab 100644 --- a/package.json +++ b/package.json @@ -31,6 +31,7 @@ "grunt-contrib-jshint": "~0.6.3", "grunt-contrib-uglify": "~0.2.2", "jison": "~0.3", + "keen.io": "0.0.3", "mocha": "*", "mustache": "~0.7.2", "semver": "~2.1.0", From bf99516e5516100ea775d19ce55bbc9bac071b7b Mon Sep 17 00:00:00 2001 From: kpdecker Date: Sun, 25 Aug 2013 14:28:19 -0500 Subject: [PATCH 097/200] Run all grunt commands as one --- .travis.yml | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/.travis.yml b/.travis.yml index 5c62e3023..6c19fa220 100644 --- a/.travis.yml +++ b/.travis.yml @@ -7,11 +7,7 @@ before_script: - npm install -g grunt-cli script: - - grunt - -after_success: - - grunt publish - - grunt metrics + - grunt build metrics publish email: on_failure: change From 3c66068279daafd568a91b7dbc546cac0e79e222 Mon Sep 17 00:00:00 2001 From: kpdecker Date: Sun, 25 Aug 2013 14:31:20 -0500 Subject: [PATCH 098/200] Add gzip estimation to dist sizes output --- bench/dist-size.js | 28 +++++++++++++++++++++------- 1 file changed, 21 insertions(+), 7 deletions(-) diff --git a/bench/dist-size.js b/bench/dist-size.js index b063beec1..26d15911d 100644 --- a/bench/dist-size.js +++ b/bench/dist-size.js @@ -1,15 +1,29 @@ var _ = require('underscore'), - fs = require('fs'); + async = require('async'), + fs = require('fs'), + zlib = require('zlib'); module.exports = function(grunt, callback) { var distFiles = fs.readdirSync('dist'), distSizes = {}; - _.each(distFiles, function(file) { - var stat = fs.statSync('dist/' + file); - distSizes[file.replace(/\.js/, '').replace(/\./g, '_')] = stat.size; - }); + async.each(distFiles, function(file, callback) { + var content = fs.readFileSync('dist/' + file); - grunt.log.writeln('Distribution sizes: ' + JSON.stringify(distSizes, undefined, 2)); - callback([distSizes]); + file = file.replace(/\.js/, '').replace(/\./g, '_'); + distSizes[file] = content.length; + + zlib.gzip(content, function(err, data) { + if (err) { + throw err; + } + + distSizes[file + '_gz'] = data.length; + callback(); + }); + }, + function() { + grunt.log.writeln('Distribution sizes: ' + JSON.stringify(distSizes, undefined, 2)); + callback([distSizes]); + }); }; From 9689ebaa1cc3abeac8e292306e501acb964b453a Mon Sep 17 00:00:00 2001 From: kpdecker Date: Sun, 25 Aug 2013 14:58:31 -0500 Subject: [PATCH 099/200] Add handlebars-only and grep metrics options --- bench/throughput.js | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/bench/throughput.js b/bench/throughput.js index 3a018cd6a..48a27dd80 100644 --- a/bench/throughput.js +++ b/bench/throughput.js @@ -20,7 +20,7 @@ function error() { throw new Error("EWOT"); } -function makeSuite(warmer, name, template) { +function makeSuite(warmer, name, template, handlebarsOnly) { warmer.suite(name, function(bench) { // Create aliases to minimize any impact from having to walk up the closure tree. var templateName = name, @@ -38,6 +38,10 @@ function makeSuite(warmer, name, template) { handlebar(context, options); }); + if (handlebarsOnly) { + return; + } + if (dust) { if (template.dust) { dust.loadSource(dust.compile(template.dust, templateName)); @@ -83,12 +87,18 @@ module.exports = function(grunt, callback) { var warmer = new BenchWarmer(); + var handlebarsOnly = grunt.option('handlebars-only'), + grep = grunt.option('grep'); + if (grep) { + grep = new RegExp(grep); + } + _.each(templates, function(template, name) { - if (!template.handlebars) { + if (!template.handlebars || (grep && !grep.test(name))) { return; } - makeSuite(warmer, name, template); + makeSuite(warmer, name, template, handlebarsOnly); }); warmer.bench(function() { From 496ebaa1a825c828819e687365edd3a74018c285 Mon Sep 17 00:00:00 2001 From: kpdecker Date: Sun, 25 Aug 2013 14:59:11 -0500 Subject: [PATCH 100/200] Remove unnecessary escapes --- bench/templates/complex.handlebars | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bench/templates/complex.handlebars b/bench/templates/complex.handlebars index 9e1a364e3..4a5953445 100644 --- a/bench/templates/complex.handlebars +++ b/bench/templates/complex.handlebars @@ -5,7 +5,7 @@ {{#if current}}
  • {{name}}
  • {{^}} -
  • {{name}}
  • +
  • {{name}}
  • {{/if}} {{/each}} From 709d57b3c15aa0d766152362b031ee9d27c054f4 Mon Sep 17 00:00:00 2001 From: kpdecker Date: Sun, 25 Aug 2013 15:28:52 -0500 Subject: [PATCH 101/200] Add eco object test --- bench/templates/object.js | 1 + 1 file changed, 1 insertion(+) diff --git a/bench/templates/object.js b/bench/templates/object.js index 9bb91819a..fef127ada 100644 --- a/bench/templates/object.js +++ b/bench/templates/object.js @@ -2,5 +2,6 @@ module.exports = { context: { person: { name: "Larry", age: 45 } }, handlebars: "{{#with person}}{{name}}{{age}}{{/with}}", dust: "{#person}{name}{age}{/person}", + eco: "<%= @person.name %><%= @person.age %>", mustache: "{{#person}}{{name}}{{age}}{{/person}}" }; From 0d1b0c049311874e86b22b7a7189979910e84a1d Mon Sep 17 00:00:00 2001 From: kpdecker Date: Sun, 25 Aug 2013 15:29:32 -0500 Subject: [PATCH 102/200] Ensure proper output from tests --- bench/throughput.js | 37 +++++++++++++++++++++++++++++++++++-- 1 file changed, 35 insertions(+), 2 deletions(-) diff --git a/bench/throughput.js b/bench/throughput.js index 48a27dd80..8477c7888 100644 --- a/bench/throughput.js +++ b/bench/throughput.js @@ -26,7 +26,12 @@ function makeSuite(warmer, name, template, handlebarsOnly) { var templateName = name, context = template.context, - partials = template.partials; + partials = template.partials, + + handlebarsOut, + dustOut, + ecoOut, + mustacheOut; var handlebar = Handlebars.compile(template.handlebars), options = {helpers: template.helpers}; @@ -34,6 +39,7 @@ function makeSuite(warmer, name, template, handlebarsOnly) { Handlebars.registerPartial(name, partial); }); + handlebarsOut = handlebar(context, options); bench("handlebars", function() { handlebar(context, options); }); @@ -44,8 +50,11 @@ function makeSuite(warmer, name, template, handlebarsOnly) { if (dust) { if (template.dust) { + dustOut = false; dust.loadSource(dust.compile(template.dust, templateName)); + dust.render(templateName, context, function(err, out) { dustOut = out; }); + bench("dust", function() { dust.render(templateName, context, function(err, out) { }); }); @@ -56,7 +65,9 @@ function makeSuite(warmer, name, template, handlebarsOnly) { if (eco) { if (template.eco) { - var ecoTemplate = eco(template.eco); + var ecoTemplate = eco.compile(template.eco); + + ecoOut = ecoTemplate(context); bench("eco", function() { ecoTemplate(context); @@ -71,6 +82,8 @@ function makeSuite(warmer, name, template, handlebarsOnly) { mustachePartials = partials && partials.mustache; if (mustacheSource) { + mustacheOut = Mustache.to_html(mustacheSource, context, mustachePartials); + bench("mustache", function() { Mustache.to_html(mustacheSource, context, mustachePartials); }); @@ -78,6 +91,26 @@ function makeSuite(warmer, name, template, handlebarsOnly) { bench("mustache", error); } } + + // Hack around whitespace until we have whitespace control + handlebarsOut = handlebarsOut.replace(/\s/g, ''); + function compare(b, lang) { + if (b == null) { + return; + } + + b = b.replace(/\s/g, ''); + + if (handlebarsOut !== b) { + throw new Error('Template output mismatch: ' + name + + '\n\nHandlebars: ' + handlebarsOut + + '\n\n' + lang + ': ' + b); + } + } + + compare(dustOut, 'dust'); + compare(ecoOut, 'eco'); + compare(mustacheOut, 'mustache'); }); } From 1df1756c71e890d5bad26555154cd3662f40cb67 Mon Sep 17 00:00:00 2001 From: kpdecker Date: Sun, 25 Aug 2013 15:38:17 -0500 Subject: [PATCH 103/200] Add complex templates for eco and mustache --- bench/templates/complex.dust | 14 ++++++++++++++ bench/templates/complex.eco | 14 ++++++++++++++ bench/templates/complex.js | 18 ++++-------------- bench/templates/complex.mustache | 13 +++++++++++++ 4 files changed, 45 insertions(+), 14 deletions(-) create mode 100644 bench/templates/complex.dust create mode 100644 bench/templates/complex.eco create mode 100644 bench/templates/complex.mustache diff --git a/bench/templates/complex.dust b/bench/templates/complex.dust new file mode 100644 index 000000000..ff453c2b8 --- /dev/null +++ b/bench/templates/complex.dust @@ -0,0 +1,14 @@ +

    {header}

    +{?items} +
      + {#items} + {#current} +
    • {name}
    • + {:else} +
    • {name}
    • + {/current} + {/items} +
    +{:else} +

    The list is empty.

    +{/items} diff --git a/bench/templates/complex.eco b/bench/templates/complex.eco new file mode 100644 index 000000000..42fb55b48 --- /dev/null +++ b/bench/templates/complex.eco @@ -0,0 +1,14 @@ +

    <%= @header() %>

    +<% if @items.length: %> +
      + <% for item in @items: %> + <% if item.current: %> +
    • <%= item.name %>
    • + <% else: %> +
    • <%= item.name %>
    • + <% end %> + <% end %> +
    +<% else: %> +

    The list is empty.

    +<% end %> diff --git a/bench/templates/complex.js b/bench/templates/complex.js index ecfbd16df..ddf361b59 100644 --- a/bench/templates/complex.js +++ b/bench/templates/complex.js @@ -5,6 +5,7 @@ module.exports = { header: function() { return "Colors"; }, + hasItems: true, // To make things fairer in mustache land due to no `{{if}}` construct on arrays items: [ {name: "red", current: true, url: "#Red"}, {name: "green", current: false, url: "#Green"}, @@ -13,18 +14,7 @@ module.exports = { }, handlebars: fs.readFileSync(__dirname + '/complex.handlebars').toString(), - dust: "

    {header}

    \n" + - "{?items}\n" + - "
      \n" + - " {#items}\n" + - " {#current}\n" + - "
    • {name}
    • \n" + - " {:else}\n" + - "
    • {name}
    • \n" + - " {/current}\n" + - " {/items}\n" + - "
    \n" + - "{:else}\n" + - "

    The list is empty.

    \n" + - "{/items}" + dust: fs.readFileSync(__dirname + '/complex.dust').toString(), + eco: fs.readFileSync(__dirname + '/complex.eco').toString(), + mustache: fs.readFileSync(__dirname + '/complex.mustache').toString() }; diff --git a/bench/templates/complex.mustache b/bench/templates/complex.mustache new file mode 100644 index 000000000..9e425872f --- /dev/null +++ b/bench/templates/complex.mustache @@ -0,0 +1,13 @@ +

    {{header}}

    +{{#hasItems}} +
      + {{#items}} + {{#current}} +
    • {{name}}
    • + {{/current}} + {{^current}} +
    • {{name}}
    • + {{/current}} + {{/items}} +
    +{{/hasItems}} From 2dac9c978a16ee353661c003769266dde337ff41 Mon Sep 17 00:00:00 2001 From: kpdecker Date: Sun, 25 Aug 2013 15:57:29 -0500 Subject: [PATCH 104/200] Add paths throughput test --- bench/templates/paths.js | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 bench/templates/paths.js diff --git a/bench/templates/paths.js b/bench/templates/paths.js new file mode 100644 index 000000000..0a426dd83 --- /dev/null +++ b/bench/templates/paths.js @@ -0,0 +1,7 @@ +module.exports = { + context: { person: { name: "Larry", age: 45 } }, + handlebars: "{{person.name}}{{person.age}}{{person.foo}}{{animal.age}}", + dust: "{person.name}{person.age}{person.foo}{animal.age}", + eco: "<%= @person.name %><%= @person.age %><%= @person.foo %><% if @animal: %><%= @animal.age %><% end %>", + mustache: "{{person.name}}{{person.age}}{{person.foo}}{{animal.age}}" +}; From 84b852c1373aed84d9a3b1da4c6e25dfb7532183 Mon Sep 17 00:00:00 2001 From: kpdecker Date: Sun, 25 Aug 2013 15:57:39 -0500 Subject: [PATCH 105/200] Add data throughput test --- bench/templates/data.js | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 bench/templates/data.js diff --git a/bench/templates/data.js b/bench/templates/data.js new file mode 100644 index 000000000..f532decd5 --- /dev/null +++ b/bench/templates/data.js @@ -0,0 +1,4 @@ +module.exports = { + context: { names: [{name: "Moe"}, {name: "Larry"}, {name: "Curly"}, {name: "Shemp"}] }, + handlebars: "{{#each names}}{{@index}}{{name}}{{/each}}" +} From a28be7c3c0a4565207373afaacb9534653814f90 Mon Sep 17 00:00:00 2001 From: kpdecker Date: Sun, 25 Aug 2013 16:09:29 -0500 Subject: [PATCH 106/200] Default data to off for performance tests --- bench/throughput.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/bench/throughput.js b/bench/throughput.js index 8477c7888..4865e5a60 100644 --- a/bench/throughput.js +++ b/bench/throughput.js @@ -33,10 +33,10 @@ function makeSuite(warmer, name, template, handlebarsOnly) { ecoOut, mustacheOut; - var handlebar = Handlebars.compile(template.handlebars), + var handlebar = Handlebars.compile(template.handlebars, {data: false}), options = {helpers: template.helpers}; _.each(template.partials && template.partials.handlebars, function(partial, name) { - Handlebars.registerPartial(name, partial); + Handlebars.registerPartial(name, Handlebars.compile(partial, {data: false})); }); handlebarsOut = handlebar(context, options); From 115db76f66c6194d23abd0a685cdd3dfcdb82264 Mon Sep 17 00:00:00 2001 From: kpdecker Date: Sun, 25 Aug 2013 16:11:05 -0500 Subject: [PATCH 107/200] Additional logging on git command error messages --- tasks/util/git.js | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/tasks/util/git.js b/tasks/util/git.js index 53e0237db..bd5eca022 100644 --- a/tasks/util/git.js +++ b/tasks/util/git.js @@ -25,7 +25,7 @@ module.exports = { head: function(callback) { childProcess.exec('git rev-parse --short HEAD', {}, function(err, stdout) { if (err) { - throw err; + throw new Error('git.head: ' + err.message); } callback(undefined, stdout.trim()); @@ -34,7 +34,7 @@ module.exports = { master: function(callback) { childProcess.exec('git rev-parse --short origin/master', {}, function(err, stdout) { if (err) { - throw err; + throw new Error('git.master: ' + err.message); } callback(undefined, stdout.trim()); @@ -44,7 +44,7 @@ module.exports = { add: function(path, callback) { childProcess.exec('git add -f ' + path, {}, function(err, stdout) { if (err) { - throw err; + throw new Error('git.add: ' + err.message); } callback(); @@ -53,7 +53,7 @@ module.exports = { commit: function(name, callback) { childProcess.exec('git commit --message=' + name, {}, function(err, stdout) { if (err) { - throw err; + throw new Error('git.commit: ' + err.message); } callback(); @@ -61,8 +61,8 @@ module.exports = { }, tag: function(name, callback) { childProcess.exec('git tag -a --message=' + name + ' ' + name, {}, function(err, stdout, stderr) { - console.log(name, stdout, stderr); if (err) { + throw new Error('git.tag: ' + err.message); throw err; } @@ -72,7 +72,7 @@ module.exports = { tagName: function(callback) { childProcess.exec('git tag -l --points-at HEAD', {}, function(err, stdout) { if (err) { - throw err; + throw new Error('git.tagName: ' + err.message); } var tags = stdout.trim().split(/\n/), From 1b4b8ed1da58194d62ed2c7b8a62f9024790c5f1 Mon Sep 17 00:00:00 2001 From: kpdecker Date: Sun, 25 Aug 2013 16:12:07 -0500 Subject: [PATCH 108/200] Rename to publish latest --- .travis.yml | 2 +- tasks/publish.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index 6c19fa220..0516269ce 100644 --- a/.travis.yml +++ b/.travis.yml @@ -7,7 +7,7 @@ before_script: - npm install -g grunt-cli script: - - grunt build metrics publish + - grunt build metrics publish:latest email: on_failure: change diff --git a/tasks/publish.js b/tasks/publish.js index 9c3d1d636..4e2cdc316 100644 --- a/tasks/publish.js +++ b/tasks/publish.js @@ -4,7 +4,7 @@ var _ = require('underscore'), git = require('./util/git'); module.exports = function(grunt) { - grunt.registerTask('publish', function() { + grunt.registerTask('publish:latest', function() { var done = this.async(); initSDK(); From dac055f01e18cd35253a23d7aa81a5f2529b8125 Mon Sep 17 00:00:00 2001 From: kpdecker Date: Sun, 25 Aug 2013 18:55:21 -0500 Subject: [PATCH 109/200] Add git debugging to publish command --- tasks/publish.js | 19 ++++++++++++------- tasks/util/git.js | 15 +++++++++++++++ 2 files changed, 27 insertions(+), 7 deletions(-) diff --git a/tasks/publish.js b/tasks/publish.js index 4e2cdc316..72a4643de 100644 --- a/tasks/publish.js +++ b/tasks/publish.js @@ -8,13 +8,18 @@ module.exports = function(grunt) { var done = this.async(); initSDK(); - git.commitInfo(function(err, info) { - if (info.isMaster) { - publish(fileMap(['-latest', '-' + info.head]), done); - } else { - // Silently ignore for branches - done(); - } + git.debug(function(remotes, branches) { + grunt.log.writeln('remotes: ' + remotes); + grunt.log.writeln('branches: ' + branches); + + git.commitInfo(function(err, info) { + if (info.isMaster) { + publish(fileMap(['-latest', '-' + info.head]), done); + } else { + // Silently ignore for branches + done(); + } + }); }); }); grunt.registerTask('publish:version', function() { diff --git a/tasks/util/git.js b/tasks/util/git.js index bd5eca022..d54623f83 100644 --- a/tasks/util/git.js +++ b/tasks/util/git.js @@ -1,6 +1,21 @@ var childProcess = require('child_process'); module.exports = { + debug: function(callback) { + childProcess.exec('git remote -v', {}, function(err, remotes) { + if (err) { + throw new Error('git.remote: ' + err.message); + } + + childProcess.exec('git branch -a', {}, function(err, branches) { + if (err) { + throw new Error('git.branch: ' + err.message); + } + + callback(remotes, branches); + }); + }); + }, clean: function(callback) { childProcess.exec('git diff-index --name-only HEAD --', {}, function(err, stdout) { callback(undefined, !err && !stdout); From 54cda3b3aefdf46ebc4cf3585bbda8f48e952c73 Mon Sep 17 00:00:00 2001 From: kpdecker Date: Sun, 25 Aug 2013 19:12:18 -0500 Subject: [PATCH 110/200] Handle missing revision error in publish --- tasks/util/git.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/tasks/util/git.js b/tasks/util/git.js index d54623f83..dc57c91b6 100644 --- a/tasks/util/git.js +++ b/tasks/util/git.js @@ -48,7 +48,9 @@ module.exports = { }, master: function(callback) { childProcess.exec('git rev-parse --short origin/master', {}, function(err, stdout) { - if (err) { + // This will error if master was not checked out but in this case we know we are not master + // so we can ignore. + if (err && !/Needed a single revision/.test(err.message)) { throw new Error('git.master: ' + err.message); } From 97fe2decc9a9cb13cf833d4a35eb192116453717 Mon Sep 17 00:00:00 2001 From: kpdecker Date: Sun, 25 Aug 2013 19:12:31 -0500 Subject: [PATCH 111/200] Track min and max time in benchwarmer --- bench/util/benchwarmer.js | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/bench/util/benchwarmer.js b/bench/util/benchwarmer.js index 72ba3ea16..a0bd42400 100644 --- a/bench/util/benchwarmer.js +++ b/bench/util/benchwarmer.js @@ -6,6 +6,8 @@ var BenchWarmer = function(names) { this.currentBenches = []; this.names = []; this.times = {}; + this.minimum = Infinity; + this.maximum = -Infinity; this.errors = {}; }; @@ -128,12 +130,20 @@ BenchWarmer.prototype = { if(!bench.error) { var count = bench.hz, - moe = count * bench.stats.rme / 100; + moe = count * bench.stats.rme / 100, + minimum, + maximum; - out = Math.round(count / 1000) + " ±" + Math.round(moe / 1000) + " (" + bench.cycles + ")"; + count = Math.round(count / 1000); + moe = Math.round(moe / 1000); + minimum = count - moe; + maximum = count + moe; + out = count + " ±" + moe + " (" + bench.cycles + ")"; - this.times[bench.suiteName][bench.benchName] = Math.round(count / 1000); + this.times[bench.suiteName][bench.benchName] = count; + this.minimum = Math.min(this.minimum, minimum); + this.maximum = Math.max(this.maximum, maximum); } else { if (bench.error.message === 'EWOT') { out = 'NA'; From 28c4f4b9ddbab49bd352946dc0b5bdd822e0a22a Mon Sep 17 00:00:00 2001 From: kpdecker Date: Sun, 25 Aug 2013 19:13:51 -0500 Subject: [PATCH 112/200] Output scaled throughput values to console --- bench/throughput.js | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/bench/throughput.js b/bench/throughput.js index 4865e5a60..67c2290b5 100644 --- a/bench/throughput.js +++ b/bench/throughput.js @@ -135,6 +135,18 @@ module.exports = function(grunt, callback) { }); warmer.bench(function() { + grunt.log.writeln(); // Clear out any trailing contnet + + var scaled = {}; + _.each(warmer.times, function(times, name) { + var output = scaled[name] = {}; + + _.each(times, function(time, lang) { + output[lang] = ((time - warmer.minimum) / (warmer.maximum - warmer.minimum) * 100).toFixed(2); + }); + }); + grunt.log.writeln('Scaled throughput: ' + JSON.stringify(scaled, undefined, 2)); + callback && callback(warmer.times); }); }; From 03ef3d42589400fcf4d4ec74d2b4675ae468c2c2 Mon Sep 17 00:00:00 2001 From: Laurie Harper Date: Wed, 28 Aug 2013 12:51:54 -0400 Subject: [PATCH 113/200] Test cases for #599 --- spec/basic.js | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/spec/basic.js b/spec/basic.js index a46636cfd..ed33f4875 100644 --- a/spec/basic.js +++ b/spec/basic.js @@ -83,6 +83,18 @@ describe("basic context", function() { "Frank", "functions are called with context arguments"); }); + it("block functions with context argument", function() { + shouldCompileTo("{{#awesome 1}}inner {{.}}{{/awesome}}", + {awesome: function(context, options) { return options.fn(context); }}, + "inner 1", "block functions are called with context and options"); + }); + + it("block functions without context argument", function() { + shouldCompileTo("{{#awesome}}inner{{/awesome}}", + {awesome: function(options) { return options.fn(this); }}, + "inner", "block functions are called with options"); + }); + it("paths with hyphens", function() { shouldCompileTo("{{foo-bar}}", {"foo-bar": "baz"}, "baz", "Paths can contain hyphens (-)"); From 035449fe02bb9e99fa7ffecabef95a0a3573393d Mon Sep 17 00:00:00 2001 From: kpdecker Date: Thu, 29 Aug 2013 23:25:20 -0500 Subject: [PATCH 114/200] Add note regarding performance impact of @data --- README.markdown | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.markdown b/README.markdown index e19e0d70e..dca8ca64d 100644 --- a/README.markdown +++ b/README.markdown @@ -307,6 +307,8 @@ normal. helpers for size and speed. - When all helpers are known in advance the `--knownOnly` argument may be used to optimize all block helper references. +- Implementations that do not use `@data` variables can improve performance of + iteration centric templates by specifying `{data: false}` in the compiler options. Supported Environments ---------------------- From 06e2a441db19eece6725b6429f4a4e14bafcfda7 Mon Sep 17 00:00:00 2001 From: kpdecker Date: Sat, 31 Aug 2013 12:51:37 -0500 Subject: [PATCH 115/200] Move scaled output to benchwarmer --- bench/throughput.js | 172 ++++++++++++++-------------------- bench/util/benchwarmer.js | 91 ++++++++++++------ bench/util/template-runner.js | 27 ++++++ 3 files changed, 162 insertions(+), 128 deletions(-) create mode 100644 bench/util/template-runner.js diff --git a/bench/throughput.js b/bench/throughput.js index 67c2290b5..277f112ca 100644 --- a/bench/throughput.js +++ b/bench/throughput.js @@ -1,5 +1,5 @@ var _ = require('underscore'), - BenchWarmer = require('./util/benchwarmer'), + runner = require('./util/template-runner'), templates = require('./templates'), eco, dust, Handlebars, Mustache, eco; @@ -20,133 +20,103 @@ function error() { throw new Error("EWOT"); } -function makeSuite(warmer, name, template, handlebarsOnly) { - warmer.suite(name, function(bench) { - // Create aliases to minimize any impact from having to walk up the closure tree. - var templateName = name, +function makeSuite(bench, name, template, handlebarsOnly) { + // Create aliases to minimize any impact from having to walk up the closure tree. + var templateName = name, - context = template.context, - partials = template.partials, + context = template.context, + partials = template.partials, - handlebarsOut, - dustOut, - ecoOut, - mustacheOut; + handlebarsOut, + dustOut, + ecoOut, + mustacheOut; - var handlebar = Handlebars.compile(template.handlebars, {data: false}), - options = {helpers: template.helpers}; - _.each(template.partials && template.partials.handlebars, function(partial, name) { - Handlebars.registerPartial(name, Handlebars.compile(partial, {data: false})); - }); + var handlebar = Handlebars.compile(template.handlebars, {data: false}), + options = {helpers: template.helpers}; + _.each(template.partials && template.partials.handlebars, function(partial, name) { + Handlebars.registerPartial(name, Handlebars.compile(partial, {data: false})); + }); - handlebarsOut = handlebar(context, options); - bench("handlebars", function() { - handlebar(context, options); - }); + handlebarsOut = handlebar(context, options); + bench("handlebars", function() { + handlebar(context, options); + }); - if (handlebarsOnly) { - return; - } + if (handlebarsOnly) { + return; + } - if (dust) { - if (template.dust) { - dustOut = false; - dust.loadSource(dust.compile(template.dust, templateName)); + if (dust) { + if (template.dust) { + dustOut = false; + dust.loadSource(dust.compile(template.dust, templateName)); - dust.render(templateName, context, function(err, out) { dustOut = out; }); + dust.render(templateName, context, function(err, out) { dustOut = out; }); - bench("dust", function() { - dust.render(templateName, context, function(err, out) { }); - }); - } else { - bench('dust', error); - } + bench("dust", function() { + dust.render(templateName, context, function(err, out) { }); + }); + } else { + bench('dust', error); } + } - if (eco) { - if (template.eco) { - var ecoTemplate = eco.compile(template.eco); + if (eco) { + if (template.eco) { + var ecoTemplate = eco.compile(template.eco); - ecoOut = ecoTemplate(context); + ecoOut = ecoTemplate(context); - bench("eco", function() { - ecoTemplate(context); - }); - } else { - bench("eco", error); - } + bench("eco", function() { + ecoTemplate(context); + }); + } else { + bench("eco", error); } + } - if (Mustache) { - var mustacheSource = template.mustache, - mustachePartials = partials && partials.mustache; + if (Mustache) { + var mustacheSource = template.mustache, + mustachePartials = partials && partials.mustache; - if (mustacheSource) { - mustacheOut = Mustache.to_html(mustacheSource, context, mustachePartials); + if (mustacheSource) { + mustacheOut = Mustache.to_html(mustacheSource, context, mustachePartials); - bench("mustache", function() { - Mustache.to_html(mustacheSource, context, mustachePartials); - }); - } else { - bench("mustache", error); - } + bench("mustache", function() { + Mustache.to_html(mustacheSource, context, mustachePartials); + }); + } else { + bench("mustache", error); } + } - // Hack around whitespace until we have whitespace control - handlebarsOut = handlebarsOut.replace(/\s/g, ''); - function compare(b, lang) { - if (b == null) { - return; - } + // Hack around whitespace until we have whitespace control + handlebarsOut = handlebarsOut.replace(/\s/g, ''); + function compare(b, lang) { + if (b == null) { + return; + } - b = b.replace(/\s/g, ''); + b = b.replace(/\s/g, ''); - if (handlebarsOut !== b) { - throw new Error('Template output mismatch: ' + name - + '\n\nHandlebars: ' + handlebarsOut - + '\n\n' + lang + ': ' + b); - } + if (handlebarsOut !== b) { + throw new Error('Template output mismatch: ' + name + + '\n\nHandlebars: ' + handlebarsOut + + '\n\n' + lang + ': ' + b); } + } - compare(dustOut, 'dust'); - compare(ecoOut, 'eco'); - compare(mustacheOut, 'mustache'); - }); + compare(dustOut, 'dust'); + compare(ecoOut, 'eco'); + compare(mustacheOut, 'mustache'); } module.exports = function(grunt, callback) { // Deferring load incase we are being run inline with the grunt build Handlebars = require('../lib/handlebars'); - var warmer = new BenchWarmer(); - - var handlebarsOnly = grunt.option('handlebars-only'), - grep = grunt.option('grep'); - if (grep) { - grep = new RegExp(grep); - } - - _.each(templates, function(template, name) { - if (!template.handlebars || (grep && !grep.test(name))) { - return; - } - - makeSuite(warmer, name, template, handlebarsOnly); - }); - - warmer.bench(function() { - grunt.log.writeln(); // Clear out any trailing contnet - - var scaled = {}; - _.each(warmer.times, function(times, name) { - var output = scaled[name] = {}; - - _.each(times, function(time, lang) { - output[lang] = ((time - warmer.minimum) / (warmer.maximum - warmer.minimum) * 100).toFixed(2); - }); - }); - grunt.log.writeln('Scaled throughput: ' + JSON.stringify(scaled, undefined, 2)); - - callback && callback(warmer.times); + runner(grunt, makeSuite, function(times, scaled) { + callback(times); }); }; diff --git a/bench/util/benchwarmer.js b/bench/util/benchwarmer.js index a0bd42400..9b77f79ad 100644 --- a/bench/util/benchwarmer.js +++ b/bench/util/benchwarmer.js @@ -1,5 +1,5 @@ - -var Benchmark = require("benchmark"); +var _ = require('underscore'), + Benchmark = require("benchmark"); var BenchWarmer = function(names) { this.benchmarks = []; @@ -51,36 +51,29 @@ BenchWarmer.prototype = { this.benchmarks.push(bench); }, - bench: function(callback) { - var benchSize = 0, names = this.names, self = this, i, l; - - for(i=0, l=names.length; i Date: Mon, 2 Sep 2013 16:26:57 -0500 Subject: [PATCH 116/200] Output scaled times for throughput --- bench/throughput.js | 3 ++- bench/util/benchwarmer.js | 1 + 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/bench/throughput.js b/bench/throughput.js index 277f112ca..b750e44f3 100644 --- a/bench/throughput.js +++ b/bench/throughput.js @@ -116,7 +116,8 @@ module.exports = function(grunt, callback) { // Deferring load incase we are being run inline with the grunt build Handlebars = require('../lib/handlebars'); + console.log('Execution Throughput'); runner(grunt, makeSuite, function(times, scaled) { - callback(times); + callback(scaled); }); }; diff --git a/bench/util/benchwarmer.js b/bench/util/benchwarmer.js index 9b77f79ad..7496a3e9e 100644 --- a/bench/util/benchwarmer.js +++ b/bench/util/benchwarmer.js @@ -73,6 +73,7 @@ BenchWarmer.prototype = { self.writeValue(value[lang] || ''); }); }); + print('\n'); var errors = false, prop, bench; for(prop in self.errors) { From a2687fdf501a878df264d9cbefd31c0601a077e7 Mon Sep 17 00:00:00 2001 From: kpdecker Date: Mon, 2 Sep 2013 16:44:17 -0500 Subject: [PATCH 117/200] Fix cjs output and testing --- configurations/transpile.js | 8 ++++++-- spec/env/node.js | 2 +- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/configurations/transpile.js b/configurations/transpile.js index 679fbf288..61a2ea5ba 100644 --- a/configurations/transpile.js +++ b/configurations/transpile.js @@ -11,8 +11,12 @@ module.exports = { cjs: { type: 'cjs', - src: ["lib/<%= pkg.barename %>.js", "lib/*/**/*.js"], - dest: "tmp/<%= pkg.barename %>.cjs.js" + files: [{ + expand: true, + cwd: 'lib/', + src: '**/*.js', + dest: 'dist/cjs/' + }] }, globals: { diff --git a/spec/env/node.js b/spec/env/node.js index 351747973..0faf45d47 100644 --- a/spec/env/node.js +++ b/spec/env/node.js @@ -1,6 +1,6 @@ require('./common'); -global.Handlebars = require('../../zomg/lib/handlebars'); +global.Handlebars = require('../../dist/cjs/handlebars'); global.CompilerContext = { compile: function(template, options, env) { From 1e8409efa6ff5c9ecc44ab3850b219266b562640 Mon Sep 17 00:00:00 2001 From: kpdecker Date: Mon, 2 Sep 2013 18:25:03 -0500 Subject: [PATCH 118/200] Fix merge errors --- lib/handlebars/runtime.js | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/lib/handlebars/runtime.js b/lib/handlebars/runtime.js index eeef1822a..460431210 100644 --- a/lib/handlebars/runtime.js +++ b/lib/handlebars/runtime.js @@ -3,18 +3,18 @@ import { COMPILER_REVISION, REVISION_CHANGES } from "./base"; function checkRevision(compilerInfo) { var compilerRevision = compilerInfo && compilerInfo[0] || 1, - currentRevision = Handlebars.COMPILER_REVISION; + currentRevision = COMPILER_REVISION; if (compilerRevision !== currentRevision) { if (compilerRevision < currentRevision) { - var runtimeVersions = Handlebars.REVISION_CHANGES[currentRevision], - compilerVersions = Handlebars.REVISION_CHANGES[compilerRevision]; - throw "Template was precompiled with an older version of Handlebars than the current runtime. "+ - "Please update your precompiler to a newer version ("+runtimeVersions+") or downgrade your runtime to an older version ("+compilerVersions+")."; + var runtimeVersions = REVISION_CHANGES[currentRevision], + compilerVersions = REVISION_CHANGES[compilerRevision]; + throw new Error("Template was precompiled with an older version of Handlebars than the current runtime. "+ + "Please update your precompiler to a newer version ("+runtimeVersions+") or downgrade your runtime to an older version ("+compilerVersions+")."); } else { // Use the embedded version info since the runtime doesn't know about this revision yet - throw "Template was precompiled with a newer version of Handlebars than the current runtime. "+ - "Please update your runtime to a newer version ("+compilerInfo[1]+")."; + throw new Error("Template was precompiled with a newer version of Handlebars than the current runtime. "+ + "Please update your runtime to a newer version ("+compilerInfo[1]+")."); } } } @@ -76,7 +76,7 @@ export function template(templateSpec, Hbars, compile) { return function(context, options) { options = options || {}; - var namespace = options.partial ? options : Handlebars, + var namespace = options.partial ? options : Hbars, helpers, partials; From 1173e5a5026a1ac2b85e92ffa99ae93675ca61d5 Mon Sep 17 00:00:00 2001 From: kpdecker Date: Sat, 28 Sep 2013 10:54:03 -0500 Subject: [PATCH 119/200] Update grunt build target --- Gruntfile.js | 4 ---- 1 file changed, 4 deletions(-) diff --git a/Gruntfile.js b/Gruntfile.js index de0c9b454..111619570 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -42,10 +42,6 @@ module.exports = function(grunt) { } }); - // By default, (i.e., if you invoke `grunt` without arguments), do - // a new build. - this.registerTask('default', ['build']); - // Build a new version of the library this.registerTask('build', "Builds a distributable version of the current project", [ 'clean', From 27d901097443bd4e1275ff39857d929248ed4ad8 Mon Sep 17 00:00:00 2001 From: kpdecker Date: Mon, 30 Sep 2013 23:52:33 -0500 Subject: [PATCH 120/200] Update logger for es6 modules --- lib/handlebars.js | 5 ++++- lib/handlebars/base.js | 8 ++++---- lib/handlebars/compiler/javascript-compiler.js | 17 ++++------------- 3 files changed, 12 insertions(+), 18 deletions(-) diff --git a/lib/handlebars.js b/lib/handlebars.js index 7ca80a7a1..dede1b868 100644 --- a/lib/handlebars.js +++ b/lib/handlebars.js @@ -1,4 +1,4 @@ -import { HandlebarsEnvironment, createFrame } from "./handlebars/base"; +import { HandlebarsEnvironment, createFrame, logger, log } from "./handlebars/base"; // Each of these augment the Handlebars object. No need to setup here. // (This is done to easily share code between commonjs and browse envs) @@ -29,6 +29,9 @@ var create = function() { hb.template = template; hb.createFrame = createFrame; + hb.log = log; + hb.logger = logger; + return hb; }; diff --git a/lib/handlebars/base.js b/lib/handlebars/base.js index b375554b2..f4cb840af 100644 --- a/lib/handlebars/base.js +++ b/lib/handlebars/base.js @@ -153,15 +153,15 @@ function registerDefaultHelpers(instance) { var levels = { DEBUG: 0, INFO: 1, WARN: 2, ERROR: 3, level: 3 -} +}; var methodMap = { 0: 'debug', 1: 'info', 2: 'warn', 3: 'error' }; export var logger = { // can be overridden in the host environment log: function(level, obj) { - if (Handlebars.logger.level <= level) { - var method = Handlebars.logger.methodMap[level]; + if (logger.level <= level) { + var method = logger.methodMap[level]; if (typeof console !== 'undefined' && console[method]) { console[method].call(console, obj); } @@ -169,7 +169,7 @@ export var logger = { } }; -export function log(level, obj) { logger.log(level, obj); }; +export function log(level, obj) { logger.log(level, obj); } export var createFrame = function(object) { var obj = {}; diff --git a/lib/handlebars/compiler/javascript-compiler.js b/lib/handlebars/compiler/javascript-compiler.js index d3bd25885..aecef8594 100644 --- a/lib/handlebars/compiler/javascript-compiler.js +++ b/lib/handlebars/compiler/javascript-compiler.js @@ -1,8 +1,8 @@ -import { COMPILER_REVISION, REVISION_CHANGES } from "../base"; +import { COMPILER_REVISION, REVISION_CHANGES, log } from "../base"; function Literal(value) { this.value = value; -}; +} export function JavaScriptCompiler() {}; @@ -43,9 +43,7 @@ JavaScriptCompiler.prototype = { this.environment = environment; this.options = options || {}; - // TODO: a module-compatible logger - // Handlebars.log(Handlebars.logger.DEBUG, this.environment.disassemble() + "\n\n"); - log(this.environment.disassemble() + "\n\n"); + log('debug', this.environment.disassemble() + "\n\n"); this.name = this.environment.name; this.isChild = !!context; @@ -167,11 +165,7 @@ JavaScriptCompiler.prototype = { return Function.apply(this, params); } else { var functionSource = 'function ' + (this.name || '') + '(' + params.join(',') + ') {\n ' + source + '}'; - - // TODO: a module-compatible logger - //Handlebars.log(Handlebars.logger.DEBUG, functionSource + "\n\n"); - log(functionSource + "\n\n"); - + log('debug', functionSource + "\n\n"); return functionSource; } }, @@ -847,6 +841,3 @@ JavaScriptCompiler.isValidJavaScriptVariableName = function(name) { return false; }; -function log(string) { - //console.log(string); -} From fbf8f134fa461cd0713a22e75fce65d78b0e6b40 Mon Sep 17 00:00:00 2001 From: kpdecker Date: Tue, 1 Oct 2013 00:12:39 -0500 Subject: [PATCH 121/200] Generate parser as es6 module --- src/parser-prefix.js | 1 - src/parser-suffix.js | 5 +---- tasks/parser.js | 2 +- 3 files changed, 2 insertions(+), 6 deletions(-) delete mode 100644 src/parser-prefix.js diff --git a/src/parser-prefix.js b/src/parser-prefix.js deleted file mode 100644 index 42345d6f6..000000000 --- a/src/parser-prefix.js +++ /dev/null @@ -1 +0,0 @@ -// BEGIN(BROWSER) diff --git a/src/parser-suffix.js b/src/parser-suffix.js index fc8df1399..6e4aa20d6 100644 --- a/src/parser-suffix.js +++ b/src/parser-suffix.js @@ -1,4 +1 @@ - -// END(BROWSER) - -module.exports = handlebars; +export default handlebars; diff --git a/tasks/parser.js b/tasks/parser.js index f6a72c617..b1c1c0f23 100644 --- a/tasks/parser.js +++ b/tasks/parser.js @@ -12,7 +12,7 @@ module.exports = function(grunt) { return; } - var src = ['src/parser-prefix.js', 'handlebars.js', 'src/parser-suffix.js'].map(grunt.file.read).join(''); + var src = ['handlebars.js', 'src/parser-suffix.js'].map(grunt.file.read).join(''); grunt.file.delete('handlebars.js'); grunt.file.write('lib/handlebars/compiler/parser.js', src); From 80b748ad3e34b29249af134e214d57fec92e22a4 Mon Sep 17 00:00:00 2001 From: kpdecker Date: Tue, 1 Oct 2013 00:13:48 -0500 Subject: [PATCH 122/200] Fix global Handlebars references --- lib/handlebars/compiler/printer.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/handlebars/compiler/printer.js b/lib/handlebars/compiler/printer.js index 0280960c9..01b05d1c7 100644 --- a/lib/handlebars/compiler/printer.js +++ b/lib/handlebars/compiler/printer.js @@ -2,10 +2,10 @@ import Visitor from "./visitor"; export function print(ast) { return new PrintVisitor().accept(ast); -}; +} -export function PrintVisitor() { this.padding = 0; }; -PrintVisitor.prototype = new Handlebars.Visitor(); +export function PrintVisitor() { this.padding = 0; } +PrintVisitor.prototype = new Visitor(); PrintVisitor.prototype.pad = function(string, newline) { var out = ""; @@ -60,7 +60,7 @@ PrintVisitor.prototype.block = function(block) { return out; }; -Handlebars.PrintVisitor.prototype.mustache = function(mustache) { +PrintVisitor.prototype.mustache = function(mustache) { var params = mustache.params, paramStrings = [], hash; for(var i=0, l=params.length; i Date: Tue, 1 Oct 2013 20:52:14 -0500 Subject: [PATCH 123/200] Break exception class out into a standalone module --- lib/handlebars.js | 3 ++- lib/handlebars/base.js | 9 +++++---- lib/handlebars/compiler/ast.js | 2 +- lib/handlebars/compiler/compiler.js | 2 +- lib/handlebars/exception.js | 15 +++++++++++++++ lib/handlebars/runtime.js | 3 ++- lib/handlebars/utils.js | 13 ------------- 7 files changed, 26 insertions(+), 21 deletions(-) create mode 100644 lib/handlebars/exception.js diff --git a/lib/handlebars.js b/lib/handlebars.js index dede1b868..72b4028e9 100644 --- a/lib/handlebars.js +++ b/lib/handlebars.js @@ -2,7 +2,8 @@ import { HandlebarsEnvironment, createFrame, logger, log } from "./handlebars/ba // Each of these augment the Handlebars object. No need to setup here. // (This is done to easily share code between commonjs and browse envs) -import { SafeString, Exception, extend, escapeExpression, isEmpty } from "./handlebars/utils"; +import Exception from "./handlebars/exception"; +import { SafeString, extend, escapeExpression, isEmpty } from "./handlebars/utils"; import { compile, precompile } from "./handlebars/compiler/compiler"; import { template } from "./handlebars/runtime"; diff --git a/lib/handlebars/base.js b/lib/handlebars/base.js index f4cb840af..c83144af5 100644 --- a/lib/handlebars/base.js +++ b/lib/handlebars/base.js @@ -1,6 +1,7 @@ /*jshint eqnull: true */ -import { Exception, extend, isEmpty } from "./utils"; +import { extend, isEmpty } from "./utils"; +import Exception from "./exception"; var K = function() { return this; }; @@ -19,9 +20,9 @@ var toString = Object.prototype.toString, // Sourced from lodash // https://github.com/bestiejs/lodash/blob/master/LICENSE.txt -function isFunction(value) { +var isFunction = function(value) { return typeof value === 'function'; -} +}; // fallback for older versions of Chrome and Safari if (isFunction(/x/)) { isFunction = function(value) { @@ -31,7 +32,7 @@ if (isFunction(/x/)) { function isArray(value) { return (value && typeof value === 'object') ? toString.call(value) === '[object Array]' : false; -}; +} export function HandlebarsEnvironment(helpers, partials) { this.helpers = helpers || {}; diff --git a/lib/handlebars/compiler/ast.js b/lib/handlebars/compiler/ast.js index b61fa0e9f..c6c8993a9 100644 --- a/lib/handlebars/compiler/ast.js +++ b/lib/handlebars/compiler/ast.js @@ -1,4 +1,4 @@ -import { Exception } from "../utils"; +import Exception from "../exception"; var exports = {}; diff --git a/lib/handlebars/compiler/compiler.js b/lib/handlebars/compiler/compiler.js index 6656a3fe9..c020e1f2b 100644 --- a/lib/handlebars/compiler/compiler.js +++ b/lib/handlebars/compiler/compiler.js @@ -1,4 +1,4 @@ -import { Exception } from "../utils"; +import Exception from "../exception"; import { template } from "../runtime"; import { parse } from "./base"; import { JavaScriptCompiler } from "./javascript-compiler"; diff --git a/lib/handlebars/exception.js b/lib/handlebars/exception.js new file mode 100644 index 000000000..fc88534cd --- /dev/null +++ b/lib/handlebars/exception.js @@ -0,0 +1,15 @@ + +var errorProps = ['description', 'fileName', 'lineNumber', 'message', 'name', 'number', 'stack']; + +function Exception(message) { + var tmp = Error.prototype.constructor.apply(this, arguments); + + // Unfortunately errors are not enumerable in Chrome (at least), so `for prop in tmp` doesn't work. + for (var idx = 0; idx < errorProps.length; idx++) { + this[errorProps[idx]] = tmp[errorProps[idx]]; + } +} + +Exception.prototype = new Error(); + +export default Exception; diff --git a/lib/handlebars/runtime.js b/lib/handlebars/runtime.js index 460431210..55a5c6fcc 100644 --- a/lib/handlebars/runtime.js +++ b/lib/handlebars/runtime.js @@ -1,4 +1,5 @@ -import { escapeExpression, extend, Exception } from "./utils"; +import Exception from "./exception"; +import { escapeExpression, extend } from "./utils"; import { COMPILER_REVISION, REVISION_CHANGES } from "./base"; function checkRevision(compilerInfo) { diff --git a/lib/handlebars/utils.js b/lib/handlebars/utils.js index 458c46db1..e3c2160e2 100644 --- a/lib/handlebars/utils.js +++ b/lib/handlebars/utils.js @@ -1,19 +1,6 @@ var toString = Object.prototype.toString, isArray = Array.isArray; -var errorProps = ['description', 'fileName', 'lineNumber', 'message', 'name', 'number', 'stack']; - -export function Exception(message) { - var tmp = Error.prototype.constructor.apply(this, arguments); - - // Unfortunately errors are not enumerable in Chrome (at least), so `for prop in tmp` doesn't work. - for (var idx = 0; idx < errorProps.length; idx++) { - this[errorProps[idx]] = tmp[errorProps[idx]]; - } -}; - -Exception.prototype = new Error(); - // Build out our basic SafeString type export function SafeString(string) { this.string = string; From e75839b18596176f7e433cb94365eda32d7c453a Mon Sep 17 00:00:00 2001 From: kpdecker Date: Tue, 1 Oct 2013 21:18:10 -0500 Subject: [PATCH 124/200] Break safe string out into standalone module --- lib/handlebars.js | 3 ++- lib/handlebars/safe-string.js | 10 ++++++++++ lib/handlebars/utils.js | 15 ++++----------- 3 files changed, 16 insertions(+), 12 deletions(-) create mode 100644 lib/handlebars/safe-string.js diff --git a/lib/handlebars.js b/lib/handlebars.js index 72b4028e9..60ed13c63 100644 --- a/lib/handlebars.js +++ b/lib/handlebars.js @@ -2,8 +2,9 @@ import { HandlebarsEnvironment, createFrame, logger, log } from "./handlebars/ba // Each of these augment the Handlebars object. No need to setup here. // (This is done to easily share code between commonjs and browse envs) +import SafeString from "./handlebars/safe-string"; import Exception from "./handlebars/exception"; -import { SafeString, extend, escapeExpression, isEmpty } from "./handlebars/utils"; +import { extend, escapeExpression, isEmpty } from "./handlebars/utils"; import { compile, precompile } from "./handlebars/compiler/compiler"; import { template } from "./handlebars/runtime"; diff --git a/lib/handlebars/safe-string.js b/lib/handlebars/safe-string.js new file mode 100644 index 000000000..2ae49aa89 --- /dev/null +++ b/lib/handlebars/safe-string.js @@ -0,0 +1,10 @@ +// Build out our basic SafeString type +function SafeString(string) { + this.string = string; +} + +SafeString.prototype.toString = function() { + return "" + this.string; +}; + +export default SafeString; diff --git a/lib/handlebars/utils.js b/lib/handlebars/utils.js index e3c2160e2..8bb28f352 100644 --- a/lib/handlebars/utils.js +++ b/lib/handlebars/utils.js @@ -1,15 +1,8 @@ +import SafeString from "./safe-string"; + var toString = Object.prototype.toString, isArray = Array.isArray; -// Build out our basic SafeString type -export function SafeString(string) { - this.string = string; -}; - -SafeString.prototype.toString = function() { - return "" + this.string; -}; - var escape = { "&": "&", "<": "<", @@ -22,9 +15,9 @@ var escape = { var badChars = /[&<>"'`]/g; var possible = /[&<>"'`]/; -var escapeChar = function(chr) { +function escapeChar(chr) { return escape[chr] || "&"; -}; +} export function extend(obj, value) { for(var key in value) { From e676e43dc5e22dfb3847ff28321b29863975cc21 Mon Sep 17 00:00:00 2001 From: kpdecker Date: Tue, 1 Oct 2013 21:19:49 -0500 Subject: [PATCH 125/200] Use proper default vs. module import semantics --- lib/handlebars/compiler/ast.js | 56 +++++++++---------- lib/handlebars/compiler/base.js | 2 +- lib/handlebars/compiler/compiler.js | 12 ++-- .../compiler/javascript-compiler.js | 3 +- lib/handlebars/compiler/visitor.js | 4 +- 5 files changed, 37 insertions(+), 40 deletions(-) diff --git a/lib/handlebars/compiler/ast.js b/lib/handlebars/compiler/ast.js index c6c8993a9..e4e804914 100644 --- a/lib/handlebars/compiler/ast.js +++ b/lib/handlebars/compiler/ast.js @@ -1,14 +1,12 @@ import Exception from "../exception"; -var exports = {}; - -exports.ProgramNode = function ProgramNode(statements, inverse) { +export function ProgramNode(statements, inverse) { this.type = "program"; this.statements = statements; if(inverse) { this.inverse = new ProgramNode(inverse); } -}; +} -exports.MustacheNode = function(rawParams, hash, unescaped) { +export function MustacheNode(rawParams, hash, unescaped) { this.type = "mustache"; this.escaped = !unescaped; this.hash = hash; @@ -28,15 +26,15 @@ exports.MustacheNode = function(rawParams, hash, unescaped) { // if a mustache is an eligible helper but not a definite // helper, it is ambiguous, and will be resolved in a later // pass or at runtime. -}; +} -exports.PartialNode = function(partialName, context) { +export function PartialNode(partialName, context) { this.type = "partial"; this.partialName = partialName; this.context = context; -}; +} -exports.BlockNode = function(mustache, program, inverse, close) { +export function BlockNode(mustache, program, inverse, close) { if(mustache.id.original !== close.original) { throw new Exception(mustache.id.original + " doesn't match " + close.original); } @@ -49,19 +47,19 @@ exports.BlockNode = function(mustache, program, inverse, close) { if (this.inverse && !this.program) { this.isInverse = true; } -}; +} -exports.ContentNode = function(string) { +export function ContentNode(string) { this.type = "content"; this.string = string; -}; +} -exports.HashNode = function(pairs) { +export function HashNode(pairs) { this.type = "hash"; this.pairs = pairs; -}; +} -exports.IdNode = function(parts) { +export function IdNode(parts) { this.type = "ID"; var original = "", @@ -90,41 +88,39 @@ exports.IdNode = function(parts) { this.isSimple = parts.length === 1 && !this.isScoped && depth === 0; this.stringModeValue = this.string; -}; +} -exports.PartialNameNode = function(name) { +export function PartialNameNode(name) { this.type = "PARTIAL_NAME"; this.name = name.original; -}; +} -exports.DataNode = function(id) { +export function DataNode(id) { this.type = "DATA"; this.id = id; -}; +} -exports.StringNode = function(string) { +export function StringNode(string) { this.type = "STRING"; this.original = this.string = this.stringModeValue = string; -}; +} -exports.IntegerNode = function(integer) { +export function IntegerNode(integer) { this.type = "INTEGER"; this.original = this.integer = integer; this.stringModeValue = Number(integer); -}; +} -exports.BooleanNode = function(bool) { +export function BooleanNode(bool) { this.type = "BOOLEAN"; this.bool = bool; this.stringModeValue = bool === "true"; -}; +} -exports.CommentNode = function(comment) { +export function CommentNode(comment) { this.type = "comment"; this.comment = comment; -}; - -export default exports; +} diff --git a/lib/handlebars/compiler/base.js b/lib/handlebars/compiler/base.js index adbf4f292..cf4f17fbe 100644 --- a/lib/handlebars/compiler/base.js +++ b/lib/handlebars/compiler/base.js @@ -1,5 +1,5 @@ import parser from "./parser"; -import AST from "./ast"; +module AST from "./ast"; export var parser = parser; diff --git a/lib/handlebars/compiler/compiler.js b/lib/handlebars/compiler/compiler.js index c020e1f2b..0691a2316 100644 --- a/lib/handlebars/compiler/compiler.js +++ b/lib/handlebars/compiler/compiler.js @@ -1,12 +1,10 @@ import Exception from "../exception"; import { template } from "../runtime"; import { parse } from "./base"; -import { JavaScriptCompiler } from "./javascript-compiler"; -import AST from "./ast"; +import JavaScriptCompiler from "./javascript-compiler"; +module AST from "./ast"; -/*jshint eqnull:true*/ - -export function Compiler() {}; +export function Compiler() {} // the foundHelper register will disambiguate helper lookup from finding a // function in a context. This is necessary for mustache compatibility, which @@ -428,7 +426,7 @@ export function precompile(input, options) { var ast = parse(input); var environment = new Compiler().compile(ast, options); return new JavaScriptCompiler().compile(environment, options); -}; +} export function compile(input, options) { if (input == null || (typeof input !== 'string' && input.constructor !== AST.ProgramNode)) { @@ -457,4 +455,4 @@ export function compile(input, options) { } return compiled.call(this, context, options); }; -}; +} diff --git a/lib/handlebars/compiler/javascript-compiler.js b/lib/handlebars/compiler/javascript-compiler.js index aecef8594..a81a99c2a 100644 --- a/lib/handlebars/compiler/javascript-compiler.js +++ b/lib/handlebars/compiler/javascript-compiler.js @@ -4,7 +4,7 @@ function Literal(value) { this.value = value; } -export function JavaScriptCompiler() {}; +function JavaScriptCompiler() {} JavaScriptCompiler.prototype = { // PUBLIC API: You can override these methods in a subclass to provide @@ -841,3 +841,4 @@ JavaScriptCompiler.isValidJavaScriptVariableName = function(name) { return false; }; +export default JavaScriptCompiler; diff --git a/lib/handlebars/compiler/visitor.js b/lib/handlebars/compiler/visitor.js index 29ea82c58..6a0373e19 100644 --- a/lib/handlebars/compiler/visitor.js +++ b/lib/handlebars/compiler/visitor.js @@ -1,4 +1,4 @@ -export function Visitor() {}; +function Visitor() {} Visitor.prototype = { constructor: Visitor, @@ -7,3 +7,5 @@ Visitor.prototype = { return this[object.type](object); } }; + +export default Visitor; From fd09ff0e1bf4db60d581fcc0653cc6344df60700 Mon Sep 17 00:00:00 2001 From: kpdecker Date: Tue, 1 Oct 2013 21:41:09 -0500 Subject: [PATCH 126/200] Expose current Handlebars namespace fields --- configurations/transpile.js | 4 +-- lib/handlebars.js | 50 ++++++++++++++------------------- lib/handlebars/compiler/base.js | 2 +- lib/index.js | 19 +++++++++++++ spec/env/node.js | 2 +- 5 files changed, 44 insertions(+), 33 deletions(-) create mode 100644 lib/index.js diff --git a/configurations/transpile.js b/configurations/transpile.js index 61a2ea5ba..34824e143 100644 --- a/configurations/transpile.js +++ b/configurations/transpile.js @@ -4,7 +4,7 @@ module.exports = { files: [{ expand: true, cwd: 'lib/', - src: '**/*.js', + src: '**/!(index).js', dest: 'tmp' }] }, @@ -14,7 +14,7 @@ module.exports = { files: [{ expand: true, cwd: 'lib/', - src: '**/*.js', + src: '**/!(index).js', dest: 'dist/cjs/' }] }, diff --git a/lib/handlebars.js b/lib/handlebars.js index 60ed13c63..cf538a8be 100644 --- a/lib/handlebars.js +++ b/lib/handlebars.js @@ -1,38 +1,47 @@ -import { HandlebarsEnvironment, createFrame, logger, log } from "./handlebars/base"; +module base from "./handlebars/base"; // Each of these augment the Handlebars object. No need to setup here. // (This is done to easily share code between commonjs and browse envs) import SafeString from "./handlebars/safe-string"; import Exception from "./handlebars/exception"; -import { extend, escapeExpression, isEmpty } from "./handlebars/utils"; -import { compile, precompile } from "./handlebars/compiler/compiler"; -import { template } from "./handlebars/runtime"; +module Utils from "./handlebars/utils"; +module runtime from "./handlebars/runtime"; + +// Compiler imports +module AST from "./handlebars/compiler/ast"; +import { parser as Parser, parse } from "./handlebars/compiler/base"; +import { Compiler, compile, precompile } from "./handlebars/compiler/compiler"; +import JavaScriptCompiler from "./handlebars/compiler/javascript-compiler"; // For compatibility and usage outside of module systems, make the Handlebars object a namespace var create = function() { var hb = {}, - env = new HandlebarsEnvironment(); + env = new base.HandlebarsEnvironment(); // support new environments in global namespace mode - hb.HandlebarsEnvironment = HandlebarsEnvironment; - hb.registerHelper = env.registerHelper.bind(env); hb.registerPartial = env.registerPartial.bind(env); + Utils.extend(hb, base); hb.SafeString = SafeString; hb.Exception = Exception; - hb.Utils = { extend: extend, escapeExpression: escapeExpression, isEmpty: isEmpty }; + hb.Utils = Utils; + + hb.VM = runtime; + hb.template = runtime.template; + hb.compile = function(input, options) { options = options || {}; options.env = options.env || env; return compile(input, options); }; hb.precompile = precompile; - hb.template = template; - hb.createFrame = createFrame; - hb.log = log; - hb.logger = logger; + hb.AST = AST; + hb.Compiler = Compiler; + hb.JavaScriptCompiler = JavaScriptCompiler; + hb.Parser = Parser; + hb.parse = parse; return hb; }; @@ -41,20 +50,3 @@ var Handlebars = create(); Handlebars.create = create; export default Handlebars; - -// Publish a Node.js require() handler for .handlebars and .hbs files -if (typeof require !== 'undefined' && require.extensions) { - var extension = function(module, filename) { - var fs = require("fs"); - var templateString = fs.readFileSync(filename, "utf8"); - module.exports = Handlebars.compile(templateString); - }; - require.extensions[".handlebars"] = extension; - require.extensions[".hbs"] = extension; -} - -// USAGE: -// var handlebars = require('handlebars'); - -// var singleton = handlebars.Handlebars, -// local = handlebars.create(); diff --git a/lib/handlebars/compiler/base.js b/lib/handlebars/compiler/base.js index cf4f17fbe..d6cb06ee2 100644 --- a/lib/handlebars/compiler/base.js +++ b/lib/handlebars/compiler/base.js @@ -1,7 +1,7 @@ import parser from "./parser"; module AST from "./ast"; -export var parser = parser; +export { parser }; export function parse(input) { // Just return if an already-compile AST was passed in. diff --git a/lib/index.js b/lib/index.js new file mode 100644 index 000000000..3a071b8a0 --- /dev/null +++ b/lib/index.js @@ -0,0 +1,19 @@ +// USAGE: +// var handlebars = require('handlebars'); + +// var local = handlebars.create(); + +var Handlebars = require('../dist/cjs/handlebars').default; + +module.exports = Handlebars; + +// Publish a Node.js require() handler for .handlebars and .hbs files +if (typeof require !== 'undefined' && require.extensions) { + var extension = function(module, filename) { + var fs = require("fs"); + var templateString = fs.readFileSync(filename, "utf8"); + module.exports = Handlebars.compile(templateString); + }; + require.extensions[".handlebars"] = extension; + require.extensions[".hbs"] = extension; +} diff --git a/spec/env/node.js b/spec/env/node.js index 0faf45d47..2a23925ea 100644 --- a/spec/env/node.js +++ b/spec/env/node.js @@ -1,6 +1,6 @@ require('./common'); -global.Handlebars = require('../../dist/cjs/handlebars'); +global.Handlebars = require('../../lib'); global.CompilerContext = { compile: function(template, options, env) { From db0f03d1b5143e375b066f3ea6326db8654e229f Mon Sep 17 00:00:00 2001 From: kpdecker Date: Tue, 1 Oct 2013 21:41:18 -0500 Subject: [PATCH 127/200] Remove empty file --- lib/handlebars/template.js | 3 --- 1 file changed, 3 deletions(-) delete mode 100644 lib/handlebars/template.js diff --git a/lib/handlebars/template.js b/lib/handlebars/template.js deleted file mode 100644 index befba7eed..000000000 --- a/lib/handlebars/template.js +++ /dev/null @@ -1,3 +0,0 @@ -import { escapeExpression, extend } from "./utils"; -import { COMPILER_REVISION, REVISION_CHANGES } from "./base"; - From 117357b74efb60135a7b054798965229ddc73c37 Mon Sep 17 00:00:00 2001 From: kpdecker Date: Tue, 1 Oct 2013 21:41:54 -0500 Subject: [PATCH 128/200] Output amd to dist --- configurations/transpile.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/configurations/transpile.js b/configurations/transpile.js index 34824e143..bac3babfc 100644 --- a/configurations/transpile.js +++ b/configurations/transpile.js @@ -5,7 +5,7 @@ module.exports = { expand: true, cwd: 'lib/', src: '**/!(index).js', - dest: 'tmp' + dest: 'dist/amd/' }] }, From a3b22ac6bfe7f56fdaec1d73c3242c09ba5f582a Mon Sep 17 00:00:00 2001 From: kpdecker Date: Tue, 1 Oct 2013 21:42:04 -0500 Subject: [PATCH 129/200] Update jshint for modules --- .jshintrc | 2 ++ Gruntfile.js | 3 ++- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/.jshintrc b/.jshintrc index 3a42ab1f7..851cf78fa 100644 --- a/.jshintrc +++ b/.jshintrc @@ -30,12 +30,14 @@ "node" : true, "browser" : true, + "esnext": true, "boss" : true, "curly": false, "debug": false, "devel": false, "eqeqeq": false, + "eqnull": true, "evil": true, "forin": false, "immed": false, diff --git a/Gruntfile.js b/Gruntfile.js index 111619570..5d8f88adf 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -15,7 +15,7 @@ module.exports = function(grunt) { force: true }, files: [ - 'lib/**/!(parser|browser-prefix|browser-suffix).js' + 'lib/**/!(parser).js' ] }, @@ -44,6 +44,7 @@ module.exports = function(grunt) { // Build a new version of the library this.registerTask('build', "Builds a distributable version of the current project", [ + 'jshint', 'clean', 'transpile:amd', 'concat:library']); From 386c20cc63054427656d7d5231702641801b6345 Mon Sep 17 00:00:00 2001 From: kpdecker Date: Tue, 1 Oct 2013 21:53:56 -0500 Subject: [PATCH 130/200] Unify build target behavior with prior behavior --- Gruntfile.js | 21 +++++++++------------ configurations/concat.js | 33 ++++++++++++++++----------------- 2 files changed, 25 insertions(+), 29 deletions(-) diff --git a/Gruntfile.js b/Gruntfile.js index 5d8f88adf..ee00067c1 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -32,12 +32,12 @@ module.exports = function(grunt) { preserveComments: 'some' }, dist: { - src: 'dist/<%= pkg.name %>.js', - dest: 'dist/<%= pkg.name %>.min.js' + src: 'dist/handlebars.js', + dest: 'dist/handlebars.min.js' }, runtime: { - src: 'dist/<%= pkg.name %>.runtime.js', - dest: 'dist/<%= pkg.name %>.runtime.min.js' + src: 'dist/handlebars.runtime.js', + dest: 'dist/handlebars.runtime.min.js' } } }); @@ -46,13 +46,11 @@ module.exports = function(grunt) { this.registerTask('build', "Builds a distributable version of the current project", [ 'jshint', 'clean', + 'parser', 'transpile:amd', - 'concat:library']); - - this.registerTask('tests', "Builds the test package", [ - 'build', - 'concat:deps', - 'transpile:tests']); + 'transpile:cjs', + 'concat', + 'uglify']); // Run a server. This is ideal for running the QUnit tests in the browser. this.registerTask('server', [ @@ -85,6 +83,5 @@ module.exports = function(grunt) { }); grunt.registerTask('bench', ['metrics']); - grunt.registerTask('build', ['jshint', 'parser', 'clean', 'concat', 'uglify', 'test']); - grunt.registerTask('default', 'build'); + grunt.registerTask('default', ['build', 'test']); }; diff --git a/configurations/concat.js b/configurations/concat.js index cced2297e..459a7dd27 100644 --- a/configurations/concat.js +++ b/configurations/concat.js @@ -4,31 +4,30 @@ module.exports = { process: function(src, name) { var match = /\/\/ BEGIN\(BROWSER\)\n((?:.|\n)*)\n\/\/ END\(BROWSER\)/.exec(src); return '\n// ' + name + '\n' + (match ? match[1] : src); - }, - separator: ';' + } }, dist: { src: [ - 'lib/handlebars/browser-prefix.js', - 'lib/handlebars/base.js', - 'lib/handlebars/compiler/parser.js', - 'lib/handlebars/compiler/base.js', - 'lib/handlebars/compiler/ast.js', - 'lib/handlebars/utils.js', - 'lib/handlebars/compiler/compiler.js', - 'lib/handlebars/compiler/javascript-compiler.js', - 'lib/handlebars/runtime.js', - 'lib/handlebars/browser-suffix.js' + 'dist/amd/handlebars/browser-prefix.js', + 'dist/amd/handlebars/base.js', + 'dist/amd/handlebars/compiler/parser.js', + 'dist/amd/handlebars/compiler/base.js', + 'dist/amd/handlebars/compiler/ast.js', + 'dist/amd/handlebars/utils.js', + 'dist/amd/handlebars/compiler/compiler.js', + 'dist/amd/handlebars/compiler/javascript-compiler.js', + 'dist/amd/handlebars/runtime.js', + 'dist/amd/handlebars/browser-suffix.js' ], dest: 'dist/handlebars.js' }, runtime: { src: [ - 'lib/handlebars/browser-prefix.js', - 'lib/handlebars/base.js', - 'lib/handlebars/utils.js', - 'lib/handlebars/runtime.js', - 'lib/handlebars/browser-suffix.js' + 'dist/amd/handlebars/browser-prefix.js', + 'dist/amd/handlebars/base.js', + 'dist/amd/handlebars/utils.js', + 'dist/amd/handlebars/runtime.js', + 'dist/amd/handlebars/browser-suffix.js' ], dest: 'dist/handlebars.runtime.js' } From 5a306ac745319a4fd35f2f4b5dd653c11a10cbdd Mon Sep 17 00:00:00 2001 From: Justin Walsh Date: Thu, 3 Oct 2013 18:04:11 -0500 Subject: [PATCH 131/200] Added the CoSchedule project to the readme --- README.markdown | 1 + 1 file changed, 1 insertion(+) diff --git a/README.markdown b/README.markdown index dca8ca64d..6ea94f8fb 100644 --- a/README.markdown +++ b/README.markdown @@ -361,6 +361,7 @@ Handlebars in the Wild * [Assemble](http://assemble.io), by [@jonschlinkert](https://github.com/jonschlinkert) and [@doowb](https://github.com/doowb), is a static site generator that uses Handlebars.js as its template engine. +* [CoSchedule](http://coschedule.com) An editorial calendar for WordPress that uses Handlebars.js * [Ember.js](http://www.emberjs.com) makes Handlebars.js the primary way to structure your views, also with automatic data binding support. * [handlebars_assets](http://github.com/leshill/handlebars_assets): A Rails Asset Pipeline gem From 5fd0d4a790fc90d9febf6c37cafa34ddfde6e88c Mon Sep 17 00:00:00 2001 From: kpdecker Date: Fri, 4 Oct 2013 10:33:40 -0500 Subject: [PATCH 132/200] Only init amazon SDK when we are publishing --- tasks/publish.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tasks/publish.js b/tasks/publish.js index 72a4643de..564ce088a 100644 --- a/tasks/publish.js +++ b/tasks/publish.js @@ -6,7 +6,6 @@ var _ = require('underscore'), module.exports = function(grunt) { grunt.registerTask('publish:latest', function() { var done = this.async(); - initSDK(); git.debug(function(remotes, branches) { grunt.log.writeln('remotes: ' + remotes); @@ -14,6 +13,7 @@ module.exports = function(grunt) { git.commitInfo(function(err, info) { if (info.isMaster) { + initSDK(); publish(fileMap(['-latest', '-' + info.head]), done); } else { // Silently ignore for branches From 19f1a1f81d5e9cd6e95b72d322ce4f324e13eeec Mon Sep 17 00:00:00 2001 From: Alex Navasardyan Date: Fri, 4 Oct 2013 14:28:05 -0400 Subject: [PATCH 133/200] fixing jshint errors --- lib/handlebars/runtime.js | 21 +++++++++++---------- lib/index.js | 6 +++--- 2 files changed, 14 insertions(+), 13 deletions(-) diff --git a/lib/handlebars/runtime.js b/lib/handlebars/runtime.js index 55a5c6fcc..f984fe672 100644 --- a/lib/handlebars/runtime.js +++ b/lib/handlebars/runtime.js @@ -23,8 +23,9 @@ function checkRevision(compilerInfo) { // TODO: Remove this line and break up compilePartial export function template(templateSpec, Hbars, compile) { + var invokePartialWrapper; if (compile) { - var invokePartialWrapper = function(partial, name, context, helpers, partials, data) { + invokePartialWrapper = function(partial, name, context, helpers, partials, data) { // TODO : Check this for all inputs and the options handling (partial flag, etc). This feels // like there should be a common exec path var result = invokePartial.apply(this, arguments); @@ -35,7 +36,7 @@ export function template(templateSpec, Hbars, compile) { return partials[name](context, options); }; } else { - var invokePartialWrapper = function(partial, name, context, helpers, partials, data) { + invokePartialWrapper = function(partial, name, context, helpers, partials, data) { var result = invokePartial.apply(this, arguments); if (result) { return result; } throw new Exception("The partial " + name + " could not be compiled when running in runtime-only mode"); @@ -103,25 +104,25 @@ export function template(templateSpec, Hbars, compile) { export function programWithDepth(i, fn, data /*, $depth */) { var args = Array.prototype.slice.call(arguments, 3); - var program = function(context, options) { + var prog = function(context, options) { options = options || {}; return fn.apply(this, [context, options.data || data].concat(args)); }; - program.program = i; - program.depth = args.length; - return program; + prog.program = i; + prog.depth = args.length; + return prog; } export function program(i, fn, data) { - var program = function(context, options) { + var prog = function(context, options) { options = options || {}; return fn(context, options.data || data); }; - program.program = i; - program.depth = 0; - return program; + prog.program = i; + prog.depth = 0; + return prog; } export function invokePartial(partial, name, context, helpers, partials, data) { diff --git a/lib/index.js b/lib/index.js index 3a071b8a0..639435c9e 100644 --- a/lib/index.js +++ b/lib/index.js @@ -3,16 +3,16 @@ // var local = handlebars.create(); -var Handlebars = require('../dist/cjs/handlebars').default; +var handlebars = require('../dist/cjs/handlebars').default; -module.exports = Handlebars; +module.exports = handlebars; // Publish a Node.js require() handler for .handlebars and .hbs files if (typeof require !== 'undefined' && require.extensions) { var extension = function(module, filename) { var fs = require("fs"); var templateString = fs.readFileSync(filename, "utf8"); - module.exports = Handlebars.compile(templateString); + module.exports = handlebars.compile(templateString); }; require.extensions[".handlebars"] = extension; require.extensions[".hbs"] = extension; From 63bb459ac6a3b25d49265bc0ca1254540c06ddeb Mon Sep 17 00:00:00 2001 From: kpdecker Date: Wed, 9 Oct 2013 02:20:52 -0700 Subject: [PATCH 134/200] Generate dist file using es6-module-packager --- Gruntfile.js | 17 ++++++++++++++++- package.json | 4 ++-- tasks/packager.js | 12 ++++++++++++ 3 files changed, 30 insertions(+), 3 deletions(-) create mode 100644 tasks/packager.js diff --git a/Gruntfile.js b/Gruntfile.js index ee00067c1..db27188fa 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -25,6 +25,21 @@ module.exports = function(grunt) { connect: config('connect'), transpile: config('transpile'), + packager: { + options: { + export: 'Handlebars' + }, + + global: { + files: [{ + cwd: 'lib/', + expand: true, + src: ['handlebars*.js'], + dest: 'dist/' + }] + } + }, + uglify: { options: { mangle: true, @@ -49,7 +64,7 @@ module.exports = function(grunt) { 'parser', 'transpile:amd', 'transpile:cjs', - 'concat', + 'packager', 'uglify']); // Run a server. This is ideal for running the QUnit tests in the browser. diff --git a/package.json b/package.json index c1a43d43c..44d5432ac 100644 --- a/package.json +++ b/package.json @@ -25,8 +25,8 @@ "grunt-contrib-uglify": "~0.2.2", "grunt-contrib-watch": "~0.4.4", "grunt-hang": "~0.1.2", - "grunt-es6-module-transpiler": "~0.4.1", - "es6-module-transpiler": "*", + "grunt-es6-module-transpiler": "joefiorini/grunt-es6-module-transpiler", + "es6-module-packager": "*", "jison": "~0.3.0", "keen.io": "0.0.3", "mocha": "*", diff --git a/tasks/packager.js b/tasks/packager.js new file mode 100644 index 000000000..43b9073a2 --- /dev/null +++ b/tasks/packager.js @@ -0,0 +1,12 @@ +var Packager = require('es6-module-packager').default, + fs = require('fs'); + +module.exports = function(grunt) { + grunt.registerMultiTask('packager', 'Transpiles scripts written using ES6 to ES5.', function() { + var options = this.options(); + this.files.forEach(function(file) { + var packager = new Packager(file.src[0], {export: options.export}); + fs.writeFileSync(file.dest, packager.toLocals()); + }); + }); +}; From 820cbb006ef05bf3a4fef915637cd5fee46a5373 Mon Sep 17 00:00:00 2001 From: kpdecker Date: Wed, 9 Oct 2013 02:37:02 -0700 Subject: [PATCH 135/200] Fix concurrent traceur exec error --- Gruntfile.js | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/Gruntfile.js b/Gruntfile.js index db27188fa..ebd03c3e1 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -64,9 +64,15 @@ module.exports = function(grunt) { 'parser', 'transpile:amd', 'transpile:cjs', - 'packager', + 'packager-fork', 'uglify']); + grunt.registerTask('packager-fork', function() { + // Allows us to run the packager task out of process to work around the multiple + // traceur exec issues + grunt.util.spawn({grunt: true, args: ['packager']}, this.async()); + }); + // Run a server. This is ideal for running the QUnit tests in the browser. this.registerTask('server', [ 'build', From 855eae37d0eeb74f673b4f97484ed2a1fee1c1fc Mon Sep 17 00:00:00 2001 From: kpdecker Date: Wed, 9 Oct 2013 02:38:04 -0700 Subject: [PATCH 136/200] Create runtime specific module --- lib/handlebars.js | 32 +++++--------------------------- lib/handlebars.runtime.js | 30 ++++++++++++++++++++++++++++++ 2 files changed, 35 insertions(+), 27 deletions(-) create mode 100644 lib/handlebars.runtime.js diff --git a/lib/handlebars.js b/lib/handlebars.js index cf538a8be..4abdd4a1b 100644 --- a/lib/handlebars.js +++ b/lib/handlebars.js @@ -1,11 +1,4 @@ -module base from "./handlebars/base"; - -// Each of these augment the Handlebars object. No need to setup here. -// (This is done to easily share code between commonjs and browse envs) -import SafeString from "./handlebars/safe-string"; -import Exception from "./handlebars/exception"; -module Utils from "./handlebars/utils"; -module runtime from "./handlebars/runtime"; +import Handlebars from "./handlebars.runtime"; // Compiler imports module AST from "./handlebars/compiler/ast"; @@ -13,27 +6,12 @@ import { parser as Parser, parse } from "./handlebars/compiler/base"; import { Compiler, compile, precompile } from "./handlebars/compiler/compiler"; import JavaScriptCompiler from "./handlebars/compiler/javascript-compiler"; -// For compatibility and usage outside of module systems, make the Handlebars object a namespace +var _create = Handlebars.create; var create = function() { - var hb = {}, - env = new base.HandlebarsEnvironment(); - - // support new environments in global namespace mode - hb.registerHelper = env.registerHelper.bind(env); - hb.registerPartial = env.registerPartial.bind(env); - - Utils.extend(hb, base); - hb.SafeString = SafeString; - hb.Exception = Exception; - hb.Utils = Utils; - - hb.VM = runtime; - hb.template = runtime.template; + var hb = _create(); hb.compile = function(input, options) { - options = options || {}; - options.env = options.env || env; - return compile(input, options); + return compile(input, options, hb); }; hb.precompile = precompile; @@ -46,7 +24,7 @@ var create = function() { return hb; }; -var Handlebars = create(); +Handlebars = create(); Handlebars.create = create; export default Handlebars; diff --git a/lib/handlebars.runtime.js b/lib/handlebars.runtime.js new file mode 100644 index 000000000..a59101919 --- /dev/null +++ b/lib/handlebars.runtime.js @@ -0,0 +1,30 @@ +module base from "./handlebars/base"; + +// Each of these augment the Handlebars object. No need to setup here. +// (This is done to easily share code between commonjs and browse envs) +import SafeString from "./handlebars/safe-string"; +import Exception from "./handlebars/exception"; +module Utils from "./handlebars/utils"; +module runtime from "./handlebars/runtime"; + +// For compatibility and usage outside of module systems, make the Handlebars object a namespace +var create = function() { + var hb = new base.HandlebarsEnvironment(); + + Utils.extend(hb, base); + hb.SafeString = SafeString; + hb.Exception = Exception; + hb.Utils = Utils; + + hb.VM = runtime; + hb.template = function(spec) { + return runtime.template(spec, hb); + }; + + return hb; +}; + +var Handlebars = create(); +Handlebars.create = create; + +export default Handlebars; From 0966f5172b39151e0fa2d4830709efec7e58e4ca Mon Sep 17 00:00:00 2001 From: kpdecker Date: Wed, 9 Oct 2013 02:40:30 -0700 Subject: [PATCH 137/200] Defer load packager module Second change required to work around multiple traceur loading issue. --- tasks/packager.js | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/tasks/packager.js b/tasks/packager.js index 43b9073a2..f01eef565 100644 --- a/tasks/packager.js +++ b/tasks/packager.js @@ -1,8 +1,9 @@ -var Packager = require('es6-module-packager').default, - fs = require('fs'); - module.exports = function(grunt) { grunt.registerMultiTask('packager', 'Transpiles scripts written using ES6 to ES5.', function() { + // Execute in here to prevent traceur private var blowup + var Packager = require('es6-module-packager').default, + fs = require('fs'); + var options = this.options(); this.files.forEach(function(file) { var packager = new Packager(file.src[0], {export: options.export}); From b6c9f85d227aad8ed2db9c04f0eed5a7c7131879 Mon Sep 17 00:00:00 2001 From: kpdecker Date: Wed, 9 Oct 2013 03:20:22 -0700 Subject: [PATCH 138/200] Use template env and compile methods --- lib/handlebars/compiler/compiler.js | 5 ++--- lib/handlebars/runtime.js | 16 ++++++++-------- 2 files changed, 10 insertions(+), 11 deletions(-) diff --git a/lib/handlebars/compiler/compiler.js b/lib/handlebars/compiler/compiler.js index 0691a2316..50195e3ab 100644 --- a/lib/handlebars/compiler/compiler.js +++ b/lib/handlebars/compiler/compiler.js @@ -1,5 +1,4 @@ import Exception from "../exception"; -import { template } from "../runtime"; import { parse } from "./base"; import JavaScriptCompiler from "./javascript-compiler"; module AST from "./ast"; @@ -428,7 +427,7 @@ export function precompile(input, options) { return new JavaScriptCompiler().compile(environment, options); } -export function compile(input, options) { +export function compile(input, options, env) { if (input == null || (typeof input !== 'string' && input.constructor !== AST.ProgramNode)) { throw new Exception("You must pass a string or Handlebars AST to Handlebars.compile. You passed " + input); } @@ -445,7 +444,7 @@ export function compile(input, options) { var ast = parse(input); var environment = new Compiler().compile(ast, options); var templateSpec = new JavaScriptCompiler().compile(environment, options, undefined, true); - return template(templateSpec, options.env || Handlebars, compile); + return env.template(templateSpec); } // Template is only compiled on first use and cached after that point. diff --git a/lib/handlebars/runtime.js b/lib/handlebars/runtime.js index f984fe672..c087b1ecf 100644 --- a/lib/handlebars/runtime.js +++ b/lib/handlebars/runtime.js @@ -22,9 +22,13 @@ function checkRevision(compilerInfo) { // TODO: Remove this line and break up compilePartial -export function template(templateSpec, Hbars, compile) { +export function template(templateSpec, env) { + if (!env) { + throw new Error("No environment passed to template"); + } + var invokePartialWrapper; - if (compile) { + if (env.compile) { invokePartialWrapper = function(partial, name, context, helpers, partials, data) { // TODO : Check this for all inputs and the options handling (partial flag, etc). This feels // like there should be a common exec path @@ -32,7 +36,7 @@ export function template(templateSpec, Hbars, compile) { if (result) { return result; } var options = { helpers: helpers, partials: partials, data: data }; - partials[name] = compile(partial, { data: data !== undefined }); + partials[name] = env.compile(partial, { data: data !== undefined }, env); return partials[name](context, options); }; } else { @@ -43,10 +47,6 @@ export function template(templateSpec, Hbars, compile) { }; } - if (!Hbars) { - throw new Error("YUNO HANDLEBARS"); - } - // Just add water var container = { escapeExpression: escapeExpression, @@ -78,7 +78,7 @@ export function template(templateSpec, Hbars, compile) { return function(context, options) { options = options || {}; - var namespace = options.partial ? options : Hbars, + var namespace = options.partial ? options : env, helpers, partials; From 4d7124f6bfcc3b316c33558840c9e952cca1ce8c Mon Sep 17 00:00:00 2001 From: kpdecker Date: Wed, 9 Oct 2013 03:21:21 -0700 Subject: [PATCH 139/200] Remove global Handlebars references --- lib/handlebars/base.js | 9 +++++---- lib/handlebars/compiler/ast.js | 2 +- spec/builtins.js | 7 +------ 3 files changed, 7 insertions(+), 11 deletions(-) diff --git a/lib/handlebars/base.js b/lib/handlebars/base.js index c83144af5..a4acef901 100644 --- a/lib/handlebars/base.js +++ b/lib/handlebars/base.js @@ -3,8 +3,6 @@ import { extend, isEmpty } from "./utils"; import Exception from "./exception"; -var K = function() { return this; }; - export var VERSION = "1.0.0"; export var COMPILER_REVISION = 4; @@ -44,6 +42,9 @@ export function HandlebarsEnvironment(helpers, partials) { HandlebarsEnvironment.prototype = { constructor: HandlebarsEnvironment, + logger: logger, + log: log, + registerHelper: function(name, fn, inverse) { if (toString.call(name) === objectType) { if (inverse || fn) { throw new Exception('Arg not supported with multiple helpers'); } @@ -148,7 +149,7 @@ function registerDefaultHelpers(instance) { instance.registerHelper('log', function(context, options) { var level = options.data && options.data.level != null ? parseInt(options.data.level, 10) : 1; - Handlebars.log(level, context); + instance.log(level, context); }); } @@ -174,6 +175,6 @@ export function log(level, obj) { logger.log(level, obj); } export var createFrame = function(object) { var obj = {}; - Handlebars.Utils.extend(obj, object); + extend(obj, object); return obj; }; diff --git a/lib/handlebars/compiler/ast.js b/lib/handlebars/compiler/ast.js index e4e804914..336492d96 100644 --- a/lib/handlebars/compiler/ast.js +++ b/lib/handlebars/compiler/ast.js @@ -71,7 +71,7 @@ export function IdNode(parts) { original += (parts[i].separator || '') + part; if (part === ".." || part === "." || part === "this") { - if (dig.length > 0) { throw new Handlebars.Exception("Invalid path: " + original); } + if (dig.length > 0) { throw new Exception("Invalid path: " + original); } else if (part === "..") { depth++; } else { this.isScoped = true; } } diff --git a/spec/builtins.js b/spec/builtins.js index 7c3e01271..c6789645e 100644 --- a/spec/builtins.js +++ b/spec/builtins.js @@ -1,10 +1,5 @@ /*global CompilerContext, shouldCompileTo, compileWithPartials */ describe('builtin helpers', function() { - var originalLog = Handlebars.log; - afterEach(function() { - Handlebars.log = originalLog; - }); - describe('#if', function() { it("if", function() { var string = "{{#if goodbye}}GOODBYE {{/if}}cruel {{world}}!"; @@ -130,7 +125,7 @@ describe('builtin helpers', function() { var hash = { blah: "whee" }; var levelArg, logArg; - Handlebars.log = function(level, arg){ levelArg = level, logArg = arg; }; + handlebarsEnv.log = function(level, arg){ levelArg = level, logArg = arg; }; shouldCompileTo(string, hash, "", "log should not display"); equals(1, levelArg, "should call log with 1"); From 3b0a3ca1b2eb61eb314f9260b860cc6ff01923fe Mon Sep 17 00:00:00 2001 From: kpdecker Date: Wed, 9 Oct 2013 03:22:05 -0700 Subject: [PATCH 140/200] Fix test runners under node --- spec/basic.js | 2 +- spec/env/browser.js | 13 +++++++++---- spec/env/node.js | 11 ++++------- spec/env/runner.js | 11 +++++------ spec/env/runtime.js | 21 +++++++++++++++------ 5 files changed, 34 insertions(+), 24 deletions(-) diff --git a/spec/basic.js b/spec/basic.js index 679cf26ef..e0e9a6ab1 100644 --- a/spec/basic.js +++ b/spec/basic.js @@ -1,7 +1,7 @@ global.handlebarsEnv = null; beforeEach(function() { - global.handlebarsEnv = new Handlebars.HandlebarsEnvironment(); + global.handlebarsEnv = Handlebars.create(); }); describe("basic context", function() { diff --git a/spec/env/browser.js b/spec/env/browser.js index a17aa6618..e6f81026a 100644 --- a/spec/env/browser.js +++ b/spec/env/browser.js @@ -1,13 +1,18 @@ require('./common'); -global.Handlebars = require('../../dist/handlebars'); +var _ = require('underscore'), + fs = require('fs'), + vm = require('vm'); + +global.Handlebars = undefined; +vm.runInThisContext(fs.readFileSync(__dirname + '/../../dist/handlebars.js'), 'dist/handlebars.js'); global.CompilerContext = { compile: function(template, options) { - var templateSpec = Handlebars.precompile(template, options); - return Handlebars.template(eval('(' + templateSpec + ')')); + var templateSpec = handlebarsEnv.precompile(template, options); + return handlebarsEnv.template(eval('(' + templateSpec + ')')); }, compileWithPartial: function(template, options) { - return Handlebars.compile(template, options); + return handlebarsEnv.compile(template, options); } }; diff --git a/spec/env/node.js b/spec/env/node.js index 2a23925ea..aed2d54af 100644 --- a/spec/env/node.js +++ b/spec/env/node.js @@ -3,14 +3,11 @@ require('./common'); global.Handlebars = require('../../lib'); global.CompilerContext = { - compile: function(template, options, env) { - env = env || handlebarsEnv; - var templateSpec = Handlebars.precompile(template, options); - return Handlebars.template(eval('(' + templateSpec + ')'), env); + compile: function(template, options) { + var templateSpec = handlebarsEnv.precompile(template, options); + return handlebarsEnv.template(eval('(' + templateSpec + ')')); }, compileWithPartial: function(template, options) { - options = options || {}; - options.env = handlebarsEnv; - return Handlebars.compile(template, options); + return handlebarsEnv.compile(template, options); } }; diff --git a/spec/env/runner.js b/spec/env/runner.js index 0d0cef2e9..143d30023 100644 --- a/spec/env/runner.js +++ b/spec/env/runner.js @@ -13,12 +13,11 @@ var files = fs.readdirSync(testDir) .map(function(name) { return testDir + '/' + name; }); run('./node', function() { - process.exit(errors); - //run('./browser', function() { - //run('./runtime', function() { - //process.exit(errors); - //}); - //}); + run('./browser', function() { + run('./runtime', function() { + process.exit(errors); + }); + }); }); diff --git a/spec/env/runtime.js b/spec/env/runtime.js index 19bd4ed03..fe5d4cac4 100644 --- a/spec/env/runtime.js +++ b/spec/env/runtime.js @@ -1,15 +1,24 @@ require('./common'); -global.Handlebars = require('../../dist/handlebars.runtime'); +var _ = require('underscore'), + fs = require('fs'), + vm = require('vm'); -var compiler = require('../../lib/handlebars'); +global.Handlebars = undefined; +vm.runInThisContext(fs.readFileSync(__dirname + '/../../dist/handlebars.runtime.js'), 'dist/handlebars.runtime.js'); + +var compiler = require('../../dist/cjs/handlebars/compiler/compiler'); global.CompilerContext = { - compile: function(template, options, env) { + compile: function(template, options) { var templateSpec = compiler.precompile(template, options); - return Handlebars.template(eval('(' + templateSpec + ')'), env); + return handlebarsEnv.template(eval('(' + templateSpec + ')')); }, - compileWithPartial: function(template, options, env) { - return compiler.compile(template, options); + compileWithPartial: function(template, options) { + // Hack the compiler on to the environment for these specific tests + handlebarsEnv.compile = function(template, options) { + return compiler.compile(template, options, handlebarsEnv); + }; + return handlebarsEnv.compile(template, options); } }; From 2abe4d404da1b2876b04ce6df7b552450e0b11f1 Mon Sep 17 00:00:00 2001 From: kpdecker Date: Wed, 9 Oct 2013 03:35:47 -0700 Subject: [PATCH 141/200] Generate single amd output file --- Gruntfile.js | 35 +++++++++++++++++++++++++++++------ configurations/transpile.js | 1 + package.json | 1 + 3 files changed, 31 insertions(+), 6 deletions(-) diff --git a/Gruntfile.js b/Gruntfile.js index ebd03c3e1..c83e8837a 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -39,6 +39,24 @@ module.exports = function(grunt) { }] } }, + requirejs: { + options: { + optimize: "none", + baseUrl: "dist/amd/" + }, + dist: { + options: { + name: "handlebars", + out: "dist/handlebars.amd.js" + } + }, + runtime: { + options: { + name: "handlebars.runtime", + out: "dist/handlebars.runtime.amd.js" + } + } + }, uglify: { options: { @@ -47,12 +65,15 @@ module.exports = function(grunt) { preserveComments: 'some' }, dist: { - src: 'dist/handlebars.js', - dest: 'dist/handlebars.min.js' - }, - runtime: { - src: 'dist/handlebars.runtime.js', - dest: 'dist/handlebars.runtime.min.js' + files: [{ + cwd: 'dist/', + expand: true, + src: ['handlebars*.js'], + dest: 'dist/', + rename: function(dest, src) { + return dest + src.replace(/\.js$/, '.min.js'); + } + }] } } }); @@ -65,6 +86,7 @@ module.exports = function(grunt) { 'transpile:amd', 'transpile:cjs', 'packager-fork', + 'requirejs', 'uglify']); grunt.registerTask('packager-fork', function() { @@ -83,6 +105,7 @@ module.exports = function(grunt) { // Load tasks from npm grunt.loadNpmTasks('grunt-contrib-clean'); grunt.loadNpmTasks('grunt-contrib-concat'); + grunt.loadNpmTasks('grunt-contrib-requirejs'); grunt.loadNpmTasks('grunt-contrib-connect'); grunt.loadNpmTasks('grunt-contrib-jshint'); grunt.loadNpmTasks('grunt-contrib-uglify'); diff --git a/configurations/transpile.js b/configurations/transpile.js index bac3babfc..4fb4a66c8 100644 --- a/configurations/transpile.js +++ b/configurations/transpile.js @@ -1,6 +1,7 @@ module.exports = { amd: { type: "amd", + anonymous: true, files: [{ expand: true, cwd: 'lib/', diff --git a/package.json b/package.json index 44d5432ac..b021bd4c6 100644 --- a/package.json +++ b/package.json @@ -22,6 +22,7 @@ "grunt-contrib-clean": "~0.4.1", "grunt-contrib-connect": "~0.3.0", "grunt-contrib-jshint": "~0.6.3", + "grunt-contrib-requirejs": "~0.4.1", "grunt-contrib-uglify": "~0.2.2", "grunt-contrib-watch": "~0.4.4", "grunt-hang": "~0.1.2", From 096ac2d552a793795fa2df381136631342cc62ed Mon Sep 17 00:00:00 2001 From: kpdecker Date: Wed, 9 Oct 2013 03:42:26 -0700 Subject: [PATCH 142/200] Record stack on failed travis build --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 0516269ce..1b828d1b1 100644 --- a/.travis.yml +++ b/.travis.yml @@ -7,7 +7,7 @@ before_script: - npm install -g grunt-cli script: - - grunt build metrics publish:latest + - grunt --stack build metrics publish:latest email: on_failure: change From b47f2cfcf5acd9b56f39fef008ddf6aee57add8c Mon Sep 17 00:00:00 2001 From: kpdecker Date: Wed, 9 Oct 2013 03:52:45 -0700 Subject: [PATCH 143/200] Additional logging on dist size task --- bench/dist-size.js | 1 + 1 file changed, 1 insertion(+) diff --git a/bench/dist-size.js b/bench/dist-size.js index 26d15911d..84926c82b 100644 --- a/bench/dist-size.js +++ b/bench/dist-size.js @@ -8,6 +8,7 @@ module.exports = function(grunt, callback) { distSizes = {}; async.each(distFiles, function(file, callback) { + grunt.log.writeln('Dist size: "dist/' + file + '"'); var content = fs.readFileSync('dist/' + file); file = file.replace(/\.js/, '').replace(/\./g, '_'); From 9769045e04bd30dbc6259d17bd7b3c9559b08fef Mon Sep 17 00:00:00 2001 From: kpdecker Date: Wed, 9 Oct 2013 03:53:09 -0700 Subject: [PATCH 144/200] Cleanup unused var warnings --- lib/handlebars/exception.js | 2 +- lib/handlebars/runtime.js | 2 +- lib/handlebars/utils.js | 3 +-- 3 files changed, 3 insertions(+), 4 deletions(-) diff --git a/lib/handlebars/exception.js b/lib/handlebars/exception.js index fc88534cd..6de9cfdb4 100644 --- a/lib/handlebars/exception.js +++ b/lib/handlebars/exception.js @@ -1,7 +1,7 @@ var errorProps = ['description', 'fileName', 'lineNumber', 'message', 'name', 'number', 'stack']; -function Exception(message) { +function Exception(/* message */) { var tmp = Error.prototype.constructor.apply(this, arguments); // Unfortunately errors are not enumerable in Chrome (at least), so `for prop in tmp` doesn't work. diff --git a/lib/handlebars/runtime.js b/lib/handlebars/runtime.js index c087b1ecf..57a830ba8 100644 --- a/lib/handlebars/runtime.js +++ b/lib/handlebars/runtime.js @@ -40,7 +40,7 @@ export function template(templateSpec, env) { return partials[name](context, options); }; } else { - invokePartialWrapper = function(partial, name, context, helpers, partials, data) { + invokePartialWrapper = function(partial, name /* , context, helpers, partials, data */) { var result = invokePartial.apply(this, arguments); if (result) { return result; } throw new Exception("The partial " + name + " could not be compiled when running in runtime-only mode"); diff --git a/lib/handlebars/utils.js b/lib/handlebars/utils.js index 8bb28f352..998c9ca6b 100644 --- a/lib/handlebars/utils.js +++ b/lib/handlebars/utils.js @@ -1,7 +1,6 @@ import SafeString from "./safe-string"; -var toString = Object.prototype.toString, - isArray = Array.isArray; +var isArray = Array.isArray; var escape = { "&": "&", From e5a4889b36b94598e2ee80177bc0b339abda9dd2 Mon Sep 17 00:00:00 2001 From: kpdecker Date: Wed, 9 Oct 2013 04:02:21 -0700 Subject: [PATCH 145/200] Fix metrics exec --- bench/precompile-size.js | 2 +- bench/throughput.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/bench/precompile-size.js b/bench/precompile-size.js index 12fb0dc0c..66eacd025 100644 --- a/bench/precompile-size.js +++ b/bench/precompile-size.js @@ -3,7 +3,7 @@ var _ = require('underscore'), module.exports = function(grunt, callback) { // Deferring to here in case we have a build for parser, etc as part of this grunt exec - var Handlebars = require('../lib/handlebars'); + var Handlebars = require('../lib'); var templateSizes = {}; _.each(templates, function(info, template) { diff --git a/bench/throughput.js b/bench/throughput.js index b750e44f3..308446a09 100644 --- a/bench/throughput.js +++ b/bench/throughput.js @@ -114,7 +114,7 @@ function makeSuite(bench, name, template, handlebarsOnly) { module.exports = function(grunt, callback) { // Deferring load incase we are being run inline with the grunt build - Handlebars = require('../lib/handlebars'); + Handlebars = require('../lib'); console.log('Execution Throughput'); runner(grunt, makeSuite, function(times, scaled) { From e1f7f00cd482cc52a61247834bcfaaab5acf9911 Mon Sep 17 00:00:00 2001 From: kpdecker Date: Wed, 9 Oct 2013 04:02:49 -0700 Subject: [PATCH 146/200] Fix dist-size metrics exec --- bench/dist-size.js | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/bench/dist-size.js b/bench/dist-size.js index 84926c82b..9e5fdc0f5 100644 --- a/bench/dist-size.js +++ b/bench/dist-size.js @@ -8,8 +8,17 @@ module.exports = function(grunt, callback) { distSizes = {}; async.each(distFiles, function(file, callback) { - grunt.log.writeln('Dist size: "dist/' + file + '"'); - var content = fs.readFileSync('dist/' + file); + var content; + try { + content = fs.readFileSync('dist/' + file); + } catch (err) { + if (err.code === 'EISDIR') { + callback(); + return; + } else { + throw err; + } + } file = file.replace(/\.js/, '').replace(/\./g, '_'); distSizes[file] = content.length; From 7ee0c10c103444d2c4fe8430cb46060dcb0789d6 Mon Sep 17 00:00:00 2001 From: kpdecker Date: Sat, 12 Oct 2013 15:40:55 -0500 Subject: [PATCH 147/200] Fix transpiler error in printer.js --- lib/handlebars/compiler/printer.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/lib/handlebars/compiler/printer.js b/lib/handlebars/compiler/printer.js index 01b05d1c7..a35cdf408 100644 --- a/lib/handlebars/compiler/printer.js +++ b/lib/handlebars/compiler/printer.js @@ -4,7 +4,10 @@ export function print(ast) { return new PrintVisitor().accept(ast); } -export function PrintVisitor() { this.padding = 0; } +export function PrintVisitor() { + this.padding = 0; +} + PrintVisitor.prototype = new Visitor(); PrintVisitor.prototype.pad = function(string, newline) { From 95d5768e0eb740e74e68cd9d487a47d511e5c780 Mon Sep 17 00:00:00 2001 From: kpdecker Date: Sat, 12 Oct 2013 15:41:15 -0500 Subject: [PATCH 148/200] Expose printer and visitor objects in node --- lib/index.js | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/lib/index.js b/lib/index.js index 639435c9e..ca51d3a49 100644 --- a/lib/index.js +++ b/lib/index.js @@ -5,6 +5,12 @@ var handlebars = require('../dist/cjs/handlebars').default; +handlebars.Visitor = require('../dist/cjs/handlebars/compiler/visitor').default; + +var printer = require('../dist/cjs/handlebars/compiler/printer'); +handlebars.PrintVisitor = printer.PrintVisitor; +handlebars.print = printer.print; + module.exports = handlebars; // Publish a Node.js require() handler for .handlebars and .hbs files From 4d31df2f30a3607d86806d0efe91b7e9f00a1fac Mon Sep 17 00:00:00 2001 From: kpdecker Date: Sat, 12 Oct 2013 15:41:38 -0500 Subject: [PATCH 149/200] Update logger interface to expose prior API --- lib/handlebars/base.js | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/lib/handlebars/base.js b/lib/handlebars/base.js index a4acef901..29c8de814 100644 --- a/lib/handlebars/base.js +++ b/lib/handlebars/base.js @@ -153,13 +153,16 @@ function registerDefaultHelpers(instance) { }); } -var levels = { - DEBUG: 0, INFO: 1, WARN: 2, ERROR: 3, level: 3 -}; +export var logger = { + methodMap: { 0: 'debug', 1: 'info', 2: 'warn', 3: 'error' }, -var methodMap = { 0: 'debug', 1: 'info', 2: 'warn', 3: 'error' }; + // State enum + DEBUG: 0, + INFO: 1, + WARN: 2, + ERROR: 3, + level: 3, -export var logger = { // can be overridden in the host environment log: function(level, obj) { if (logger.level <= level) { From 6136e7f346b36ee4fcdb832d08469e2fee7eacac Mon Sep 17 00:00:00 2001 From: kpdecker Date: Sat, 12 Oct 2013 15:41:58 -0500 Subject: [PATCH 150/200] Do not emit configurations dir in npm --- .npmignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.npmignore b/.npmignore index 233b2cc89..c1e8834ea 100644 --- a/.npmignore +++ b/.npmignore @@ -10,6 +10,7 @@ Rakefile *.gemspec *.nuspec bench/* +configurations/* spec/* src/* tasks/* From abf37ad739eb6a5bba8ca4a1046659e5ae20bff4 Mon Sep 17 00:00:00 2001 From: kpdecker Date: Sat, 12 Oct 2013 15:44:02 -0500 Subject: [PATCH 151/200] Update package.json for backwards compatibility --- package.json | 25 +++++++++++++++++++++---- 1 file changed, 21 insertions(+), 4 deletions(-) diff --git a/package.json b/package.json index b021bd4c6..e816a361b 100644 --- a/package.json +++ b/package.json @@ -1,8 +1,15 @@ { - "name": "handlebars.js", + "name": "handlebars", "barename": "handlebars", "version": "1.0.12", "description": "Handlebars provides the power necessary to let you build semantic templates effectively with no frustration", + "homepage": "http://www.handlebarsjs.com/", + "keywords": [ + "handlebars", + "mustache", + "template", + "html" + ], "repository": { "type": "git", "url": "https://github.com/wycats/handlebars.js.git" @@ -10,6 +17,16 @@ "author": "Yehuda Katz", "license": "BSD", "readmeFilename": "README.md", + + "engines": { + "node": ">=0.4.7" + }, + "dependencies": { + "optimist": "~0.3" + }, + "optionalDependencies": { + "uglify-js": "~2.3" + }, "devDependencies": { "async": "~0.2.9", "aws-sdk": "~1.5.0", @@ -36,12 +53,12 @@ "should": "~1.2.2", "underscore": "~1.5.1" }, - "main": "lib/handlebars.js", + + "main": "lib/index.js", "bin": { "handlebars": "bin/handlebars" }, "scripts": { "test": "node ./spec/env/runner" - }, - "optionalDependencies": {} + } } From 52dea1e70e439ddb7fb17a5ea5c2465408e3ca8c Mon Sep 17 00:00:00 2001 From: kpdecker Date: Sat, 12 Oct 2013 15:47:18 -0500 Subject: [PATCH 152/200] Force rebuild on publish --- package.json | 1 + 1 file changed, 1 insertion(+) diff --git a/package.json b/package.json index e816a361b..dbd8801ff 100644 --- a/package.json +++ b/package.json @@ -59,6 +59,7 @@ "handlebars": "bin/handlebars" }, "scripts": { + "prepublish": "grunt", "test": "node ./spec/env/runner" } } From 83b5e10d703fb07f45b69fd444dcf154c06d6999 Mon Sep 17 00:00:00 2001 From: kpdecker Date: Sat, 12 Oct 2013 16:10:14 -0500 Subject: [PATCH 153/200] Install grunt prior to other actions --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 1b828d1b1..a796707c6 100644 --- a/.travis.yml +++ b/.travis.yml @@ -3,7 +3,7 @@ node_js: - "0.8" - "0.10" -before_script: +before_install: - npm install -g grunt-cli script: From 3f96319f103d1e9dc4a6de220d2a9934e00df0b6 Mon Sep 17 00:00:00 2001 From: kpdecker Date: Sat, 12 Oct 2013 16:21:52 -0500 Subject: [PATCH 154/200] Update compat notes for es6-modules --- release-notes.md | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/release-notes.md b/release-notes.md index e7f5ca0f6..786761bc3 100644 --- a/release-notes.md +++ b/release-notes.md @@ -8,8 +8,10 @@ - [#544](https://github.com/wycats/handlebars.js/issues/544) - Push travis builds to build server ([@fivetanley](https://github.com/fivetanley)) Compatibility notes: -- The client-code has been wrapped in a hybrid AMD/CommonJS loader. - This may cause unexpected issues with different build/loading mechanisms so this change is being made early in the 1.1 lifecycle. +- The project now includes separate artifacts for AMD, CommonJS, and global objects. + - AMD: Users may load the bundled `handlebars.amd.js` or `handlebars.runtime.amd.js` files or load individual modules directly. AMD users should also note that the handlebars object is exposed via the `default` field on the imported object. This [gist](https://gist.github.com/wycats/7417be0dc361a69d5916) provides some discussion of possible compatibility shims. + - CommonJS/Node: Node loading occurs as normal via `require` + - Globals: The `handlebars.js` and `handlebars.runtime.js` files should behave in the same manner as the v1.0.12 / 1.0.0 release. [Commits](https://github.com/wycats/handlebars.js/compare/v1.0.12...master) From e20591b49dbc38b391fa9dfc3846e0bf9dd9cfff Mon Sep 17 00:00:00 2001 From: denniskuczynski Date: Sat, 21 Sep 2013 15:45:43 -0400 Subject: [PATCH 155/200] Add @first and @last data variables to #each helper resolving Issue #483 --- lib/handlebars/base.js | 6 +++++- spec/builtins.js | 40 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 45 insertions(+), 1 deletion(-) diff --git a/lib/handlebars/base.js b/lib/handlebars/base.js index 29c8de814..cf0cbb93b 100644 --- a/lib/handlebars/base.js +++ b/lib/handlebars/base.js @@ -106,7 +106,11 @@ function registerDefaultHelpers(instance) { if(context && typeof context === 'object') { if (isArray(context)) { for(var j = context.length; i Date: Mon, 30 Sep 2013 14:15:52 -0700 Subject: [PATCH 156/200] Remove uncessary ? operator when returning boolean values for @first and @last --- lib/handlebars/base.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/handlebars/base.js b/lib/handlebars/base.js index cf0cbb93b..6d19a33b6 100644 --- a/lib/handlebars/base.js +++ b/lib/handlebars/base.js @@ -108,8 +108,8 @@ function registerDefaultHelpers(instance) { for(var j = context.length; i Date: Sat, 12 Oct 2013 17:13:23 -0500 Subject: [PATCH 157/200] Update precompiler for default output Fixes #563 --- bin/handlebars | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bin/handlebars b/bin/handlebars index b30a1fe19..8ab6afda8 100755 --- a/bin/handlebars +++ b/bin/handlebars @@ -127,7 +127,7 @@ extension = new RegExp('\\.' + extension + '$'); var output = []; if (!argv.simple) { if (argv.amd) { - output.push('define([\'' + argv.handlebarPath + 'handlebars\'], function(Handlebars) {\n'); + output.push('define([\'' + argv.handlebarPath + 'handlebars\'], function(Handlebars) {\n Handlebars = Handlebars["default"];'); } else if (argv.commonjs) { output.push('var Handlebars = require("' + argv.commonjs + '");'); } else { From 468fa8b6dd743431ac42cc2b6ad351ea4506561d Mon Sep 17 00:00:00 2001 From: Daniel Marcotte Date: Sat, 24 Aug 2013 12:18:12 -0700 Subject: [PATCH 158/200] Fix "\\" escaping Previously, "\\{{foo}}" would only result in the desired "\fooValue" if it was at the beginning of the file or immediately after a close stache. --- spec/basic.js | 2 ++ spec/tokenizer.js | 38 ++++++++++++++++++++++++++++++++++++++ src/handlebars.l | 12 +++++++++--- 3 files changed, 49 insertions(+), 3 deletions(-) diff --git a/spec/basic.js b/spec/basic.js index e0e9a6ab1..67b4300d3 100644 --- a/spec/basic.js +++ b/spec/basic.js @@ -11,7 +11,9 @@ describe("basic context", function() { it("escaping", function() { shouldCompileTo("\\{{foo}}", { foo: "food" }, "{{foo}}"); + shouldCompileTo("content \\{{foo}}", { foo: "food" }, "content {{foo}}"); shouldCompileTo("\\\\{{foo}}", { foo: "food" }, "\\food"); + shouldCompileTo("content \\\\{{foo}}", { foo: "food" }, "content \\food"); shouldCompileTo("\\\\ {{foo}}", { foo: "food" }, "\\\\ food"); }); diff --git a/spec/tokenizer.js b/spec/tokenizer.js index f86774a69..de981e427 100644 --- a/spec/tokenizer.js +++ b/spec/tokenizer.js @@ -58,6 +58,7 @@ describe('Tokenizer', function() { var result = tokenize("{{foo}} \\{{bar}} {{baz}}"); result.should.match_tokens(['OPEN', 'ID', 'CLOSE', 'CONTENT', 'CONTENT', 'OPEN', 'ID', 'CLOSE']); + result[3].should.be_token("CONTENT", " "); result[4].should.be_token("CONTENT", "{{bar}} "); }); @@ -77,6 +78,43 @@ describe('Tokenizer', function() { result[4].should.be_token("CONTENT", "{{{bar}}} "); }); + it('supports escaping escape character', function() { + var result = tokenize("{{foo}} \\\\{{bar}} {{baz}}"); + result.should.match_tokens(['OPEN', 'ID', 'CLOSE', 'CONTENT', 'OPEN', 'ID', 'CLOSE', 'CONTENT', 'OPEN', 'ID', 'CLOSE']); + + result[3].should.be_token("CONTENT", " \\"); + result[5].should.be_token("ID", "bar"); + }); + + it('supports escaping multiple escape characters', function() { + var result = tokenize("{{foo}} \\\\{{bar}} \\\\{{baz}}"); + result.should.match_tokens(['OPEN', 'ID', 'CLOSE', 'CONTENT', 'OPEN', 'ID', 'CLOSE', 'CONTENT', 'OPEN', 'ID', 'CLOSE']); + + result[3].should.be_token("CONTENT", " \\"); + result[5].should.be_token("ID", "bar"); + result[7].should.be_token("CONTENT", " \\"); + result[9].should.be_token("ID", "baz"); + }); + + it('supports mixed escaped delimiters and escaped escape characters', function() { + var result = tokenize("{{foo}} \\\\{{bar}} \\{{baz}}"); + result.should.match_tokens(['OPEN', 'ID', 'CLOSE', 'CONTENT', 'OPEN', 'ID', 'CLOSE', 'CONTENT', 'CONTENT', 'CONTENT']); + + result[3].should.be_token("CONTENT", " \\"); + result[4].should.be_token("OPEN", "{{"); + result[5].should.be_token("ID", "bar"); + result[7].should.be_token("CONTENT", " "); + result[8].should.be_token("CONTENT", "{{baz}}"); + }); + + it('supports escaped escape character on a triple stash', function() { + var result = tokenize("{{foo}} \\\\{{{bar}}} {{baz}}"); + result.should.match_tokens(['OPEN', 'ID', 'CLOSE', 'CONTENT', 'OPEN_UNESCAPED', 'ID', 'CLOSE_UNESCAPED', 'CONTENT', 'OPEN', 'ID', 'CLOSE']); + + result[3].should.be_token("CONTENT", " \\"); + result[5].should.be_token("ID", "bar"); + }); + it('tokenizes a simple path', function() { var result = tokenize("{{foo/bar}}"); result.should.match_tokens(['OPEN', 'ID', 'SEP', 'ID', 'CLOSE']); diff --git a/src/handlebars.l b/src/handlebars.l index 018096b01..7593189ef 100644 --- a/src/handlebars.l +++ b/src/handlebars.l @@ -23,10 +23,16 @@ ID [^\s!"#%-,\.\/;->@\[-\^`\{-~]+/[=}\s\/.] %% -"\\\\"/("{{") yytext = "\\"; return 'CONTENT'; [^\x00]*?/("{{") { - if(yytext.slice(-1) !== "\\") this.begin("mu"); - if(yytext.slice(-1) === "\\") strip(0,1), this.begin("emu"); + if(yytext.slice(-2) === "\\\\") { + strip(0,1); + this.begin("mu"); + } else if(yytext.slice(-1) === "\\") { + strip(0,1); + this.begin("emu"); + } else { + this.begin("mu"); + } if(yytext) return 'CONTENT'; } From 84c837869ef3b4067acc0ac638099de6833e6ba5 Mon Sep 17 00:00:00 2001 From: kpdecker Date: Mon, 14 Oct 2013 11:15:27 -0500 Subject: [PATCH 159/200] Create release grunt task Allows for tests to be run slightly faster when developing locally. --- .travis.yml | 2 +- Gruntfile.js | 9 ++++++--- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/.travis.yml b/.travis.yml index a796707c6..1e4e9651f 100644 --- a/.travis.yml +++ b/.travis.yml @@ -7,7 +7,7 @@ before_install: - npm install -g grunt-cli script: - - grunt --stack build metrics publish:latest + - grunt --stack default metrics publish:latest email: on_failure: change diff --git a/Gruntfile.js b/Gruntfile.js index c83e8837a..c1fb65cf4 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -85,9 +85,12 @@ module.exports = function(grunt) { 'parser', 'transpile:amd', 'transpile:cjs', - 'packager-fork', + 'packager-fork']); + + this.registerTask('release', 'Build final packages', [ 'requirejs', - 'uglify']); + 'uglify' + ]); grunt.registerTask('packager-fork', function() { // Allows us to run the packager task out of process to work around the multiple @@ -127,5 +130,5 @@ module.exports = function(grunt) { }); grunt.registerTask('bench', ['metrics']); - grunt.registerTask('default', ['build', 'test']); + grunt.registerTask('default', ['build', 'test', 'release']); }; From 3f52109c1c751bc11d7b69569a462169bb9c4762 Mon Sep 17 00:00:00 2001 From: kpdecker Date: Mon, 14 Oct 2013 11:42:45 -0500 Subject: [PATCH 160/200] Cleanup currently unused grunt configs --- Gruntfile.js | 63 +++++++++++++++++++------------------ configurations/browser.js | 6 ---- configurations/concat.js | 34 -------------------- configurations/connect.js | 8 ----- configurations/transpile.js | 34 -------------------- configurations/watch.js | 4 --- package.json | 5 --- 7 files changed, 33 insertions(+), 121 deletions(-) delete mode 100644 configurations/browser.js delete mode 100644 configurations/concat.js delete mode 100644 configurations/connect.js delete mode 100644 configurations/transpile.js delete mode 100644 configurations/watch.js diff --git a/Gruntfile.js b/Gruntfile.js index c1fb65cf4..8687bbace 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -1,9 +1,5 @@ var childProcess = require('child_process'); -function config(name) { - return require('./configurations/' + name); -} - module.exports = function(grunt) { grunt.initConfig({ @@ -20,10 +16,28 @@ module.exports = function(grunt) { }, clean: ["dist"], - watch: config('watch') , - concat: config('concat'), - connect: config('connect'), - transpile: config('transpile'), + transpile: { + amd: { + type: "amd", + anonymous: true, + files: [{ + expand: true, + cwd: 'lib/', + src: '**/!(index).js', + dest: 'dist/amd/' + }] + }, + + cjs: { + type: 'cjs', + files: [{ + expand: true, + cwd: 'lib/', + src: '**/!(index).js', + dest: 'dist/cjs/' + }] + } + }, packager: { options: { @@ -83,40 +97,29 @@ module.exports = function(grunt) { 'jshint', 'clean', 'parser', - 'transpile:amd', - 'transpile:cjs', - 'packager-fork']); - - this.registerTask('release', 'Build final packages', [ - 'requirejs', - 'uglify' - ]); + 'node', + 'globals']); - grunt.registerTask('packager-fork', function() { - // Allows us to run the packager task out of process to work around the multiple - // traceur exec issues - grunt.util.spawn({grunt: true, args: ['packager']}, this.async()); - }); + this.registerTask('amd', ['transpile:amd', 'requirejs']); + this.registerTask('node', ['transpile:cjs']); + this.registerTask('globals', ['packager-fork']); - // Run a server. This is ideal for running the QUnit tests in the browser. - this.registerTask('server', [ - 'build', - 'tests', - 'connect', - 'watch']); + this.registerTask('release', 'Build final packages', ['amd', 'uglify']); // Load tasks from npm grunt.loadNpmTasks('grunt-contrib-clean'); - grunt.loadNpmTasks('grunt-contrib-concat'); grunt.loadNpmTasks('grunt-contrib-requirejs'); - grunt.loadNpmTasks('grunt-contrib-connect'); grunt.loadNpmTasks('grunt-contrib-jshint'); grunt.loadNpmTasks('grunt-contrib-uglify'); - grunt.loadNpmTasks('grunt-contrib-watch'); grunt.loadNpmTasks('grunt-es6-module-transpiler'); grunt.task.loadTasks('tasks'); + grunt.registerTask('packager-fork', function() { + // Allows us to run the packager task out of process to work around the multiple + // traceur exec issues + grunt.util.spawn({grunt: true, args: ['packager']}, this.async()); + }); grunt.registerTask('test', function() { var done = this.async(); diff --git a/configurations/browser.js b/configurations/browser.js deleted file mode 100644 index e3d0dc3b2..000000000 --- a/configurations/browser.js +++ /dev/null @@ -1,6 +0,0 @@ -module.exports = { - dist: { - src: 'tmp/<%= pkg.barename %>.browser1.js', - dest: 'dist/<%= pkg.name %>-<%= pkg.version %>.js' - } -}; diff --git a/configurations/concat.js b/configurations/concat.js deleted file mode 100644 index 459a7dd27..000000000 --- a/configurations/concat.js +++ /dev/null @@ -1,34 +0,0 @@ -module.exports = { - options: { - banner: '/*!\n\n <%= pkg.name %> v<%= pkg.version %>\n\n<%= grunt.file.read("LICENSE") %>\n@license\n*/\n', - process: function(src, name) { - var match = /\/\/ BEGIN\(BROWSER\)\n((?:.|\n)*)\n\/\/ END\(BROWSER\)/.exec(src); - return '\n// ' + name + '\n' + (match ? match[1] : src); - } - }, - dist: { - src: [ - 'dist/amd/handlebars/browser-prefix.js', - 'dist/amd/handlebars/base.js', - 'dist/amd/handlebars/compiler/parser.js', - 'dist/amd/handlebars/compiler/base.js', - 'dist/amd/handlebars/compiler/ast.js', - 'dist/amd/handlebars/utils.js', - 'dist/amd/handlebars/compiler/compiler.js', - 'dist/amd/handlebars/compiler/javascript-compiler.js', - 'dist/amd/handlebars/runtime.js', - 'dist/amd/handlebars/browser-suffix.js' - ], - dest: 'dist/handlebars.js' - }, - runtime: { - src: [ - 'dist/amd/handlebars/browser-prefix.js', - 'dist/amd/handlebars/base.js', - 'dist/amd/handlebars/utils.js', - 'dist/amd/handlebars/runtime.js', - 'dist/amd/handlebars/browser-suffix.js' - ], - dest: 'dist/handlebars.runtime.js' - } -}; diff --git a/configurations/connect.js b/configurations/connect.js deleted file mode 100644 index dacc7600b..000000000 --- a/configurations/connect.js +++ /dev/null @@ -1,8 +0,0 @@ -module.exports = { - server: {}, - options: { - hostname: '0.0.0.0', - port: 8000, - base: '.' - } -}; diff --git a/configurations/transpile.js b/configurations/transpile.js deleted file mode 100644 index 4fb4a66c8..000000000 --- a/configurations/transpile.js +++ /dev/null @@ -1,34 +0,0 @@ -module.exports = { - amd: { - type: "amd", - anonymous: true, - files: [{ - expand: true, - cwd: 'lib/', - src: '**/!(index).js', - dest: 'dist/amd/' - }] - }, - - cjs: { - type: 'cjs', - files: [{ - expand: true, - cwd: 'lib/', - src: '**/!(index).js', - dest: 'dist/cjs/' - }] - }, - - globals: { - type: 'globals', - src: ["lib/<%= pkg.barename %>.js", "lib/*/**/*.js"], - dest: "tmp/<%= pkg.barename %>.globals.js" - }, - - tests: { - type: 'amd', - src: ['test/test_helpers.js', 'test/tests.js', 'test/tests/**/*_test.js'], - dest: 'tmp/tests.amd.js' - } -}; diff --git a/configurations/watch.js b/configurations/watch.js deleted file mode 100644 index 49b1c49bd..000000000 --- a/configurations/watch.js +++ /dev/null @@ -1,4 +0,0 @@ -module.exports = { - files: ['lib/**', 'vendor/*', 'test/**/*'], - tasks: ['build', 'tests'] -}; diff --git a/package.json b/package.json index dbd8801ff..2dae1ec4b 100644 --- a/package.json +++ b/package.json @@ -34,15 +34,10 @@ "dustjs-linkedin": "~2.0.2", "eco": "~1.1.0-rc-3", "grunt": "~0.4.1", - "connect": "~2.7.4", - "grunt-contrib-concat": "~0.3.0", "grunt-contrib-clean": "~0.4.1", - "grunt-contrib-connect": "~0.3.0", "grunt-contrib-jshint": "~0.6.3", "grunt-contrib-requirejs": "~0.4.1", "grunt-contrib-uglify": "~0.2.2", - "grunt-contrib-watch": "~0.4.4", - "grunt-hang": "~0.1.2", "grunt-es6-module-transpiler": "joefiorini/grunt-es6-module-transpiler", "es6-module-packager": "*", "jison": "~0.3.0", From 15500aef7002738cbd3f9ca6a5423529b6c888e9 Mon Sep 17 00:00:00 2001 From: kpdecker Date: Mon, 14 Oct 2013 11:50:18 -0500 Subject: [PATCH 161/200] Access utils methods via modules Allows for monkey patching (under ES5 systems). This somewhat mirrors the proposed behavior in https://github.com/square/es6-module-transpiler/issues/37 but applies the behavior via manual code changes rather than compiler support. --- lib/handlebars/base.js | 13 ++++++------- lib/handlebars/runtime.js | 9 +++++---- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/lib/handlebars/base.js b/lib/handlebars/base.js index 6d19a33b6..e33c0eb32 100644 --- a/lib/handlebars/base.js +++ b/lib/handlebars/base.js @@ -1,6 +1,5 @@ -/*jshint eqnull: true */ - -import { extend, isEmpty } from "./utils"; +/*globals Exception, Utils */ +module Utils from "./Utils"; import Exception from "./exception"; export var VERSION = "1.0.0"; @@ -48,7 +47,7 @@ HandlebarsEnvironment.prototype = { registerHelper: function(name, fn, inverse) { if (toString.call(name) === objectType) { if (inverse || fn) { throw new Exception('Arg not supported with multiple helpers'); } - extend(this.helpers, name); + Utils.extend(this.helpers, name); } else { if (inverse) { fn.not = inverse; } this.helpers[name] = fn; @@ -57,7 +56,7 @@ HandlebarsEnvironment.prototype = { registerPartial: function(name, str) { if (toString.call(name) === objectType) { - extend(this.partials, name); + Utils.extend(this.partials, name); } else { this.partials[name] = str; } @@ -148,7 +147,7 @@ function registerDefaultHelpers(instance) { instance.registerHelper('with', function(context, options) { if (isFunction(context)) { context = context.call(this); } - if (!isEmpty(context)) return options.fn(context); + if (!Utils.isEmpty(context)) return options.fn(context); }); instance.registerHelper('log', function(context, options) { @@ -182,6 +181,6 @@ export function log(level, obj) { logger.log(level, obj); } export var createFrame = function(object) { var obj = {}; - extend(obj, object); + Utils.extend(obj, object); return obj; }; diff --git a/lib/handlebars/runtime.js b/lib/handlebars/runtime.js index 57a830ba8..fe5fef4ab 100644 --- a/lib/handlebars/runtime.js +++ b/lib/handlebars/runtime.js @@ -1,5 +1,6 @@ +/*global Utils */ +module Utils from "./utils"; import Exception from "./exception"; -import { escapeExpression, extend } from "./utils"; import { COMPILER_REVISION, REVISION_CHANGES } from "./base"; function checkRevision(compilerInfo) { @@ -49,7 +50,7 @@ export function template(templateSpec, env) { // Just add water var container = { - escapeExpression: escapeExpression, + escapeExpression: Utils.escapeExpression, invokePartial: invokePartialWrapper, programs: [], program: function(i, fn, data) { @@ -66,8 +67,8 @@ export function template(templateSpec, env) { if (param && common && (param !== common)) { ret = {}; - extend(ret, common); - extend(ret, param); + Utils.extend(ret, common); + Utils.extend(ret, param); } return ret; }, From 64de5c681273969c11bf36d1b63cbd3b726d7954 Mon Sep 17 00:00:00 2001 From: kpdecker Date: Mon, 14 Oct 2013 12:15:33 -0500 Subject: [PATCH 162/200] Improve packager error tracking --- Gruntfile.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Gruntfile.js b/Gruntfile.js index 8687bbace..a316715c4 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -118,7 +118,7 @@ module.exports = function(grunt) { grunt.registerTask('packager-fork', function() { // Allows us to run the packager task out of process to work around the multiple // traceur exec issues - grunt.util.spawn({grunt: true, args: ['packager']}, this.async()); + grunt.util.spawn({grunt: true, args: ['--stack', 'packager'], opts: {stdio: 'inherit'}}, this.async()); }); grunt.registerTask('test', function() { var done = this.async(); From 15e1eb36cad9bafcb0083d7598210e15ddd0b2be Mon Sep 17 00:00:00 2001 From: kpdecker Date: Mon, 14 Oct 2013 13:05:51 -0500 Subject: [PATCH 163/200] Fix case typo --- lib/handlebars/base.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/handlebars/base.js b/lib/handlebars/base.js index e33c0eb32..24ab53221 100644 --- a/lib/handlebars/base.js +++ b/lib/handlebars/base.js @@ -1,5 +1,5 @@ /*globals Exception, Utils */ -module Utils from "./Utils"; +module Utils from "./utils"; import Exception from "./exception"; export var VERSION = "1.0.0"; From 97d5efd94aacd3c3dfc5ed4db0db77ea312c0985 Mon Sep 17 00:00:00 2001 From: kpdecker Date: Mon, 14 Oct 2013 14:38:40 -0500 Subject: [PATCH 164/200] Remove prepublish command This caused breakages with the npm install/link commands. --- package.json | 1 - 1 file changed, 1 deletion(-) diff --git a/package.json b/package.json index 2dae1ec4b..844199647 100644 --- a/package.json +++ b/package.json @@ -54,7 +54,6 @@ "handlebars": "bin/handlebars" }, "scripts": { - "prepublish": "grunt", "test": "node ./spec/env/runner" } } From 9218f2e1ef88f5a6042f9e0bdb0c3c45922a32bf Mon Sep 17 00:00:00 2001 From: kpdecker Date: Mon, 14 Oct 2013 14:49:55 -0500 Subject: [PATCH 165/200] Fix improper stash --- lib/handlebars/base.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/handlebars/base.js b/lib/handlebars/base.js index 24ab53221..a1b346104 100644 --- a/lib/handlebars/base.js +++ b/lib/handlebars/base.js @@ -133,7 +133,7 @@ function registerDefaultHelpers(instance) { instance.registerHelper('if', function(conditional, options) { if (isFunction(conditional)) { conditional = conditional.call(this); } - if (isEmpty(conditional)) { + if (Utils.isEmpty(conditional)) { return options.inverse(this); } else { return options.fn(this); From 4fda71364ede88eaff126ac3eeb18496f10f8725 Mon Sep 17 00:00:00 2001 From: kpdecker Date: Mon, 14 Oct 2013 21:23:06 -0500 Subject: [PATCH 166/200] Pass options hash to context function exec Fixes #599 --- lib/handlebars/compiler/javascript-compiler.js | 2 +- release-notes.md | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/handlebars/compiler/javascript-compiler.js b/lib/handlebars/compiler/javascript-compiler.js index a81a99c2a..b63137207 100644 --- a/lib/handlebars/compiler/javascript-compiler.js +++ b/lib/handlebars/compiler/javascript-compiler.js @@ -499,7 +499,7 @@ JavaScriptCompiler.prototype = { var nextStack = this.nextStack(); this.source.push('if (' + nextStack + ' = ' + helperName + ') { ' + nextStack + ' = ' + nextStack + '.call(' + helper.callParams + '); }'); - this.source.push('else { ' + nextStack + ' = ' + nonHelper + '; ' + nextStack + ' = typeof ' + nextStack + ' === functionType ? ' + nextStack + '.apply(depth0) : ' + nextStack + '; }'); + this.source.push('else { ' + nextStack + ' = ' + nonHelper + '; ' + nextStack + ' = typeof ' + nextStack + ' === functionType ? ' + nextStack + '.call(depth0, options) : ' + nextStack + '; }'); }, // [invokePartial] diff --git a/release-notes.md b/release-notes.md index 786761bc3..774001b4e 100644 --- a/release-notes.md +++ b/release-notes.md @@ -12,6 +12,7 @@ Compatibility notes: - AMD: Users may load the bundled `handlebars.amd.js` or `handlebars.runtime.amd.js` files or load individual modules directly. AMD users should also note that the handlebars object is exposed via the `default` field on the imported object. This [gist](https://gist.github.com/wycats/7417be0dc361a69d5916) provides some discussion of possible compatibility shims. - CommonJS/Node: Node loading occurs as normal via `require` - Globals: The `handlebars.js` and `handlebars.runtime.js` files should behave in the same manner as the v1.0.12 / 1.0.0 release. +- Context-stored helpers are now always passed the `options` hash. Previously no-argument helpers did not have this argument. [Commits](https://github.com/wycats/handlebars.js/compare/v1.0.12...master) From 84049bf0c5576727e9d6a0880abb123454d28861 Mon Sep 17 00:00:00 2001 From: kpdecker Date: Mon, 14 Oct 2013 21:36:46 -0500 Subject: [PATCH 167/200] Add includeZero flag to if conditional Allows for users who desire non-falsy handling of numbers to utilize if while maintaining the legacy if behavior. Fixes #608 --- lib/handlebars/base.js | 7 +++++-- spec/builtins.js | 7 +++++-- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/lib/handlebars/base.js b/lib/handlebars/base.js index a1b346104..dcf446b78 100644 --- a/lib/handlebars/base.js +++ b/lib/handlebars/base.js @@ -133,7 +133,10 @@ function registerDefaultHelpers(instance) { instance.registerHelper('if', function(conditional, options) { if (isFunction(conditional)) { conditional = conditional.call(this); } - if (Utils.isEmpty(conditional)) { + // Default behavior is to render the positive path if the value is truthy and not empty. + // The `includeZero` option may be set to treat the condtional as purely not empty based on the + // behavior of isEmpty. Effectively this determines if 0 is handled by the positive path or negative. + if ((!options.hash.includeZero && !conditional) || Utils.isEmpty(conditional)) { return options.inverse(this); } else { return options.fn(this); @@ -141,7 +144,7 @@ function registerDefaultHelpers(instance) { }); instance.registerHelper('unless', function(conditional, options) { - return instance.helpers['if'].call(this, conditional, {fn: options.inverse, inverse: options.fn}); + return instance.helpers['if'].call(this, conditional, {fn: options.inverse, inverse: options.fn, hash: options.hash}); }); instance.registerHelper('with', function(context, options) { diff --git a/spec/builtins.js b/spec/builtins.js index 140f5f510..401e789a9 100644 --- a/spec/builtins.js +++ b/spec/builtins.js @@ -15,8 +15,11 @@ describe('builtin helpers', function() { "if with non-empty array shows the contents"); shouldCompileTo(string, {goodbye: [], world: "world"}, "cruel world!", "if with empty array does not show the contents"); - shouldCompileTo(string, {goodbye: 0, world: "world"}, "GOODBYE cruel world!", - "if with zero does show the contents"); + shouldCompileTo(string, {goodbye: 0, world: "world"}, "cruel world!", + "if with zero does not show the contents"); + shouldCompileTo("{{#if goodbye includeZero=true}}GOODBYE {{/if}}cruel {{world}}!", + {goodbye: 0, world: "world"}, "GOODBYE cruel world!", + "if with zero does not show the contents"); }); it("if with function argument", function() { From 6f94fc0a3be36bd32babd70680bfcb1195dafc30 Mon Sep 17 00:00:00 2001 From: kpdecker Date: Mon, 14 Oct 2013 22:49:23 -0500 Subject: [PATCH 168/200] Reduce eval scope in test env --- spec/env/browser.js | 7 ++++++- spec/env/node.js | 7 ++++++- spec/env/runtime.js | 7 ++++++- 3 files changed, 18 insertions(+), 3 deletions(-) diff --git a/spec/env/browser.js b/spec/env/browser.js index e6f81026a..9f69e74d7 100644 --- a/spec/env/browser.js +++ b/spec/env/browser.js @@ -1,3 +1,4 @@ +/*global handlebarsEnv */ require('./common'); var _ = require('underscore'), @@ -10,9 +11,13 @@ vm.runInThisContext(fs.readFileSync(__dirname + '/../../dist/handlebars.js'), 'd global.CompilerContext = { compile: function(template, options) { var templateSpec = handlebarsEnv.precompile(template, options); - return handlebarsEnv.template(eval('(' + templateSpec + ')')); + return handlebarsEnv.template(safeEval(templateSpec)); }, compileWithPartial: function(template, options) { return handlebarsEnv.compile(template, options); } }; + +function safeEval(templateSpec) { + return eval('(' + templateSpec + ')'); +} diff --git a/spec/env/node.js b/spec/env/node.js index aed2d54af..808c07ef4 100644 --- a/spec/env/node.js +++ b/spec/env/node.js @@ -1,3 +1,4 @@ +/*global handlebarsEnv */ require('./common'); global.Handlebars = require('../../lib'); @@ -5,9 +6,13 @@ global.Handlebars = require('../../lib'); global.CompilerContext = { compile: function(template, options) { var templateSpec = handlebarsEnv.precompile(template, options); - return handlebarsEnv.template(eval('(' + templateSpec + ')')); + return handlebarsEnv.template(safeEval(templateSpec)); }, compileWithPartial: function(template, options) { return handlebarsEnv.compile(template, options); } }; + +function safeEval(templateSpec) { + return eval('(' + templateSpec + ')'); +} diff --git a/spec/env/runtime.js b/spec/env/runtime.js index fe5d4cac4..68e40b058 100644 --- a/spec/env/runtime.js +++ b/spec/env/runtime.js @@ -1,3 +1,4 @@ +/*global handlebarsEnv */ require('./common'); var _ = require('underscore'), @@ -12,7 +13,7 @@ var compiler = require('../../dist/cjs/handlebars/compiler/compiler'); global.CompilerContext = { compile: function(template, options) { var templateSpec = compiler.precompile(template, options); - return handlebarsEnv.template(eval('(' + templateSpec + ')')); + return handlebarsEnv.template(safeEval(templateSpec)); }, compileWithPartial: function(template, options) { // Hack the compiler on to the environment for these specific tests @@ -22,3 +23,7 @@ global.CompilerContext = { return handlebarsEnv.compile(template, options); } }; + +function safeEval(templateSpec) { + return eval('(' + templateSpec + ')'); +} From 06d94fed56b43bdf0c824bdce966596e551d3324 Mon Sep 17 00:00:00 2001 From: kpdecker Date: Mon, 14 Oct 2013 22:49:34 -0500 Subject: [PATCH 169/200] Fix argument not found error --- lib/handlebars/compiler/javascript-compiler.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/handlebars/compiler/javascript-compiler.js b/lib/handlebars/compiler/javascript-compiler.js index b63137207..283c20c44 100644 --- a/lib/handlebars/compiler/javascript-compiler.js +++ b/lib/handlebars/compiler/javascript-compiler.js @@ -499,7 +499,7 @@ JavaScriptCompiler.prototype = { var nextStack = this.nextStack(); this.source.push('if (' + nextStack + ' = ' + helperName + ') { ' + nextStack + ' = ' + nextStack + '.call(' + helper.callParams + '); }'); - this.source.push('else { ' + nextStack + ' = ' + nonHelper + '; ' + nextStack + ' = typeof ' + nextStack + ' === functionType ? ' + nextStack + '.call(depth0, options) : ' + nextStack + '; }'); + this.source.push('else { ' + nextStack + ' = ' + nonHelper + '; ' + nextStack + ' = typeof ' + nextStack + ' === functionType ? ' + nextStack + '.call(' + helper.callParams + ') : ' + nextStack + '; }'); }, // [invokePartial] From 0cc6e270f971f6ad7e916bfab82e889419affa27 Mon Sep 17 00:00:00 2001 From: kpdecker Date: Mon, 29 Jul 2013 23:33:56 -0500 Subject: [PATCH 170/200] Pass open token to MustacheNode for flag parsing --- lib/handlebars/compiler/ast.js | 5 +++-- src/handlebars.yy | 13 ++++++------- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/lib/handlebars/compiler/ast.js b/lib/handlebars/compiler/ast.js index 336492d96..db7063c99 100644 --- a/lib/handlebars/compiler/ast.js +++ b/lib/handlebars/compiler/ast.js @@ -6,11 +6,12 @@ export function ProgramNode(statements, inverse) { if(inverse) { this.inverse = new ProgramNode(inverse); } } -export function MustacheNode(rawParams, hash, unescaped) { +export function MustacheNode(rawParams, hash, open) { this.type = "mustache"; - this.escaped = !unescaped; this.hash = hash; + this.escaped = open[2] !== '{' && open[2] !== '&'; + var id = this.id = rawParams[0]; var params = this.params = rawParams.slice(1); diff --git a/src/handlebars.yy b/src/handlebars.yy index d2f24c45d..96a502962 100644 --- a/src/handlebars.yy +++ b/src/handlebars.yy @@ -32,11 +32,11 @@ statement ; openBlock - : OPEN_BLOCK inMustache CLOSE -> new yy.MustacheNode($2[0], $2[1]) + : OPEN_BLOCK inMustache CLOSE -> new yy.MustacheNode($2[0], $2[1], $1) ; openInverse - : OPEN_INVERSE inMustache CLOSE -> new yy.MustacheNode($2[0], $2[1]) + : OPEN_INVERSE inMustache CLOSE -> new yy.MustacheNode($2[0], $2[1], $1) ; closeBlock @@ -44,11 +44,10 @@ closeBlock ; mustache - : OPEN inMustache CLOSE { - // Parsing out the '&' escape token at this level saves ~500 bytes after min due to the removal of one parser node. - $$ = new yy.MustacheNode($2[0], $2[1], $1[2] === '&'); - } - | OPEN_UNESCAPED inMustache CLOSE_UNESCAPED -> new yy.MustacheNode($2[0], $2[1], true) + // Parsing out the '&' escape token at AST level saves ~500 bytes after min due to the removal of one parser node. + // This also allows for handler unification as all mustache node instances can utilize the same handler + : OPEN inMustache CLOSE -> new yy.MustacheNode($2[0], $2[1], $1) + | OPEN_UNESCAPED inMustache CLOSE_UNESCAPED -> new yy.MustacheNode($2[0], $2[1], $1) ; From 4cf49732a232c64b752097e8ef3f0faf1fa574ed Mon Sep 17 00:00:00 2001 From: kpdecker Date: Mon, 29 Jul 2013 23:51:05 -0500 Subject: [PATCH 171/200] Push Source helper method --- .../compiler/javascript-compiler.js | 26 +++++++++++-------- 1 file changed, 15 insertions(+), 11 deletions(-) diff --git a/lib/handlebars/compiler/javascript-compiler.js b/lib/handlebars/compiler/javascript-compiler.js index 283c20c44..e3eddd968 100644 --- a/lib/handlebars/compiler/javascript-compiler.js +++ b/lib/handlebars/compiler/javascript-compiler.js @@ -141,7 +141,7 @@ JavaScriptCompiler.prototype = { } if (!this.environment.isSimple) { - this.source.push("return buffer;"); + this.pushSource("return buffer;"); } var params = this.isChild ? ["depth0", "data"] : ["Handlebars", "depth0", "helpers", "partials", "data"]; @@ -232,7 +232,7 @@ JavaScriptCompiler.prototype = { // Use the options value generated from the invocation params[params.length-1] = 'options'; - this.source.push("if (!" + this.lastHelper + ") { " + current + " = blockHelperMissing.call(" + params.join(", ") + "); }"); + this.pushSource("if (!" + this.lastHelper + ") { " + current + " = blockHelperMissing.call(" + params.join(", ") + "); }"); }, // [appendContent] @@ -242,7 +242,7 @@ JavaScriptCompiler.prototype = { // // Appends the string value of `content` to the current buffer appendContent: function(content) { - this.source.push(this.appendToBuffer(this.quotedString(content))); + this.pushSource(this.appendToBuffer(this.quotedString(content))); }, // [append] @@ -259,9 +259,9 @@ JavaScriptCompiler.prototype = { // when we examine local this.flushInline(); var local = this.popStack(); - this.source.push("if(" + local + " || " + local + " === 0) { " + this.appendToBuffer(local) + " }"); + this.pushSource("if(" + local + " || " + local + " === 0) { " + this.appendToBuffer(local) + " }"); if (this.environment.isSimple) { - this.source.push("else { " + this.appendToBuffer("''") + " }"); + this.pushSource("else { " + this.appendToBuffer("''") + " }"); } }, @@ -274,7 +274,7 @@ JavaScriptCompiler.prototype = { appendEscaped: function() { this.context.aliases.escapeExpression = 'this.escapeExpression'; - this.source.push(this.appendToBuffer("escapeExpression(" + this.popStack() + ")")); + this.pushSource(this.appendToBuffer("escapeExpression(" + this.popStack() + ")")); }, // [getContext] @@ -498,8 +498,8 @@ JavaScriptCompiler.prototype = { var nonHelper = this.nameLookup('depth' + this.lastContext, name, 'context'); var nextStack = this.nextStack(); - this.source.push('if (' + nextStack + ' = ' + helperName + ') { ' + nextStack + ' = ' + nextStack + '.call(' + helper.callParams + '); }'); - this.source.push('else { ' + nextStack + ' = ' + nonHelper + '; ' + nextStack + ' = typeof ' + nextStack + ' === functionType ? ' + nextStack + '.call(' + helper.callParams + ') : ' + nextStack + '; }'); + this.pushSource('if (' + nextStack + ' = ' + helperName + ') { ' + nextStack + ' = ' + nextStack + '.call(' + helper.callParams + '); }'); + this.pushSource('else { ' + nextStack + ' = ' + nonHelper + '; ' + nextStack + ' = typeof ' + nextStack + ' === functionType ? ' + nextStack + '.call(' + helper.callParams + ') : ' + nextStack + '; }'); }, // [invokePartial] @@ -606,7 +606,7 @@ JavaScriptCompiler.prototype = { register: function(name, val) { this.useRegister(name); - this.source.push(name + " = " + val + ";"); + this.pushSource(name + " = " + val + ";"); }, useRegister: function(name) { @@ -620,12 +620,16 @@ JavaScriptCompiler.prototype = { return this.push(new Literal(item)); }, + pushSource: function(source) { + this.source.push(source); + }, + pushStack: function(item) { this.flushInline(); var stack = this.incrStack(); if (item) { - this.source.push(stack + " = " + item + ";"); + this.pushSource(stack + " = " + item + ";"); } this.compileStack.push(stack); return stack; @@ -668,7 +672,7 @@ JavaScriptCompiler.prototype = { stack = this.nextStack(); } - this.source.push(stack + " = (" + prefix + item + ");"); + this.pushSource(stack + " = (" + prefix + item + ");"); } return stack; }, From 30384789d261159985f1582934ed28e954b84836 Mon Sep 17 00:00:00 2001 From: kpdecker Date: Tue, 30 Jul 2013 01:09:30 -0500 Subject: [PATCH 172/200] Lookahead control classes --- src/handlebars.l | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/src/handlebars.l b/src/handlebars.l index 7593189ef..cc190b196 100644 --- a/src/handlebars.l +++ b/src/handlebars.l @@ -10,6 +10,9 @@ function strip(start, end) { %} +LOOKAHEAD [=}\s\/.] +LITERAL_LOOKAHEAD [}\s] + /* ID is the inverse of control characters. Control characters ranges: @@ -19,7 +22,7 @@ Control characters ranges: [\[-\^`] [, \, ], ^, `, Exceptions in range: _ [\{-~] {, |, }, ~ */ -ID [^\s!"#%-,\.\/;->@\[-\^`\{-~]+/[=}\s\/.] +ID [^\s!"#%-,\.\/;->@\[-\^`\{-~]+/{LOOKAHEAD} %% @@ -58,8 +61,8 @@ ID [^\s!"#%-,\.\/;->@\[-\^`\{-~]+/[=}\s\/.] "{{" return 'OPEN'; "=" return 'EQUALS'; -"."/[}\/ ] return 'ID'; ".." return 'ID'; +"."/{LOOKAHEAD} return 'ID'; [\/.] return 'SEP'; \s+ /*ignore whitespace*/ "}}}" this.popState(); return 'CLOSE_UNESCAPED'; @@ -67,9 +70,9 @@ ID [^\s!"#%-,\.\/;->@\[-\^`\{-~]+/[=}\s\/.] '"'("\\"["]|[^"])*'"' yytext = strip(1,2).replace(/\\"/g,'"'); return 'STRING'; "'"("\\"[']|[^'])*"'" yytext = strip(1,2).replace(/\\'/g,"'"); return 'STRING'; "@" return 'DATA'; -"true"/[}\s] return 'BOOLEAN'; -"false"/[}\s] return 'BOOLEAN'; -\-?[0-9]+/[}\s] return 'INTEGER'; +"true"/{LITERAL_LOOKAHEAD} return 'BOOLEAN'; +"false"/{LITERAL_LOOKAHEAD} return 'BOOLEAN'; +\-?[0-9]+/{LITERAL_LOOKAHEAD} return 'INTEGER'; {ID} return 'ID'; From 5b483ff78decee27c221fabdbccffec614cedadd Mon Sep 17 00:00:00 2001 From: kpdecker Date: Tue, 30 Jul 2013 02:53:07 -0500 Subject: [PATCH 173/200] Define test cases --- spec/whitespace-control.js | 62 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 62 insertions(+) create mode 100644 spec/whitespace-control.js diff --git a/spec/whitespace-control.js b/spec/whitespace-control.js new file mode 100644 index 000000000..3a2711ddd --- /dev/null +++ b/spec/whitespace-control.js @@ -0,0 +1,62 @@ +describe('whitespace control', function() { + it('should strip whitespace around mustache calls', function() { + var hash = {foo: 'bar<'}; + + shouldCompileTo(' {{(foo)}} ', hash, 'bar<'); + shouldCompileTo(' {{(foo}} ', hash, 'bar< '); + shouldCompileTo(' {{foo)}} ', hash, ' bar<'); + + shouldCompileTo(' {{(&foo)}} ', hash, 'bar<'); + shouldCompileTo(' {{({foo})}} ', hash, 'bar<'); + }); + + describe('blocks', function() { + it('should strip whitespace around simple block calls', function() { + var hash = {foo: 'bar<'}; + + shouldCompileTo(' {{(#if foo)}} bar {{(/if)}} ', hash, 'bar'); + shouldCompileTo(' {{#if foo)}} bar {{/if)}} ', hash, ' bar '); + shouldCompileTo(' {{(#if foo}} bar {{(/if}} ', hash, ' bar '); + shouldCompileTo(' {{#if foo}} bar {{/if}} ', hash, ' bar '); + }); + it('should strip whitespace around inverse block calls', function() { + var hash = {}; + + shouldCompileTo(' {{(^if foo)}} bar {{(/if)}} ', hash, 'bar'); + shouldCompileTo(' {{^if foo)}} bar {{/if)}} ', hash, ' bar '); + shouldCompileTo(' {{(^if foo}} bar {{(/if}} ', hash, ' bar '); + shouldCompileTo(' {{^if foo}} bar {{/if}} ', hash, ' bar '); + }); + it('should strip whitespace around complex block calls', function() { + var hash = {foo: 'bar<'}; + + shouldCompileTo('{{#if foo)}} bar {{(^)}} baz {{(/if}}', hash, 'bar'); + shouldCompileTo('{{#if foo)}} bar {{^)}} baz {{/if}}', hash, 'bar '); + shouldCompileTo('{{#if foo}} bar {{(^)}} baz {{(/if}}', hash, ' bar'); + shouldCompileTo('{{#if foo}} bar {{^)}} baz {{/if}}', hash, ' bar '); + + shouldCompileTo('{{#if foo)}} bar {{(else)}} baz {{(/if}}', hash, 'bar'); + + hash = {}; + + shouldCompileTo('{{#if foo)}} bar {{(^)}} baz {{(/if}}', hash, 'baz'); + shouldCompileTo('{{#if foo}} bar {{(^)}} baz {{/if}}', hash, 'baz '); + shouldCompileTo('{{#if foo)}} bar {{(^}} baz {{(/if}}', hash, ' baz'); + shouldCompileTo('{{#if foo)}} bar {{(^}} baz {{/if}}', hash, ' baz '); + + shouldCompileTo('{{#if foo)}} bar {{(else)}} baz {{(/if}}', hash, 'baz'); + }); + }); + + it('should strip whitespace around partials', function() { + shouldCompileToWithPartials('foo {{(> dude)}} ', [{}, {}, {dude: 'bar'}], true, 'foobar'); + shouldCompileToWithPartials('foo {{> dude)}} ', [{}, {}, {dude: 'bar'}], true, 'foo bar'); + shouldCompileToWithPartials('foo {{> dude}} ', [{}, {}, {dude: 'bar'}], true, 'foo bar '); + }); + + it('should only strip whitespace once', function() { + var hash = {foo: 'bar'}; + + shouldCompileTo(' {{(foo)}} {{foo}} {{foo}} ', hash, 'barbar bar '); + }); +}); From a1edab6ef578d4dacb7811c8587306194fa9d54a Mon Sep 17 00:00:00 2001 From: kpdecker Date: Tue, 30 Jul 2013 03:20:19 -0500 Subject: [PATCH 174/200] Kill unused functions --- lib/handlebars/compiler/javascript-compiler.js | 9 --------- 1 file changed, 9 deletions(-) diff --git a/lib/handlebars/compiler/javascript-compiler.js b/lib/handlebars/compiler/javascript-compiler.js index e3eddd968..d90f0b1d4 100644 --- a/lib/handlebars/compiler/javascript-compiler.js +++ b/lib/handlebars/compiler/javascript-compiler.js @@ -80,15 +80,6 @@ JavaScriptCompiler.prototype = { return this.createFunctionContext(asObject); }, - nextOpcode: function() { - var opcodes = this.environment.opcodes; - return opcodes[this.i + 1]; - }, - - eat: function() { - this.i = this.i + 1; - }, - preamble: function() { var out = []; From 14ef86f9ca5717033499e69ad47f5a2847dd1261 Mon Sep 17 00:00:00 2001 From: kpdecker Date: Tue, 30 Jul 2013 10:24:09 -0500 Subject: [PATCH 175/200] Use accept for all compiler traversal --- lib/handlebars/compiler/compiler.js | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/lib/handlebars/compiler/compiler.js b/lib/handlebars/compiler/compiler.js index 50195e3ab..5b782c47c 100644 --- a/lib/handlebars/compiler/compiler.js +++ b/lib/handlebars/compiler/compiler.js @@ -72,6 +72,7 @@ Compiler.prototype = { guid: 0, compile: function(program, options) { + this.opcodes = []; this.children = []; this.depths = {list: []}; this.options = options; @@ -93,7 +94,7 @@ Compiler.prototype = { } } - return this.program(program); + return this.accept(program); }, accept: function(node) { @@ -102,11 +103,9 @@ Compiler.prototype = { program: function(program) { var statements = program.statements, statement; - this.opcodes = []; for(var i=0, l=statements.length; i Date: Tue, 30 Jul 2013 10:25:05 -0500 Subject: [PATCH 176/200] Fix whitespace --- src/handlebars.l | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/handlebars.l b/src/handlebars.l index cc190b196..546852d97 100644 --- a/src/handlebars.l +++ b/src/handlebars.l @@ -62,7 +62,7 @@ ID [^\s!"#%-,\.\/;->@\[-\^`\{-~]+/{LOOKAHEAD} "=" return 'EQUALS'; ".." return 'ID'; -"."/{LOOKAHEAD} return 'ID'; +"."/{LOOKAHEAD} return 'ID'; [\/.] return 'SEP'; \s+ /*ignore whitespace*/ "}}}" this.popState(); return 'CLOSE_UNESCAPED'; From 4e4920cc78b1ad68ca325d9677b2fb8f3dfbaa3a Mon Sep 17 00:00:00 2001 From: kpdecker Date: Tue, 30 Jul 2013 10:25:41 -0500 Subject: [PATCH 177/200] Add strip token parsing to lexer --- src/handlebars.l | 26 ++++++++++++++------------ 1 file changed, 14 insertions(+), 12 deletions(-) diff --git a/src/handlebars.l b/src/handlebars.l index 546852d97..740015be5 100644 --- a/src/handlebars.l +++ b/src/handlebars.l @@ -9,9 +9,11 @@ function strip(start, end) { %} +LEFT_STRIP "(" +RIGHT_STRIP ")" -LOOKAHEAD [=}\s\/.] -LITERAL_LOOKAHEAD [}\s] +LOOKAHEAD [=)}\s\/.] +LITERAL_LOOKAHEAD [)}\s] /* ID is the inverse of control characters. @@ -49,24 +51,24 @@ ID [^\s!"#%-,\.\/;->@\[-\^`\{-~]+/{LOOKAHEAD} [\s\S]*?"--}}" strip(0,4); this.popState(); return 'COMMENT'; -"{{>" return 'OPEN_PARTIAL'; -"{{#" return 'OPEN_BLOCK'; -"{{/" return 'OPEN_ENDBLOCK'; -"{{^" return 'OPEN_INVERSE'; -"{{"\s*"else" return 'OPEN_INVERSE'; -"{{{" return 'OPEN_UNESCAPED'; -"{{&" return 'OPEN'; +"{{"{LEFT_STRIP}?">" return 'OPEN_PARTIAL'; +"{{"{LEFT_STRIP}?"#" return 'OPEN_BLOCK'; +"{{"{LEFT_STRIP}?"/" return 'OPEN_ENDBLOCK'; +"{{"{LEFT_STRIP}?"^" return 'OPEN_INVERSE'; +"{{"{LEFT_STRIP}?\s*"else" return 'OPEN_INVERSE'; +"{{"{LEFT_STRIP}?"{" return 'OPEN_UNESCAPED'; +"{{"{LEFT_STRIP}?"&" return 'OPEN'; "{{!--" this.popState(); this.begin('com'); "{{!"[\s\S]*?"}}" strip(3,5); this.popState(); return 'COMMENT'; -"{{" return 'OPEN'; +"{{"{LEFT_STRIP}? return 'OPEN'; "=" return 'EQUALS'; ".." return 'ID'; "."/{LOOKAHEAD} return 'ID'; [\/.] return 'SEP'; \s+ /*ignore whitespace*/ -"}}}" this.popState(); return 'CLOSE_UNESCAPED'; -"}}" this.popState(); return 'CLOSE'; +"}"{RIGHT_STRIP}?"}}" this.popState(); return 'CLOSE_UNESCAPED'; +{RIGHT_STRIP}?"}}" this.popState(); return 'CLOSE'; '"'("\\"["]|[^"])*'"' yytext = strip(1,2).replace(/\\"/g,'"'); return 'STRING'; "'"("\\"[']|[^'])*"'" yytext = strip(1,2).replace(/\\'/g,"'"); return 'STRING'; "@" return 'DATA'; From 151e6d49c05dd93305a6ee1cad57154a01396ea0 Mon Sep 17 00:00:00 2001 From: kpdecker Date: Tue, 30 Jul 2013 10:56:58 -0500 Subject: [PATCH 178/200] Wrap AST nodes in strip opcodes if defined --- lib/handlebars/compiler/compiler.js | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/lib/handlebars/compiler/compiler.js b/lib/handlebars/compiler/compiler.js index 5b782c47c..4f232ebce 100644 --- a/lib/handlebars/compiler/compiler.js +++ b/lib/handlebars/compiler/compiler.js @@ -98,11 +98,23 @@ Compiler.prototype = { }, accept: function(node) { - return this[node.type](node); + var strip = node.strip || {}, + ret; + if (strip.left) { + this.opcode('strip'); + } + + ret = this[node.type](node); + + if (strip.right) { + this.opcode('strip'); + } + + return ret; }, program: function(program) { - var statements = program.statements, statement; + var statements = program.statements; for(var i=0, l=statements.length; i Date: Tue, 30 Jul 2013 11:03:12 -0500 Subject: [PATCH 179/200] Load strip flags from lex stream --- lib/handlebars/compiler/ast.js | 34 ++++++++++++++++++++++++++-------- src/handlebars.yy | 31 +++++++++++++++++++++---------- 2 files changed, 47 insertions(+), 18 deletions(-) diff --git a/lib/handlebars/compiler/ast.js b/lib/handlebars/compiler/ast.js index db7063c99..f6229e299 100644 --- a/lib/handlebars/compiler/ast.js +++ b/lib/handlebars/compiler/ast.js @@ -1,16 +1,25 @@ import Exception from "../exception"; -export function ProgramNode(statements, inverse) { +export function ProgramNode(statements, inverseStrip, inverse) { this.type = "program"; this.statements = statements; - if(inverse) { this.inverse = new ProgramNode(inverse); } + this.strip = {}; + + if(inverse) { + this.inverse = new ProgramNode(inverse, inverseStrip); + this.strip.right = inverseStrip.left; + } else if (inverseStrip) { + this.strip.left = inverseStrip.right; + } } -export function MustacheNode(rawParams, hash, open) { +export function MustacheNode(rawParams, hash, open, strip) { this.type = "mustache"; this.hash = hash; + this.strip = strip; - this.escaped = open[2] !== '{' && open[2] !== '&'; + var escapeFlag = open[3] || open[2]; + this.escaped = escapeFlag !== '{' && escapeFlag !== '&'; var id = this.id = rawParams[0]; var params = this.params = rawParams.slice(1); @@ -29,15 +38,16 @@ export function MustacheNode(rawParams, hash, open) { // pass or at runtime. } -export function PartialNode(partialName, context) { +export function PartialNode(partialName, context, strip) { this.type = "partial"; this.partialName = partialName; this.context = context; + this.strip = strip; } export function BlockNode(mustache, program, inverse, close) { - if(mustache.id.original !== close.original) { - throw new Exception(mustache.id.original + " doesn't match " + close.original); + if(mustache.id.original !== close.path.original) { + throw new Exception(mustache.id.original + " doesn't match " + close.path.original); } this.type = "block"; @@ -45,7 +55,15 @@ export function BlockNode(mustache, program, inverse, close) { this.program = program; this.inverse = inverse; - if (this.inverse && !this.program) { + this.strip = { + left: mustache.strip.left, + right: close.strip.right + }; + + (program || inverse).strip.left = mustache.strip.right; + (inverse || program).strip.right = close.strip.left; + + if (inverse && !program) { this.isInverse = true; } } diff --git a/src/handlebars.yy b/src/handlebars.yy index 96a502962..6ee220775 100644 --- a/src/handlebars.yy +++ b/src/handlebars.yy @@ -2,6 +2,17 @@ %ebnf +%{ + +function stripFlags(open, close) { + return { + left: open[2] === '(', + right: close[0] === ')' || close[1] === ')' + }; +} + +%} + %% root @@ -9,9 +20,9 @@ root ; program - : simpleInverse statements -> new yy.ProgramNode([], $2) - | statements simpleInverse statements -> new yy.ProgramNode($1, $3) - | statements simpleInverse -> new yy.ProgramNode($1, []) + : simpleInverse statements -> new yy.ProgramNode([], $1, $2) + | statements simpleInverse statements -> new yy.ProgramNode($1, $2, $3) + | statements simpleInverse -> new yy.ProgramNode($1, $2, []) | statements -> new yy.ProgramNode($1) | simpleInverse -> new yy.ProgramNode([]) | "" -> new yy.ProgramNode([]) @@ -32,31 +43,31 @@ statement ; openBlock - : OPEN_BLOCK inMustache CLOSE -> new yy.MustacheNode($2[0], $2[1], $1) + : OPEN_BLOCK inMustache CLOSE -> new yy.MustacheNode($2[0], $2[1], $1, stripFlags($1, $3)) ; openInverse - : OPEN_INVERSE inMustache CLOSE -> new yy.MustacheNode($2[0], $2[1], $1) + : OPEN_INVERSE inMustache CLOSE -> new yy.MustacheNode($2[0], $2[1], $1, stripFlags($1, $3)) ; closeBlock - : OPEN_ENDBLOCK path CLOSE -> $2 + : OPEN_ENDBLOCK path CLOSE -> {path: $2, strip: stripFlags($1, $3)} ; mustache // Parsing out the '&' escape token at AST level saves ~500 bytes after min due to the removal of one parser node. // This also allows for handler unification as all mustache node instances can utilize the same handler - : OPEN inMustache CLOSE -> new yy.MustacheNode($2[0], $2[1], $1) - | OPEN_UNESCAPED inMustache CLOSE_UNESCAPED -> new yy.MustacheNode($2[0], $2[1], $1) + : OPEN inMustache CLOSE -> new yy.MustacheNode($2[0], $2[1], $1, stripFlags($1, $3)) + | OPEN_UNESCAPED inMustache CLOSE_UNESCAPED -> new yy.MustacheNode($2[0], $2[1], $1, stripFlags($1, $3)) ; partial - : OPEN_PARTIAL partialName path? CLOSE -> new yy.PartialNode($2, $3) + : OPEN_PARTIAL partialName path? CLOSE -> new yy.PartialNode($2, $3, stripFlags($1, $4)) ; simpleInverse - : OPEN_INVERSE CLOSE { } + : OPEN_INVERSE CLOSE -> stripFlags($1, $2) ; inMustache From 6b0b3fa2ca4e8d274a71f8b8337e628e3466a7c5 Mon Sep 17 00:00:00 2001 From: kpdecker Date: Tue, 30 Jul 2013 11:06:54 -0500 Subject: [PATCH 180/200] Defer content output Allows for stripping of the content after the fact. --- .../compiler/javascript-compiler.js | 21 +++++++++++++++++-- 1 file changed, 19 insertions(+), 2 deletions(-) diff --git a/lib/handlebars/compiler/javascript-compiler.js b/lib/handlebars/compiler/javascript-compiler.js index d90f0b1d4..f0bd58b99 100644 --- a/lib/handlebars/compiler/javascript-compiler.js +++ b/lib/handlebars/compiler/javascript-compiler.js @@ -77,6 +77,9 @@ JavaScriptCompiler.prototype = { } } + // Flush any trailing content that might be pending. + this.pushSource(''); + return this.createFunctionContext(asObject); }, @@ -233,7 +236,14 @@ JavaScriptCompiler.prototype = { // // Appends the string value of `content` to the current buffer appendContent: function(content) { - this.pushSource(this.appendToBuffer(this.quotedString(content))); + if (this.pendingContent) { + content = this.pendingContent + content; + } + if (this.stripNext) { + content = content.replace(/^\s+/, ''); + } + + this.pendingContent = content; }, // [append] @@ -612,7 +622,14 @@ JavaScriptCompiler.prototype = { }, pushSource: function(source) { - this.source.push(source); + if (this.pendingContent) { + this.source.push(this.appendToBuffer(this.quotedString(this.pendingContent))); + this.pendingContent = undefined; + } + + if (source) { + this.source.push(source); + } }, pushStack: function(item) { From c776f171aba41dc8cc620b26372f8f1eda9525e1 Mon Sep 17 00:00:00 2001 From: kpdecker Date: Tue, 30 Jul 2013 11:07:09 -0500 Subject: [PATCH 181/200] Implement strip opcode --- .../compiler/javascript-compiler.js | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/lib/handlebars/compiler/javascript-compiler.js b/lib/handlebars/compiler/javascript-compiler.js index f0bd58b99..b04ef1ad3 100644 --- a/lib/handlebars/compiler/javascript-compiler.js +++ b/lib/handlebars/compiler/javascript-compiler.js @@ -75,6 +75,11 @@ JavaScriptCompiler.prototype = { } else { this[opcode.opcode].apply(this, opcode.args); } + + // Reset the stripNext flag if it was not set by this operation. + if (opcode.opcode !== this.stripNext) { + this.stripNext = false; + } } // Flush any trailing content that might be pending. @@ -246,6 +251,20 @@ JavaScriptCompiler.prototype = { this.pendingContent = content; }, + // [strip] + // + // On stack, before: ... + // On stack, after: ... + // + // Removes any trailing whitespace from the prior content node and flags + // the next operation for stripping if it is a content node. + strip: function() { + if (this.pendingContent) { + this.pendingContent = this.pendingContent.replace(/\s+$/, ''); + } + this.stripNext = 'strip'; + }, + // [append] // // On stack, before: value, ... From 31f7c25a8faac32e4996d95ccec509ead41f3f3c Mon Sep 17 00:00:00 2001 From: kpdecker Date: Mon, 14 Oct 2013 11:11:24 -0500 Subject: [PATCH 182/200] Use ~ rather than () for whitespace control. --- spec/whitespace-control.js | 48 +++++++++++++++++++------------------- src/handlebars.l | 8 +++---- src/handlebars.yy | 4 ++-- 3 files changed, 30 insertions(+), 30 deletions(-) diff --git a/spec/whitespace-control.js b/spec/whitespace-control.js index 3a2711ddd..2088ed8b7 100644 --- a/spec/whitespace-control.js +++ b/spec/whitespace-control.js @@ -2,61 +2,61 @@ describe('whitespace control', function() { it('should strip whitespace around mustache calls', function() { var hash = {foo: 'bar<'}; - shouldCompileTo(' {{(foo)}} ', hash, 'bar<'); - shouldCompileTo(' {{(foo}} ', hash, 'bar< '); - shouldCompileTo(' {{foo)}} ', hash, ' bar<'); + shouldCompileTo(' {{~foo~}} ', hash, 'bar<'); + shouldCompileTo(' {{~foo}} ', hash, 'bar< '); + shouldCompileTo(' {{foo~}} ', hash, ' bar<'); - shouldCompileTo(' {{(&foo)}} ', hash, 'bar<'); - shouldCompileTo(' {{({foo})}} ', hash, 'bar<'); + shouldCompileTo(' {{~&foo~}} ', hash, 'bar<'); + shouldCompileTo(' {{~{foo}~}} ', hash, 'bar<'); }); describe('blocks', function() { it('should strip whitespace around simple block calls', function() { var hash = {foo: 'bar<'}; - shouldCompileTo(' {{(#if foo)}} bar {{(/if)}} ', hash, 'bar'); - shouldCompileTo(' {{#if foo)}} bar {{/if)}} ', hash, ' bar '); - shouldCompileTo(' {{(#if foo}} bar {{(/if}} ', hash, ' bar '); + shouldCompileTo(' {{~#if foo~}} bar {{~/if~}} ', hash, 'bar'); + shouldCompileTo(' {{#if foo~}} bar {{/if~}} ', hash, ' bar '); + shouldCompileTo(' {{~#if foo}} bar {{~/if}} ', hash, ' bar '); shouldCompileTo(' {{#if foo}} bar {{/if}} ', hash, ' bar '); }); it('should strip whitespace around inverse block calls', function() { var hash = {}; - shouldCompileTo(' {{(^if foo)}} bar {{(/if)}} ', hash, 'bar'); - shouldCompileTo(' {{^if foo)}} bar {{/if)}} ', hash, ' bar '); - shouldCompileTo(' {{(^if foo}} bar {{(/if}} ', hash, ' bar '); + shouldCompileTo(' {{~^if foo~}} bar {{~/if~}} ', hash, 'bar'); + shouldCompileTo(' {{^if foo~}} bar {{/if~}} ', hash, ' bar '); + shouldCompileTo(' {{~^if foo}} bar {{~/if}} ', hash, ' bar '); shouldCompileTo(' {{^if foo}} bar {{/if}} ', hash, ' bar '); }); it('should strip whitespace around complex block calls', function() { var hash = {foo: 'bar<'}; - shouldCompileTo('{{#if foo)}} bar {{(^)}} baz {{(/if}}', hash, 'bar'); - shouldCompileTo('{{#if foo)}} bar {{^)}} baz {{/if}}', hash, 'bar '); - shouldCompileTo('{{#if foo}} bar {{(^)}} baz {{(/if}}', hash, ' bar'); - shouldCompileTo('{{#if foo}} bar {{^)}} baz {{/if}}', hash, ' bar '); + shouldCompileTo('{{#if foo~}} bar {{~^~}} baz {{~/if}}', hash, 'bar'); + shouldCompileTo('{{#if foo~}} bar {{^~}} baz {{/if}}', hash, 'bar '); + shouldCompileTo('{{#if foo}} bar {{~^~}} baz {{~/if}}', hash, ' bar'); + shouldCompileTo('{{#if foo}} bar {{^~}} baz {{/if}}', hash, ' bar '); - shouldCompileTo('{{#if foo)}} bar {{(else)}} baz {{(/if}}', hash, 'bar'); + shouldCompileTo('{{#if foo~}} bar {{~else~}} baz {{~/if}}', hash, 'bar'); hash = {}; - shouldCompileTo('{{#if foo)}} bar {{(^)}} baz {{(/if}}', hash, 'baz'); - shouldCompileTo('{{#if foo}} bar {{(^)}} baz {{/if}}', hash, 'baz '); - shouldCompileTo('{{#if foo)}} bar {{(^}} baz {{(/if}}', hash, ' baz'); - shouldCompileTo('{{#if foo)}} bar {{(^}} baz {{/if}}', hash, ' baz '); + shouldCompileTo('{{#if foo~}} bar {{~^~}} baz {{~/if}}', hash, 'baz'); + shouldCompileTo('{{#if foo}} bar {{~^~}} baz {{/if}}', hash, 'baz '); + shouldCompileTo('{{#if foo~}} bar {{~^}} baz {{~/if}}', hash, ' baz'); + shouldCompileTo('{{#if foo~}} bar {{~^}} baz {{/if}}', hash, ' baz '); - shouldCompileTo('{{#if foo)}} bar {{(else)}} baz {{(/if}}', hash, 'baz'); + shouldCompileTo('{{#if foo~}} bar {{~else~}} baz {{~/if}}', hash, 'baz'); }); }); it('should strip whitespace around partials', function() { - shouldCompileToWithPartials('foo {{(> dude)}} ', [{}, {}, {dude: 'bar'}], true, 'foobar'); - shouldCompileToWithPartials('foo {{> dude)}} ', [{}, {}, {dude: 'bar'}], true, 'foo bar'); + shouldCompileToWithPartials('foo {{~> dude~}} ', [{}, {}, {dude: 'bar'}], true, 'foobar'); + shouldCompileToWithPartials('foo {{> dude~}} ', [{}, {}, {dude: 'bar'}], true, 'foo bar'); shouldCompileToWithPartials('foo {{> dude}} ', [{}, {}, {dude: 'bar'}], true, 'foo bar '); }); it('should only strip whitespace once', function() { var hash = {foo: 'bar'}; - shouldCompileTo(' {{(foo)}} {{foo}} {{foo}} ', hash, 'barbar bar '); + shouldCompileTo(' {{~foo~}} {{foo}} {{foo}} ', hash, 'barbar bar '); }); }); diff --git a/src/handlebars.l b/src/handlebars.l index 740015be5..ddb7fe9ca 100644 --- a/src/handlebars.l +++ b/src/handlebars.l @@ -9,11 +9,11 @@ function strip(start, end) { %} -LEFT_STRIP "(" -RIGHT_STRIP ")" +LEFT_STRIP "~" +RIGHT_STRIP "~" -LOOKAHEAD [=)}\s\/.] -LITERAL_LOOKAHEAD [)}\s] +LOOKAHEAD [=~}\s\/.] +LITERAL_LOOKAHEAD [~}\s] /* ID is the inverse of control characters. diff --git a/src/handlebars.yy b/src/handlebars.yy index 6ee220775..0afd2cbbc 100644 --- a/src/handlebars.yy +++ b/src/handlebars.yy @@ -6,8 +6,8 @@ function stripFlags(open, close) { return { - left: open[2] === '(', - right: close[0] === ')' || close[1] === ')' + left: open[2] === '~', + right: close[0] === '~' || close[1] === '~' }; } From 9f138c007a92c991b5b222396f255044c1b9dca1 Mon Sep 17 00:00:00 2001 From: Christian Wesselhoeft Date: Mon, 28 Oct 2013 17:35:48 -0700 Subject: [PATCH 183/200] Remove generated parser.js as part of clean task. --- Gruntfile.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Gruntfile.js b/Gruntfile.js index a316715c4..382abf2a5 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -15,7 +15,7 @@ module.exports = function(grunt) { ] }, - clean: ["dist"], + clean: ["dist", "lib/handlebars/compiler/parser.js"], transpile: { amd: { type: "amd", From 88fefc1521641084d18fe1abf9697a852eedf28c Mon Sep 17 00:00:00 2001 From: kpdecker Date: Sun, 3 Nov 2013 10:50:50 -0600 Subject: [PATCH 184/200] Use grunt rather than explicit test runner for npm --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 844199647..c701fa05f 100644 --- a/package.json +++ b/package.json @@ -54,6 +54,6 @@ "handlebars": "bin/handlebars" }, "scripts": { - "test": "node ./spec/env/runner" + "test": "grunt" } } From ded0a1617fda248252a92d71b12c62a5e8536c33 Mon Sep 17 00:00:00 2001 From: kpdecker Date: Sun, 3 Nov 2013 10:54:24 -0600 Subject: [PATCH 185/200] Protect context-lookups from undefined values Fixes #166 Fixes #587 --- lib/handlebars/compiler/javascript-compiler.js | 18 +++++++++++++++--- spec/basic.js | 6 ++++++ spec/partials.js | 7 +++++++ 3 files changed, 28 insertions(+), 3 deletions(-) diff --git a/lib/handlebars/compiler/javascript-compiler.js b/lib/handlebars/compiler/javascript-compiler.js index b04ef1ad3..57af1d449 100644 --- a/lib/handlebars/compiler/javascript-compiler.js +++ b/lib/handlebars/compiler/javascript-compiler.js @@ -10,13 +10,25 @@ JavaScriptCompiler.prototype = { // PUBLIC API: You can override these methods in a subclass to provide // alternative compiled forms for name lookup and buffering semantics nameLookup: function(parent, name /* , type*/) { + var wrap, + ret; + if (parent.indexOf('depth') === 0) { + wrap = true; + } + if (/^[0-9]+$/.test(name)) { - return parent + "[" + name + "]"; + ret = parent + "[" + name + "]"; } else if (JavaScriptCompiler.isValidJavaScriptVariableName(name)) { - return parent + "." + name; + ret = parent + "." + name; } else { - return parent + "['" + name + "']"; + ret = parent + "['" + name + "']"; + } + + if (wrap) { + return '(' + parent + ' && ' + ret + ')'; + } else { + return ret; } }, diff --git a/spec/basic.js b/spec/basic.js index 4ee65efdf..ee154a165 100644 --- a/spec/basic.js +++ b/spec/basic.js @@ -22,6 +22,12 @@ describe("basic context", function() { "It works if all the required keys are provided"); }); + it("compiling with an undefined context", function() { + shouldCompileTo("Goodbye\n{{cruel}}\n{{world.bar}}!", undefined, "Goodbye\n\n!"); + + shouldCompileTo("{{#unless foo}}Goodbye{{../test}}{{test2}}{{/unless}}", undefined, "Goodbye"); + }); + it("comments", function() { shouldCompileTo("{{! Goodbye}}Goodbye\n{{cruel}}\n{{world}}!", {cruel: "cruel", world: "world"}, "Goodbye\ncruel\nworld!", diff --git a/spec/partials.js b/spec/partials.js index fa2fa56d4..7ff9d3c25 100644 --- a/spec/partials.js +++ b/spec/partials.js @@ -16,6 +16,13 @@ describe('partials', function() { "Partials can be passed a context"); }); + it("partials with undefined context", function() { + var string = "Dudes: {{>dude dudes}}"; + var partial = "{{foo}} Empty"; + var hash = {}; + shouldCompileToWithPartials(string, [hash, {}, {dude: partial}], true, "Dudes: Empty"); + }); + it("partial in a partial", function() { var string = "Dudes: {{#dudes}}{{>dude}}{{/dudes}}"; var dude = "{{name}} {{> url}} "; From c70e897f1c64e273c8aad7731f9e615a91f2dae7 Mon Sep 17 00:00:00 2001 From: kpdecker Date: Sun, 3 Nov 2013 15:49:05 -0600 Subject: [PATCH 186/200] Move component definitions into sub directory --- bower.json => components/bower.json | 2 +- components/component.json | 9 +++++ components/composer.json | 35 +++++++++++++++++++ .../handlebars-source.gemspec | 8 ++--- .../handlebars.js.nuspec | 2 +- components/source.rb | 11 ++++++ lib/handlebars/source.rb | 11 ------ tasks/release.js | 7 ++-- 8 files changed, 64 insertions(+), 21 deletions(-) rename bower.json => components/bower.json (77%) create mode 100644 components/component.json create mode 100644 components/composer.json rename handlebars-source.gemspec => components/handlebars-source.gemspec (79%) rename handlebars.js.nuspec => components/handlebars.js.nuspec (90%) create mode 100644 components/source.rb delete mode 100644 lib/handlebars/source.rb diff --git a/bower.json b/components/bower.json similarity index 77% rename from bower.json rename to components/bower.json index 4b86a80a4..c34fc3fef 100644 --- a/bower.json +++ b/components/bower.json @@ -1,7 +1,7 @@ { "name": "handlebars.js", "version": "1.0.0", - "main": "dist/handlebars.js", + "main": "handlebars.js", "ignore": [ "node_modules", "components" diff --git a/components/component.json b/components/component.json new file mode 100644 index 000000000..f74c83962 --- /dev/null +++ b/components/component.json @@ -0,0 +1,9 @@ +{ + "name": "handlebars", + "repo": "components/handlebars.js", + "version": "1.0.0", + "main": "handlebars.js", + "scripts": [ + "handlebars.js" + ] +} diff --git a/components/composer.json b/components/composer.json new file mode 100644 index 000000000..c5dc4271f --- /dev/null +++ b/components/composer.json @@ -0,0 +1,35 @@ +{ + "name": "components/handlebars.js", + "description": "Handlebars.js and Mustache are both logicless templating languages that keep the view and the code separated like we all know they should be.", + "homepage": "http://handlebarsjs.com", + "license": "MIT", + "type": "component", + "keywords": [ + "handlebars", + "mustache", + "html" + ], + "authors": [ + { + "name": "Chris Wanstrath", + "homepage": "http://chriswanstrath.com" + } + ], + "require": { + "robloach/component-installer": "*" + }, + "extra": { + "component": { + "name": "handlebars", + "scripts": [ + "handlebars.js" + ], + "files": [ + "handlebars.runtime.js" + ], + "shim": { + "exports": "Handlebars" + } + } + } +} diff --git a/handlebars-source.gemspec b/components/handlebars-source.gemspec similarity index 79% rename from handlebars-source.gemspec rename to components/handlebars-source.gemspec index 514e2b1e4..01ca1d1ed 100644 --- a/handlebars-source.gemspec +++ b/components/handlebars-source.gemspec @@ -1,7 +1,7 @@ # -*- encoding: utf-8 -*- require 'json' -package = JSON.parse(File.read('package.json')) +package = JSON.parse(File.read('bower.json')) Gem::Specification.new do |gem| gem.name = "handlebars-source" @@ -14,8 +14,8 @@ Gem::Specification.new do |gem| gem.version = package["version"] gem.files = [ - 'dist/handlebars.js', - 'dist/handlebars.runtime.js', - 'lib/handlebars/source.rb' + 'handlebars.js', + 'handlebars.runtime.js', + 'source.rb' ] end diff --git a/handlebars.js.nuspec b/components/handlebars.js.nuspec similarity index 90% rename from handlebars.js.nuspec rename to components/handlebars.js.nuspec index 4515f2ebd..37f2c4cde 100644 --- a/handlebars.js.nuspec +++ b/components/handlebars.js.nuspec @@ -12,6 +12,6 @@ handlebars mustache template html - + diff --git a/components/source.rb b/components/source.rb new file mode 100644 index 000000000..ec4caac91 --- /dev/null +++ b/components/source.rb @@ -0,0 +1,11 @@ +module Handlebars + module Source + def self.bundled_path + File.expand_path("handlebars.js", __FILE__) + end + + def self.runtime_bundled_path + File.expand_path("handlebars.runtime.js", __FILE__) + end + end +end diff --git a/lib/handlebars/source.rb b/lib/handlebars/source.rb deleted file mode 100644 index f576885ae..000000000 --- a/lib/handlebars/source.rb +++ /dev/null @@ -1,11 +0,0 @@ -module Handlebars - module Source - def self.bundled_path - File.expand_path("../../../dist/handlebars.js", __FILE__) - end - - def self.runtime_bundled_path - File.expand_path("../../../dist/handlebars.runtime.js", __FILE__) - end - end -end diff --git a/tasks/release.js b/tasks/release.js index 636670e1c..e7b0f191e 100644 --- a/tasks/release.js +++ b/tasks/release.js @@ -28,10 +28,9 @@ module.exports = function(grunt) { grunt.task.run(['build', 'tag']); async.each([ - ['lib/handlebars/base.js', /Handlebars.VERSION = "(.*)";/, 'Handlebars.VERSION = "' + version + '";'], - ['package.json', /"version":.*/, '"version": "' + version + '",'], - ['bower.json', /"version":.*/, '"version": "' + version + '",'], - ['handlebars.js.nuspec', /.*<\/version>/, '' + version + ''] + ['lib/handlebars/base.js', /var VERSION = "(.*)";/, 'var VERSION = "' + version + '";'], + ['components/bower.json', /"version":.*/, '"version": "' + version + '",'], + ['components/handlebars.js.nuspec', /.*<\/version>/, '' + version + ''] ], function(args, callback) { replace.apply(undefined, args); From 23ff9829d5d526fa51288671f53b43ac9f39e1de Mon Sep 17 00:00:00 2001 From: kpdecker Date: Sun, 3 Nov 2013 15:49:39 -0600 Subject: [PATCH 187/200] Remove tag target --- tasks/release.js | 13 ------------- 1 file changed, 13 deletions(-) diff --git a/tasks/release.js b/tasks/release.js index e7b0f191e..c6749b7c1 100644 --- a/tasks/release.js +++ b/tasks/release.js @@ -40,19 +40,6 @@ module.exports = function(grunt) { }); }); - grunt.registerTask('tag', 'Tags the current release version', function() { - var done = this.async(), - name = 'v' + grunt.config('pkg').version; - - async.series([ - function(callback) { git.add('dist/handlebars.js', callback); }, - function(callback) { git.add('dist/handlebars.runtime.js', callback); }, - function(callback) { git.commit(name, callback); }, - function(callback) { git.tag(name, callback); } - ], - done); - }); - function replace(path, regex, replace) { var content = grunt.file.read(path); content = content.replace(regex, replace); From a6781265a7cdedc5dd3a53390136bfb478e1304b Mon Sep 17 00:00:00 2001 From: kpdecker Date: Sun, 3 Nov 2013 15:58:44 -0600 Subject: [PATCH 188/200] Update version task for generator-release --- tasks/release.js | 48 ------------------------------------------------ tasks/version.js | 41 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 41 insertions(+), 48 deletions(-) delete mode 100644 tasks/release.js create mode 100644 tasks/version.js diff --git a/tasks/release.js b/tasks/release.js deleted file mode 100644 index c6749b7c1..000000000 --- a/tasks/release.js +++ /dev/null @@ -1,48 +0,0 @@ -var async = require('async'), - git = require('./util/git'), - semver = require('semver'); - -module.exports = function(grunt) { - grunt.registerTask('version', 'Updates the current release version', function() { - var done = this.async(), - pkg = grunt.config('pkg'), - version = grunt.option('ver'); - - if (version === 'major' || version === 'minor' || version === 'patch' || version === 'prerelease') { - version = semver.inc(pkg.version, version); - } - if (!semver.valid(version)) { - throw new Error('Must provide a version number (Ex: --ver=patch):\n\t' + version + '\n\n'); - } - - pkg.version = version; - grunt.config('pkg', pkg); - - git.clean(function(err, clean) { - if (err || !clean) { - throw new Error('The repository must be clean'); - } - - grunt.log.write('Updating to version ' + version); - - grunt.task.run(['build', 'tag']); - - async.each([ - ['lib/handlebars/base.js', /var VERSION = "(.*)";/, 'var VERSION = "' + version + '";'], - ['components/bower.json', /"version":.*/, '"version": "' + version + '",'], - ['components/handlebars.js.nuspec', /.*<\/version>/, '' + version + ''] - ], - function(args, callback) { - replace.apply(undefined, args); - git.add(args[0], callback); - }, - done); - }); - }); - - function replace(path, regex, replace) { - var content = grunt.file.read(path); - content = content.replace(regex, replace); - grunt.file.write(path, content); - } -}; diff --git a/tasks/version.js b/tasks/version.js new file mode 100644 index 000000000..c622a22fd --- /dev/null +++ b/tasks/version.js @@ -0,0 +1,41 @@ +var async = require('async'), + git = require('./util/git'), + semver = require('semver'); + +module.exports = function(grunt) { + grunt.registerTask('version', 'Updates the current release version', function() { + var done = this.async(), + pkg = grunt.config('pkg'), + version = grunt.option('ver'); + + if (!semver.valid(version)) { + throw new Error('Must provide a version number (Ex: --ver=1.0.0):\n\t' + version + '\n\n'); + } + + pkg.version = version; + grunt.config('pkg', pkg); + + grunt.log.writeln('Updating to version ' + version); + + async.each([ + ['lib/handlebars/base.js', /var VERSION = "(.*)";/, 'var VERSION = "' + version + '";'], + ['components/bower.json', /"version":.*/, '"version": "' + version + '",'], + ['components/handlebars.js.nuspec', /.*<\/version>/, '' + version + ''] + ], + function(args, callback) { + replace.apply(undefined, args); + grunt.log.writeln(' - ' + args[0]); + git.add(args[0], callback); + }, + function() { + grunt.task.run(['default']); + done(); + }); + }); + + function replace(path, regex, replace) { + var content = grunt.file.read(path); + content = content.replace(regex, replace); + grunt.file.write(path, content); + } +}; From 6b62a2e58e2259cb44cba5b91d09a653e00a0873 Mon Sep 17 00:00:00 2001 From: kpdecker Date: Sun, 3 Nov 2013 16:01:55 -0600 Subject: [PATCH 189/200] Log tag on jenkins build --- tasks/publish.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tasks/publish.js b/tasks/publish.js index 564ce088a..5a04db63b 100644 --- a/tasks/publish.js +++ b/tasks/publish.js @@ -12,6 +12,8 @@ module.exports = function(grunt) { grunt.log.writeln('branches: ' + branches); git.commitInfo(function(err, info) { + grunt.log(writeln('tag: ' + info.tagName); + if (info.isMaster) { initSDK(); publish(fileMap(['-latest', '-' + info.head]), done); From 479acfbe9e15f051d0eb2f65ea0eb0b71b408d74 Mon Sep 17 00:00:00 2001 From: kpdecker Date: Sun, 3 Nov 2013 16:04:12 -0600 Subject: [PATCH 190/200] Fix typo --- tasks/publish.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tasks/publish.js b/tasks/publish.js index 5a04db63b..bcbaefae8 100644 --- a/tasks/publish.js +++ b/tasks/publish.js @@ -12,7 +12,7 @@ module.exports = function(grunt) { grunt.log.writeln('branches: ' + branches); git.commitInfo(function(err, info) { - grunt.log(writeln('tag: ' + info.tagName); + grunt.log.writeln('tag: ' + info.tagName); if (info.isMaster) { initSDK(); From a2bb02638bc0a16f22d925fac73d85cfc01f354e Mon Sep 17 00:00:00 2001 From: kpdecker Date: Sun, 3 Nov 2013 16:14:27 -0600 Subject: [PATCH 191/200] Publish tag versions from travis build --- tasks/publish.js | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/tasks/publish.js b/tasks/publish.js index bcbaefae8..190f17e46 100644 --- a/tasks/publish.js +++ b/tasks/publish.js @@ -1,7 +1,8 @@ var _ = require('underscore'), async = require('async'), AWS = require('aws-sdk'), - git = require('./util/git'); + git = require('./util/git'), + semver = require('semver'); module.exports = function(grunt) { grunt.registerTask('publish:latest', function() { @@ -16,7 +17,13 @@ module.exports = function(grunt) { if (info.isMaster) { initSDK(); - publish(fileMap(['-latest', '-' + info.head]), done); + + var files = ['-latest', '-' + info.head]; + if (info.tagName && semver.valid(info.tagName) { + files.push('-' + info.tagName); + } + + publish(fileMap(files), done); } else { // Silently ignore for branches done(); From 60f8aad5c965d21084f582d606e97db0fe7438f3 Mon Sep 17 00:00:00 2001 From: kpdecker Date: Sun, 3 Nov 2013 16:50:36 -0600 Subject: [PATCH 192/200] Fix publish task typo --- tasks/publish.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tasks/publish.js b/tasks/publish.js index 190f17e46..68b3157aa 100644 --- a/tasks/publish.js +++ b/tasks/publish.js @@ -19,7 +19,7 @@ module.exports = function(grunt) { initSDK(); var files = ['-latest', '-' + info.head]; - if (info.tagName && semver.valid(info.tagName) { + if (info.tagName && semver.valid(info.tagName)) { files.push('-' + info.tagName); } From 6ab579bb2acd20df5191c5ae585d1c8ea91cb125 Mon Sep 17 00:00:00 2001 From: kpdecker Date: Sun, 3 Nov 2013 17:20:28 -0600 Subject: [PATCH 193/200] Copy components definitions to the dist directory --- Gruntfile.js | 12 +++++++++++- package.json | 1 + 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/Gruntfile.js b/Gruntfile.js index 382abf2a5..61591a45b 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -16,6 +16,15 @@ module.exports = function(grunt) { }, clean: ["dist", "lib/handlebars/compiler/parser.js"], + + copy: { + components: { + files: [ + {expand: true, cwd: 'components/', src: ['**'], dest: 'dist/'} + ] + } + }, + transpile: { amd: { type: "amd", @@ -104,10 +113,11 @@ module.exports = function(grunt) { this.registerTask('node', ['transpile:cjs']); this.registerTask('globals', ['packager-fork']); - this.registerTask('release', 'Build final packages', ['amd', 'uglify']); + this.registerTask('release', 'Build final packages', ['copy:components', 'amd', 'uglify']); // Load tasks from npm grunt.loadNpmTasks('grunt-contrib-clean'); + grunt.loadNpmTasks('grunt-contrib-copy'); grunt.loadNpmTasks('grunt-contrib-requirejs'); grunt.loadNpmTasks('grunt-contrib-jshint'); grunt.loadNpmTasks('grunt-contrib-uglify'); diff --git a/package.json b/package.json index c701fa05f..0ff88ff33 100644 --- a/package.json +++ b/package.json @@ -35,6 +35,7 @@ "eco": "~1.1.0-rc-3", "grunt": "~0.4.1", "grunt-contrib-clean": "~0.4.1", + "grunt-contrib-copy": "~0.4.1", "grunt-contrib-jshint": "~0.6.3", "grunt-contrib-requirejs": "~0.4.1", "grunt-contrib-uglify": "~0.2.2", From 7cef79288a4d11c1975524367f240fc72ae8c0d3 Mon Sep 17 00:00:00 2001 From: kpdecker Date: Sun, 3 Nov 2013 17:20:47 -0600 Subject: [PATCH 194/200] Fix the handlebars-source gem definition --- components/handlebars-source.gemspec | 2 +- components/lib/handlebars/source.rb | 11 +++++++++++ components/source.rb | 11 ----------- 3 files changed, 12 insertions(+), 12 deletions(-) create mode 100644 components/lib/handlebars/source.rb delete mode 100644 components/source.rb diff --git a/components/handlebars-source.gemspec b/components/handlebars-source.gemspec index 01ca1d1ed..4313e9581 100644 --- a/components/handlebars-source.gemspec +++ b/components/handlebars-source.gemspec @@ -16,6 +16,6 @@ Gem::Specification.new do |gem| gem.files = [ 'handlebars.js', 'handlebars.runtime.js', - 'source.rb' + 'lib/handlebars/source.rb' ] end diff --git a/components/lib/handlebars/source.rb b/components/lib/handlebars/source.rb new file mode 100644 index 000000000..4f3f8bfce --- /dev/null +++ b/components/lib/handlebars/source.rb @@ -0,0 +1,11 @@ +module Handlebars + module Source + def self.bundled_path + File.expand_path("../../../handlebars.js", __FILE__) + end + + def self.runtime_bundled_path + File.expand_path("../../../handlebars.runtime.js", __FILE__) + end + end +end diff --git a/components/source.rb b/components/source.rb deleted file mode 100644 index ec4caac91..000000000 --- a/components/source.rb +++ /dev/null @@ -1,11 +0,0 @@ -module Handlebars - module Source - def self.bundled_path - File.expand_path("handlebars.js", __FILE__) - end - - def self.runtime_bundled_path - File.expand_path("handlebars.runtime.js", __FILE__) - end - end -end From 904daba10b3d89d89fa54ad9e8e783d3dd6a99ac Mon Sep 17 00:00:00 2001 From: kpdecker Date: Sun, 3 Nov 2013 19:03:49 -0600 Subject: [PATCH 195/200] Create cdnjs and components dist dirs --- .npmignore | 4 ++++ Gruntfile.js | 10 ++++++++-- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/.npmignore b/.npmignore index c1e8834ea..ca2bc9744 100644 --- a/.npmignore +++ b/.npmignore @@ -11,7 +11,11 @@ Rakefile *.nuspec bench/* configurations/* +components/* +dist/cdnjs/* +dist/components/* spec/* src/* tasks/* +publish/* vendor/* diff --git a/Gruntfile.js b/Gruntfile.js index 61591a45b..6f4159682 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -18,9 +18,15 @@ module.exports = function(grunt) { clean: ["dist", "lib/handlebars/compiler/parser.js"], copy: { + cdnjs: { + files: [ + {expand: true, cwd: 'dist/', src: ['*.js'], dest: 'dist/cdnjs'} + ] + }, components: { files: [ - {expand: true, cwd: 'components/', src: ['**'], dest: 'dist/'} + {expand: true, cwd: 'components/', src: ['**'], dest: 'dist/components'}, + {expand: true, cwd: 'dist/', src: ['*.js'], dest: 'dist/components'} ] } }, @@ -113,7 +119,7 @@ module.exports = function(grunt) { this.registerTask('node', ['transpile:cjs']); this.registerTask('globals', ['packager-fork']); - this.registerTask('release', 'Build final packages', ['copy:components', 'amd', 'uglify']); + this.registerTask('release', 'Build final packages', ['amd', 'uglify', 'copy:components', 'copy:cdnjs']); // Load tasks from npm grunt.loadNpmTasks('grunt-contrib-clean'); From b5969394f150b3fbec666e1671b38d428c500544 Mon Sep 17 00:00:00 2001 From: kpdecker Date: Sun, 3 Nov 2013 20:57:39 -0600 Subject: [PATCH 196/200] Update release notes --- release-notes.md | 32 +++++++++++++++++++++++++++----- 1 file changed, 27 insertions(+), 5 deletions(-) diff --git a/release-notes.md b/release-notes.md index 774001b4e..6040f411f 100644 --- a/release-notes.md +++ b/release-notes.md @@ -2,19 +2,36 @@ ## Development -- [#602](https://github.com/wycats/handlebars.js/pull/602) - Handle zero uniformly between mustache conditionals and the if helper ([@artiee](https://github.com/artiee)) -- [#535](https://github.com/wycats/handlebars.js/pull/535) - Fix for probably JIT error under Safari. ([@sorentwo](https://github.com/sorentwo)) -- [#537](https://github.com/wycats/handlebars.js/issues/537) - Add CommonJS and AMD loader support ([@spikebrehm](https://github.com/spikebrehm)) -- [#544](https://github.com/wycats/handlebars.js/issues/544) - Push travis builds to build server ([@fivetanley](https://github.com/fivetanley)) +[Commits](https://github.com/wycats/handlebars.js/compare/v1.1.0...master) + +## v1.1.0 - November 3rd, 2013 + +- [#628](https://github.com/wycats/handlebars.js/pull/628) - Convert code to ES6 modules ([@kpdecker](https://api.github.com/users/kpdecker)) +- [#336](https://github.com/wycats/handlebars.js/pull/336) - Add whitespace control syntax ([@kpdecker](https://api.github.com/users/kpdecker)) +- [#535](https://github.com/wycats/handlebars.js/pull/535) - Fix for probable JIT error under Safari ([@sorentwo](https://api.github.com/users/sorentwo)) +- [#483](https://github.com/wycats/handlebars.js/issues/483) - Add first and last @ vars to each helper ([@denniskuczynski](https://api.github.com/users/denniskuczynski)) +- [#557](https://github.com/wycats/handlebars.js/pull/557) - `\\{{foo}}` escaping only works in some situations ([@dmarcotte](https://api.github.com/users/dmarcotte)) +- [#552](https://github.com/wycats/handlebars.js/pull/552) - Added BOM removal flag. ([@blessenm](https://api.github.com/users/blessenm)) +- [#543](https://github.com/wycats/handlebars.js/pull/543) - publish passing master builds to s3 ([@fivetanley](https://api.github.com/users/fivetanley)) + +- [#608](https://github.com/wycats/handlebars.js/issues/608) - Add `includeZero` flag to `if` conditional +- [#498](https://github.com/wycats/handlebars.js/issues/498) - `Handlebars.compile` fails on empty string although a single blank works fine +- [#599](https://github.com/wycats/handlebars.js/issues/599) - lambda helpers only receive options if used with arguments +- [#592](https://github.com/wycats/handlebars.js/issues/592) - Optimize array and subprogram performance +- [#571](https://github.com/wycats/handlebars.js/issues/571) - uglify upgrade breaks compatibility with older versions of node +- [#587](https://github.com/wycats/handlebars.js/issues/587) - Partial inside partial breaks? + Compatibility notes: - The project now includes separate artifacts for AMD, CommonJS, and global objects. - AMD: Users may load the bundled `handlebars.amd.js` or `handlebars.runtime.amd.js` files or load individual modules directly. AMD users should also note that the handlebars object is exposed via the `default` field on the imported object. This [gist](https://gist.github.com/wycats/7417be0dc361a69d5916) provides some discussion of possible compatibility shims. - CommonJS/Node: Node loading occurs as normal via `require` - Globals: The `handlebars.js` and `handlebars.runtime.js` files should behave in the same manner as the v1.0.12 / 1.0.0 release. +- Build artifacts have been removed from the repository. [npm][npm], [components/handlebars.js][components], [cdnjs][cdnjs-lib], or the [builds page][builds-page] should now be used as the source of built artifacts. - Context-stored helpers are now always passed the `options` hash. Previously no-argument helpers did not have this argument. -[Commits](https://github.com/wycats/handlebars.js/compare/v1.0.12...master) + +[Commits](https://github.com/wycats/handlebars.js/compare/v1.0.12...v1.1.0) ## v1.0.12 / 1.0.0 - May 31 2013 @@ -105,3 +122,8 @@ Use: ```js template(context, {helpers: helpers, partials: partials, data: data}) ``` + +[builds-page]: http://builds.handlebarsjs.com.s3.amazonaws.com/index.html +[cdn-js]: http://cdnjs.com/libraries/handlebars.js/ +[components]: https://github.com/components/handlebars.js +[npm]: https://npmjs.org/package/handlebars From 29e3858d217c1ce4552e91f3f85a0fad39697259 Mon Sep 17 00:00:00 2001 From: kpdecker Date: Sun, 3 Nov 2013 21:25:46 -0600 Subject: [PATCH 197/200] v1.1.0 --- components/bower.json | 2 +- components/handlebars.js.nuspec | 2 +- lib/handlebars/base.js | 2 +- package.json | 4 +--- 4 files changed, 4 insertions(+), 6 deletions(-) diff --git a/components/bower.json b/components/bower.json index c34fc3fef..a1ce4ad6a 100644 --- a/components/bower.json +++ b/components/bower.json @@ -1,6 +1,6 @@ { "name": "handlebars.js", - "version": "1.0.0", + "version": "1.1.0", "main": "handlebars.js", "ignore": [ "node_modules", diff --git a/components/handlebars.js.nuspec b/components/handlebars.js.nuspec index 37f2c4cde..07d097c73 100644 --- a/components/handlebars.js.nuspec +++ b/components/handlebars.js.nuspec @@ -2,7 +2,7 @@ handlebars.js - 1.0.0 + 1.1.0 handlebars.js Authors https://github.com/wycats/handlebars.js/blob/master/LICENSE https://github.com/wycats/handlebars.js/ diff --git a/lib/handlebars/base.js b/lib/handlebars/base.js index dcf446b78..236a3c272 100644 --- a/lib/handlebars/base.js +++ b/lib/handlebars/base.js @@ -2,7 +2,7 @@ module Utils from "./utils"; import Exception from "./exception"; -export var VERSION = "1.0.0"; +export var VERSION = "1.1.0"; export var COMPILER_REVISION = 4; export var REVISION_CHANGES = { diff --git a/package.json b/package.json index 0ff88ff33..3e7595b39 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "handlebars", "barename": "handlebars", - "version": "1.0.12", + "version": "1.1.0", "description": "Handlebars provides the power necessary to let you build semantic templates effectively with no frustration", "homepage": "http://www.handlebarsjs.com/", "keywords": [ @@ -17,7 +17,6 @@ "author": "Yehuda Katz", "license": "BSD", "readmeFilename": "README.md", - "engines": { "node": ">=0.4.7" }, @@ -49,7 +48,6 @@ "should": "~1.2.2", "underscore": "~1.5.1" }, - "main": "lib/index.js", "bin": { "handlebars": "bin/handlebars" From 79fe7afb656178b01ed5fe9aee7fc7a4a28ee3e6 Mon Sep 17 00:00:00 2001 From: kpdecker Date: Sun, 3 Nov 2013 22:01:12 -0600 Subject: [PATCH 198/200] Fix license output on combined artifacts --- Gruntfile.js | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/Gruntfile.js b/Gruntfile.js index 6f4159682..4fe9b0531 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -18,6 +18,17 @@ module.exports = function(grunt) { clean: ["dist", "lib/handlebars/compiler/parser.js"], copy: { + dist: { + options: { + processContent: function(content, path) { + return grunt.template.process('/*!\n\n <%= pkg.name %> v<%= pkg.version %>\n\n<%= grunt.file.read("LICENSE") %>\n@license\n*/\n') + + content; + } + }, + files: [ + {expand: true, cwd: 'dist/', src: ['*.js'], dest: 'dist/'} + ] + }, cdnjs: { files: [ {expand: true, cwd: 'dist/', src: ['*.js'], dest: 'dist/cdnjs'} @@ -119,7 +130,7 @@ module.exports = function(grunt) { this.registerTask('node', ['transpile:cjs']); this.registerTask('globals', ['packager-fork']); - this.registerTask('release', 'Build final packages', ['amd', 'uglify', 'copy:components', 'copy:cdnjs']); + this.registerTask('release', 'Build final packages', ['amd', 'uglify', 'copy:dist', 'copy:components', 'copy:cdnjs']); // Load tasks from npm grunt.loadNpmTasks('grunt-contrib-clean'); From 41e2b7658bea61837eaeef8542854c49de74f449 Mon Sep 17 00:00:00 2001 From: kpdecker Date: Sun, 3 Nov 2013 22:02:59 -0600 Subject: [PATCH 199/200] Remove Gruntfile from npm package --- .npmignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.npmignore b/.npmignore index ca2bc9744..eeb1ceb5e 100644 --- a/.npmignore +++ b/.npmignore @@ -7,6 +7,7 @@ Gemfile Gemfile.lock Rakefile +Gruntfile.js *.gemspec *.nuspec bench/* From c53e95414f4e74bf6dfb2379a6b9b2468b8fcb31 Mon Sep 17 00:00:00 2001 From: kpdecker Date: Sun, 3 Nov 2013 22:20:46 -0600 Subject: [PATCH 200/200] Update bower information for components package --- components/bower.json | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/components/bower.json b/components/bower.json index a1ce4ad6a..ee9011dd5 100644 --- a/components/bower.json +++ b/components/bower.json @@ -1,9 +1,6 @@ { - "name": "handlebars.js", + "name": "handlebars", "version": "1.1.0", "main": "handlebars.js", - "ignore": [ - "node_modules", - "components" - ] + "dependencies": {} }