From efca3c8ef5665fe3cf2802f866db0c73755824e0 Mon Sep 17 00:00:00 2001
From: Spike Brehm <ocelot@gmail.com>
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 <kpdecker@gmail.com>
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 <dstanley.stuart@gmail.com>
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<j; i++) {
-        if (data) { data.index = i; }
-        ret = ret + fn(context[i], { data: data });
-      }
-    } else {
-      for(var key in context) {
-        if(context.hasOwnProperty(key)) {
-          if(data) { data.key = key; }
-          ret = ret + fn(context[key], {data: data});
-          i++;
-        }
-      }
-    }
-  }
-
-  if(i === 0){
-    ret = inverse(this);
-  }
-
-  return ret;
-});
-
-Handlebars.registerHelper('if', function(conditional, options) {
-  var type = toString.call(conditional);
-  if(type === functionType) { conditional = conditional.call(this); }
-
-  if(!conditional || Handlebars.Utils.isEmpty(conditional)) {
-    return options.inverse(this);
-  } else {
-    return options.fn(this);
-  }
-});
-
-Handlebars.registerHelper('unless', function(conditional, options) {
-  return Handlebars.helpers['if'].call(this, conditional, {fn: options.inverse, inverse: options.fn});
-});
-
-Handlebars.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);
-});
-
-Handlebars.registerHelper('log', function(context, options) {
-  var level = options.data && options.data.level != null ? parseInt(options.data.level, 10) : 1;
-  Handlebars.log(level, context);
-});
-;
-// lib/handlebars/compiler/parser.js
-/* Jison generated parser */
-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},
-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]],
-performAction: function anonymous(yytext,yyleng,yylineno,yy,yystate,$$,_$) {
-
-var $0 = $$.length - 1;
-switch (yystate) {
-case 1: return $$[$0-1]; 
-break;
-case 2: this.$ = new yy.ProgramNode([], $$[$0]); 
-break;
-case 3: this.$ = new yy.ProgramNode($$[$0-2], $$[$0]); 
-break;
-case 4: this.$ = new yy.ProgramNode($$[$0-1], []); 
-break;
-case 5: this.$ = new yy.ProgramNode($$[$0]); 
-break;
-case 6: this.$ = new yy.ProgramNode([], []); 
-break;
-case 7: this.$ = new yy.ProgramNode([]); 
-break;
-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]); 
-break;
-case 11: this.$ = new yy.BlockNode($$[$0-2], $$[$0-1], $$[$0-1].inverse, $$[$0]); 
-break;
-case 12: this.$ = $$[$0]; 
-break;
-case 13: this.$ = $$[$0]; 
-break;
-case 14: this.$ = new yy.ContentNode($$[$0]); 
-break;
-case 15: this.$ = new yy.CommentNode($$[$0]); 
-break;
-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]); 
-break;
-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); 
-break;
-case 21: this.$ = new yy.PartialNode($$[$0-1]); 
-break;
-case 22: this.$ = new yy.PartialNode($$[$0-2], $$[$0-1]); 
-break;
-case 23: 
-break;
-case 24: this.$ = [[$$[$0-2]].concat($$[$0-1]), $$[$0]]; 
-break;
-case 25: this.$ = [[$$[$0-1]].concat($$[$0]), null]; 
-break;
-case 26: this.$ = [[$$[$0-1]], $$[$0]]; 
-break;
-case 27: this.$ = [[$$[$0]], null]; 
-break;
-case 28: this.$ = [[$$[$0]], null]; 
-break;
-case 29: $$[$0-1].push($$[$0]); this.$ = $$[$0-1]; 
-break;
-case 30: this.$ = [$$[$0]]; 
-break;
-case 31: this.$ = $$[$0]; 
-break;
-case 32: this.$ = new yy.StringNode($$[$0]); 
-break;
-case 33: this.$ = new yy.IntegerNode($$[$0]); 
-break;
-case 34: this.$ = new yy.BooleanNode($$[$0]); 
-break;
-case 35: this.$ = $$[$0]; 
-break;
-case 36: this.$ = new yy.HashNode($$[$0]); 
-break;
-case 37: $$[$0-1].push($$[$0]); this.$ = $$[$0-1]; 
-break;
-case 38: this.$ = [$$[$0]]; 
-break;
-case 39: this.$ = [$$[$0-2], $$[$0]]; 
-break;
-case 40: this.$ = [$$[$0-2], new yy.StringNode($$[$0])]; 
-break;
-case 41: this.$ = [$$[$0-2], new yy.IntegerNode($$[$0])]; 
-break;
-case 42: this.$ = [$$[$0-2], new yy.BooleanNode($$[$0])]; 
-break;
-case 43: this.$ = [$$[$0-2], $$[$0]]; 
-break;
-case 44: this.$ = new yy.PartialNameNode($$[$0]); 
-break;
-case 45: this.$ = new yy.PartialNameNode(new yy.StringNode($$[$0])); 
-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]}]; 
-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]}],
-defaultActions: {17:[2,1]},
-parseError: function parseError(str, hash) {
-    throw new Error(str);
-},
-parse: function parse(input) {
-    var self = this, stack = [0], vstack = [null], lstack = [], table = this.table, yytext = "", yylineno = 0, yyleng = 0, recovering = 0, TERROR = 2, EOF = 1;
-    this.lexer.setInput(input);
-    this.lexer.yy = this.yy;
-    this.yy.lexer = this.lexer;
-    this.yy.parser = this;
-    if (typeof this.lexer.yylloc == "undefined")
-        this.lexer.yylloc = {};
-    var yyloc = this.lexer.yylloc;
-    lstack.push(yyloc);
-    var ranges = this.lexer.options && this.lexer.options.ranges;
-    if (typeof this.yy.parseError === "function")
-        this.parseError = this.yy.parseError;
-    function popStack(n) {
-        stack.length = stack.length - 2 * n;
-        vstack.length = vstack.length - n;
-        lstack.length = lstack.length - n;
-    }
-    function lex() {
-        var token;
-        token = self.lexer.lex() || 1;
-        if (typeof token !== "number") {
-            token = self.symbols_[token] || token;
-        }
-        return token;
-    }
-    var symbol, preErrorSymbol, state, action, a, r, yyval = {}, p, len, newState, expected;
-    while (true) {
-        state = stack[stack.length - 1];
-        if (this.defaultActions[state]) {
-            action = this.defaultActions[state];
-        } else {
-            if (symbol === null || typeof symbol == "undefined") {
-                symbol = lex();
-            }
-            action = table[state] && table[state][symbol];
-        }
-        if (typeof action === "undefined" || !action.length || !action[0]) {
-            var errStr = "";
-            if (!recovering) {
-                expected = [];
-                for (p in table[state])
-                    if (this.terminals_[p] && p > 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<l; i++) {
-    var part = parts[i].part;
-    original += (parts[i].separator || '') + part;
-
-    if (part === ".." || part === "." || part === "this") {
-      if (dig.length > 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 = {
-  "&": "&amp;",
-  "<": "&lt;",
-  ">": "&gt;",
-  '"': "&quot;",
-  "'": "&#x27;",
-  "`": "&#x60;"
-};
-
-var badChars = /[&<>"'`]/g;
-var possible = /[&<>"'`]/;
-
-var escapeChar = function(chr) {
-  return escape[chr] || "&amp;";
-};
-
-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<l; i++) {
-      opcode = opcodes[i];
-
-      if (opcode.opcode === 'DECLARE') {
-        out.push("DECLARE " + opcode.name + "=" + opcode.value);
-      } else {
-        params = [];
-        for (var j=0; j<opcode.args.length; j++) {
-          param = opcode.args[j];
-          if (typeof param === "string") {
-            param = "\"" + param.replace("\n", "\\n") + "\"";
-          }
-          params.push(param);
-        }
-        out.push(opcode.opcode + " " + params.join(" "));
-      }
-    }
-
-    return out.join("\n");
-  },
-  equals: function(other) {
-    var len = this.opcodes.length;
-    if (other.opcodes.length !== len) {
-      return false;
-    }
-
-    for (var i = 0; i < len; i++) {
-      var opcode = this.opcodes[i],
-          otherOpcode = other.opcodes[i];
-      if (opcode.opcode !== otherOpcode.opcode || opcode.args.length !== otherOpcode.args.length) {
-        return false;
-      }
-      for (var j = 0; j < opcode.args.length; j++) {
-        if (opcode.args[j] !== otherOpcode.args[j]) {
-          return false;
-        }
-      }
-    }
-
-    len = this.children.length;
-    if (other.children.length !== len) {
-      return false;
-    }
-    for (i = 0; i < len; i++) {
-      if (!this.children[i].equals(other.children[i])) {
-        return false;
-      }
-    }
-
-    return true;
-  },
-
-  guid: 0,
-
-  compile: function(program, options) {
-    this.children = [];
-    this.depths = {list: []};
-    this.options = options;
-
-    // These changes will propagate to the other compiler components
-    var knownHelpers = this.options.knownHelpers;
-    this.options.knownHelpers = {
-      'helperMissing': true,
-      'blockHelperMissing': true,
-      'each': true,
-      'if': true,
-      'unless': true,
-      'with': true,
-      'log': true
-    };
-    if (knownHelpers) {
-      for (var name in knownHelpers) {
-        this.options.knownHelpers[name] = knownHelpers[name];
-      }
-    }
-
-    return this.program(program);
-  },
-
-  accept: function(node) {
-    return this[node.type](node);
-  },
-
-  program: function(program) {
-    var statements = program.statements, statement;
-    this.opcodes = [];
-
-    for(var i=0, l=statements.length; i<l; i++) {
-      statement = statements[i];
-      this[statement.type](statement);
-    }
-    this.isSimple = l === 1;
-
-    this.depths.list = this.depths.list.sort(function(a, b) {
-      return a - b;
-    });
-
-    return this;
-  },
-
-  compileProgram: function(program) {
-    var result = new this.compiler().compile(program, this.options);
-    var guid = this.guid++, depth;
-
-    this.usePartial = this.usePartial || result.usePartial;
-
-    this.children[guid] = result;
-
-    for(var i=0, l=result.depths.list.length; i<l; i++) {
-      depth = result.depths.list[i];
-
-      if(depth < 2) { continue; }
-      else { this.addDepth(depth - 1); }
-    }
-
-    return guid;
-  },
-
-  block: function(block) {
-    var mustache = block.mustache,
-        program = block.program,
-        inverse = block.inverse;
-
-    if (program) {
-      program = this.compileProgram(program);
-    }
-
-    if (inverse) {
-      inverse = this.compileProgram(inverse);
-    }
-
-    var type = this.classifyMustache(mustache);
-
-    if (type === "helper") {
-      this.helperMustache(mustache, program, inverse);
-    } else if (type === "simple") {
-      this.simpleMustache(mustache);
-
-      // now that the simple mustache is resolved, we need to
-      // evaluate it by executing `blockHelperMissing`
-      this.opcode('pushProgram', program);
-      this.opcode('pushProgram', inverse);
-      this.opcode('emptyHash');
-      this.opcode('blockValue');
-    } else {
-      this.ambiguousMustache(mustache, program, inverse);
-
-      // now that the simple mustache is resolved, we need to
-      // evaluate it by executing `blockHelperMissing`
-      this.opcode('pushProgram', program);
-      this.opcode('pushProgram', inverse);
-      this.opcode('emptyHash');
-      this.opcode('ambiguousBlockValue');
-    }
-
-    this.opcode('append');
-  },
-
-  hash: function(hash) {
-    var pairs = hash.pairs, pair, val;
-
-    this.opcode('pushHash');
-
-    for(var i=0, l=pairs.length; i<l; i++) {
-      pair = pairs[i];
-      val  = pair[1];
-
-      if (this.options.stringParams) {
-        if(val.depth) {
-          this.addDepth(val.depth);
-        }
-        this.opcode('getContext', val.depth || 0);
-        this.opcode('pushStringParam', val.stringModeValue, val.type);
-      } else {
-        this.accept(val);
-      }
-
-      this.opcode('assignToHash', pair[0]);
-    }
-    this.opcode('popHash');
-  },
-
-  partial: function(partial) {
-    var partialName = partial.partialName;
-    this.usePartial = true;
-
-    if(partial.context) {
-      this.ID(partial.context);
-    } else {
-      this.opcode('push', 'depth0');
-    }
-
-    this.opcode('invokePartial', partialName.name);
-    this.opcode('append');
-  },
-
-  content: function(content) {
-    this.opcode('appendContent', content.string);
-  },
-
-  mustache: function(mustache) {
-    var options = this.options;
-    var type = this.classifyMustache(mustache);
-
-    if (type === "simple") {
-      this.simpleMustache(mustache);
-    } else if (type === "helper") {
-      this.helperMustache(mustache);
-    } else {
-      this.ambiguousMustache(mustache);
-    }
-
-    if(mustache.escaped && !options.noEscape) {
-      this.opcode('appendEscaped');
-    } else {
-      this.opcode('append');
-    }
-  },
-
-  ambiguousMustache: function(mustache, program, inverse) {
-    var id = mustache.id,
-        name = id.parts[0],
-        isBlock = program != null || inverse != null;
-
-    this.opcode('getContext', id.depth);
-
-    this.opcode('pushProgram', program);
-    this.opcode('pushProgram', inverse);
-
-    this.opcode('invokeAmbiguous', name, isBlock);
-  },
-
-  simpleMustache: function(mustache) {
-    var id = mustache.id;
-
-    if (id.type === 'DATA') {
-      this.DATA(id);
-    } else if (id.parts.length) {
-      this.ID(id);
-    } else {
-      // Simplified ID for `this`
-      this.addDepth(id.depth);
-      this.opcode('getContext', id.depth);
-      this.opcode('pushContext');
-    }
-
-    this.opcode('resolvePossibleLambda');
-  },
-
-  helperMustache: function(mustache, program, inverse) {
-    var params = this.setupFullMustacheParams(mustache, program, inverse),
-        name = mustache.id.parts[0];
-
-    if (this.options.knownHelpers[name]) {
-      this.opcode('invokeKnownHelper', params.length, name);
-    } else if (this.options.knownHelpersOnly) {
-      throw new Error("You specified knownHelpersOnly, but used the unknown helper " + name);
-    } else {
-      this.opcode('invokeHelper', params.length, name);
-    }
-  },
-
-  ID: function(id) {
-    this.addDepth(id.depth);
-    this.opcode('getContext', id.depth);
-
-    var name = id.parts[0];
-    if (!name) {
-      this.opcode('pushContext');
-    } else {
-      this.opcode('lookupOnContext', id.parts[0]);
-    }
-
-    for(var i=1, l=id.parts.length; i<l; i++) {
-      this.opcode('lookup', id.parts[i]);
-    }
-  },
-
-  DATA: function(data) {
-    this.options.data = true;
-    if (data.id.isScoped || data.id.depth) {
-      throw new Handlebars.Exception('Scoped data references are not supported: ' + data.original);
-    }
-
-    this.opcode('lookupData');
-    var parts = data.id.parts;
-    for(var i=0, l=parts.length; i<l; i++) {
-      this.opcode('lookup', parts[i]);
-    }
-  },
-
-  STRING: function(string) {
-    this.opcode('pushString', string.string);
-  },
-
-  INTEGER: function(integer) {
-    this.opcode('pushLiteral', integer.integer);
-  },
-
-  BOOLEAN: function(bool) {
-    this.opcode('pushLiteral', bool.bool);
-  },
-
-  comment: function() {},
-
-  // HELPERS
-  opcode: function(name) {
-    this.opcodes.push({ opcode: name, args: [].slice.call(arguments, 1) });
-  },
-
-  declare: function(name, value) {
-    this.opcodes.push({ opcode: 'DECLARE', name: name, value: value });
-  },
-
-  addDepth: function(depth) {
-    if(isNaN(depth)) { throw new Error("EWOT"); }
-    if(depth === 0) { return; }
-
-    if(!this.depths[depth]) {
-      this.depths[depth] = true;
-      this.depths.list.push(depth);
-    }
-  },
-
-  classifyMustache: function(mustache) {
-    var isHelper   = mustache.isHelper;
-    var isEligible = mustache.eligibleHelper;
-    var options    = this.options;
-
-    // if ambiguous, we can possibly resolve the ambiguity now
-    if (isEligible && !isHelper) {
-      var name = mustache.id.parts[0];
-
-      if (options.knownHelpers[name]) {
-        isHelper = true;
-      } else if (options.knownHelpersOnly) {
-        isEligible = false;
-      }
-    }
-
-    if (isHelper) { return "helper"; }
-    else if (isEligible) { return "ambiguous"; }
-    else { return "simple"; }
-  },
-
-  pushParams: function(params) {
-    var i = params.length, param;
-
-    while(i--) {
-      param = params[i];
-
-      if(this.options.stringParams) {
-        if(param.depth) {
-          this.addDepth(param.depth);
-        }
-
-        this.opcode('getContext', param.depth || 0);
-        this.opcode('pushStringParam', param.stringModeValue, param.type);
-      } else {
-        this[param.type](param);
-      }
-    }
-  },
-
-  setupMustacheParams: function(mustache) {
-    var params = mustache.params;
-    this.pushParams(params);
-
-    if(mustache.hash) {
-      this.hash(mustache.hash);
-    } else {
-      this.opcode('emptyHash');
-    }
-
-    return params;
-  },
-
-  // this will replace setupMustacheParams when we're done
-  setupFullMustacheParams: function(mustache, program, inverse) {
-    var params = mustache.params;
-    this.pushParams(params);
-
-    this.opcode('pushProgram', program);
-    this.opcode('pushProgram', inverse);
-
-    if(mustache.hash) {
-      this.hash(mustache.hash);
-    } else {
-      this.opcode('emptyHash');
-    }
-
-    return params;
-  }
-};
-
-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<l; this.i++) {
-      opcode = opcodes[this.i];
-
-      if(opcode.opcode === 'DECLARE') {
-        this[opcode.name] = opcode.value;
-      } else {
-        this[opcode.opcode].apply(this, opcode.args);
-      }
-    }
-
-    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 = [];
-
-    if (!this.isChild) {
-      var namespace = this.namespace;
-
-      var copies = "helpers = this.merge(helpers, " + namespace + ".helpers);";
-      if (this.environment.usePartial) { copies = copies + " partials = this.merge(partials, " + namespace + ".partials);"; }
-      if (this.options.data) { copies = copies + " data = data || {};"; }
-      out.push(copies);
-    } else {
-      out.push('');
-    }
-
-    if (!this.environment.isSimple) {
-      out.push(", buffer = " + this.initializeBuffer());
-    } else {
-      out.push("");
-    }
-
-    // track the last context pushed into place to allow skipping the
-    // getContext opcode when it would be a noop
-    this.lastContext = 0;
-    this.source = out;
-  },
-
-  createFunctionContext: function(asObject) {
-    var locals = this.stackVars.concat(this.registers.list);
-
-    if(locals.length > 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<l; i++) {
-      params.push("depth" + this.environment.depths.list[i]);
-    }
-
-    // Perform a second pass over the output to merge content when possible
-    var source = this.mergeSource();
-
-    if (!this.isChild) {
-      var revision = Handlebars.COMPILER_REVISION,
-          versions = Handlebars.REVISION_CHANGES[revision];
-      source = "this.compilerInfo = ["+revision+",'"+versions+"'];\n"+source;
-    }
-
-    if (asObject) {
-      params.push(source);
-
-      return Function.apply(this, params);
-    } else {
-      var functionSource = 'function ' + (this.name || '') + '(' + params.join(',') + ') {\n  ' + source + '}';
-      Handlebars.log(Handlebars.logger.DEBUG, functionSource + "\n\n");
-      return functionSource;
-    }
-  },
-  mergeSource: function() {
-    // WARN: We are not handling the case where buffer is still populated as the source should
-    // not have buffer append operations as their final action.
-    var source = '',
-        buffer;
-    for (var i = 0, len = this.source.length; i < len; i++) {
-      var line = this.source[i];
-      if (line.appendToBuffer) {
-        if (buffer) {
-          buffer = buffer + '\n    + ' + line.content;
-        } else {
-          buffer = line.content;
-        }
-      } else {
-        if (buffer) {
-          source += 'buffer += ' + buffer + ';\n  ';
-          buffer = undefined;
-        }
-        source += line + '\n  ';
-      }
-    }
-    return source;
-  },
-
-  // [blockValue]
-  //
-  // On stack, before: hash, inverse, program, value
-  // On stack, after: return value of blockHelperMissing
-  //
-  // The purpose of this opcode is to take a block of the form
-  // `{{#foo}}...{{/foo}}`, resolve the value of `foo`, and
-  // replace it on the stack with the result of properly
-  // invoking blockHelperMissing.
-  blockValue: function() {
-    this.context.aliases.blockHelperMissing = 'helpers.blockHelperMissing';
-
-    var params = ["depth0"];
-    this.setupParams(0, params);
-
-    this.replaceStack(function(current) {
-      params.splice(1, 0, current);
-      return "blockHelperMissing.call(" + params.join(", ") + ")";
-    });
-  },
-
-  // [ambiguousBlockValue]
-  //
-  // On stack, before: hash, inverse, program, value
-  // Compiler value, before: lastHelper=value of last found helper, if any
-  // On stack, after, if no lastHelper: same as [blockValue]
-  // On stack, after, if lastHelper: value
-  ambiguousBlockValue: function() {
-    this.context.aliases.blockHelperMissing = 'helpers.blockHelperMissing';
-
-    var params = ["depth0"];
-    this.setupParams(0, params);
-
-    var current = this.topStack();
-    params.splice(1, 0, current);
-
-    // Use the options value generated from the invocation
-    params[params.length-1] = 'options';
-
-    this.source.push("if (!" + this.lastHelper + ") { " + current + " = blockHelperMissing.call(" + params.join(", ") + "); }");
-  },
-
-  // [appendContent]
-  //
-  // On stack, before: ...
-  // On stack, after: ...
-  //
-  // Appends the string value of `content` to the current buffer
-  appendContent: function(content) {
-    this.source.push(this.appendToBuffer(this.quotedString(content)));
-  },
-
-  // [append]
-  //
-  // On stack, before: value, ...
-  // On stack, after: ...
-  //
-  // Coerces `value` to a String and appends it to the current buffer.
-  //
-  // If `value` is truthy, or 0, it is coerced into a string and appended
-  // Otherwise, the empty string is appended
-  append: function() {
-    // Force anything that is inlined onto the stack so we don't have duplication
-    // when we examine local
-    this.flushInline();
-    var local = this.popStack();
-    this.source.push("if(" + local + " || " + local + " === 0) { " + this.appendToBuffer(local) + " }");
-    if (this.environment.isSimple) {
-      this.source.push("else { " + this.appendToBuffer("''") + " }");
-    }
-  },
-
-  // [appendEscaped]
-  //
-  // On stack, before: value, ...
-  // On stack, after: ...
-  //
-  // Escape `value` and append it to the buffer
-  appendEscaped: function() {
-    this.context.aliases.escapeExpression = 'this.escapeExpression';
-
-    this.source.push(this.appendToBuffer("escapeExpression(" + this.popStack() + ")"));
-  },
-
-  // [getContext]
-  //
-  // On stack, before: ...
-  // On stack, after: ...
-  // Compiler value, after: lastContext=depth
-  //
-  // Set the value of the `lastContext` compiler value to the depth
-  getContext: function(depth) {
-    if(this.lastContext !== depth) {
-      this.lastContext = depth;
-    }
-  },
-
-  // [lookupOnContext]
-  //
-  // On stack, before: ...
-  // On stack, after: currentContext[name], ...
-  //
-  // Looks up the value of `name` on the current context and pushes
-  // it onto the stack.
-  lookupOnContext: function(name) {
-    this.push(this.nameLookup('depth' + this.lastContext, name, 'context'));
-  },
-
-  // [pushContext]
-  //
-  // On stack, before: ...
-  // On stack, after: currentContext, ...
-  //
-  // Pushes the value of the current context onto the stack.
-  pushContext: function() {
-    this.pushStackLiteral('depth' + this.lastContext);
-  },
-
-  // [resolvePossibleLambda]
-  //
-  // On stack, before: value, ...
-  // On stack, after: resolved value, ...
-  //
-  // If the `value` is a lambda, replace it on the stack by
-  // the return value of the lambda
-  resolvePossibleLambda: function() {
-    this.context.aliases.functionType = '"function"';
-
-    this.replaceStack(function(current) {
-      return "typeof " + current + " === functionType ? " + current + ".apply(depth0) : " + current;
-    });
-  },
-
-  // [lookup]
-  //
-  // On stack, before: value, ...
-  // On stack, after: value[name], ...
-  //
-  // Replace the value on the stack with the result of looking
-  // up `name` on `value`
-  lookup: function(name) {
-    this.replaceStack(function(current) {
-      return current + " == null || " + current + " === false ? " + current + " : " + this.nameLookup(current, name, 'context');
-    });
-  },
-
-  // [lookupData]
-  //
-  // On stack, before: ...
-  // On stack, after: data[id], ...
-  //
-  // Push the result of looking up `id` on the current data
-  lookupData: function(id) {
-    this.push('data');
-  },
-
-  // [pushStringParam]
-  //
-  // On stack, before: ...
-  // On stack, after: string, currentContext, ...
-  //
-  // This opcode is designed for use in string mode, which
-  // provides the string value of a parameter along with its
-  // depth rather than resolving it immediately.
-  pushStringParam: function(string, type) {
-    this.pushStackLiteral('depth' + this.lastContext);
-
-    this.pushString(type);
-
-    if (typeof string === 'string') {
-      this.pushString(string);
-    } else {
-      this.pushStackLiteral(string);
-    }
-  },
-
-  emptyHash: function() {
-    this.pushStackLiteral('{}');
-
-    if (this.options.stringParams) {
-      this.register('hashTypes', '{}');
-      this.register('hashContexts', '{}');
-    }
-  },
-  pushHash: function() {
-    this.hash = {values: [], types: [], contexts: []};
-  },
-  popHash: function() {
-    var hash = this.hash;
-    this.hash = undefined;
-
-    if (this.options.stringParams) {
-      this.register('hashContexts', '{' + hash.contexts.join(',') + '}');
-      this.register('hashTypes', '{' + hash.types.join(',') + '}');
-    }
-    this.push('{\n    ' + hash.values.join(',\n    ') + '\n  }');
-  },
-
-  // [pushString]
-  //
-  // On stack, before: ...
-  // On stack, after: quotedString(string), ...
-  //
-  // Push a quoted version of `string` onto the stack
-  pushString: function(string) {
-    this.pushStackLiteral(this.quotedString(string));
-  },
-
-  // [push]
-  //
-  // On stack, before: ...
-  // On stack, after: expr, ...
-  //
-  // Push an expression onto the stack
-  push: function(expr) {
-    this.inlineStack.push(expr);
-    return expr;
-  },
-
-  // [pushLiteral]
-  //
-  // On stack, before: ...
-  // On stack, after: value, ...
-  //
-  // Pushes a value onto the stack. This operation prevents
-  // the compiler from creating a temporary variable to hold
-  // it.
-  pushLiteral: function(value) {
-    this.pushStackLiteral(value);
-  },
-
-  // [pushProgram]
-  //
-  // On stack, before: ...
-  // On stack, after: program(guid), ...
-  //
-  // Push a program expression onto the stack. This takes
-  // a compile-time guid and converts it into a runtime-accessible
-  // expression.
-  pushProgram: function(guid) {
-    if (guid != null) {
-      this.pushStackLiteral(this.programExpression(guid));
-    } else {
-      this.pushStackLiteral(null);
-    }
-  },
-
-  // [invokeHelper]
-  //
-  // On stack, before: hash, inverse, program, params..., ...
-  // On stack, after: result of helper invocation
-  //
-  // Pops off the helper's parameters, invokes the helper,
-  // and pushes the helper's return value onto the stack.
-  //
-  // If the helper is not found, `helperMissing` is called.
-  invokeHelper: function(paramSize, name) {
-    this.context.aliases.helperMissing = 'helpers.helperMissing';
-
-    var helper = this.lastHelper = this.setupHelper(paramSize, name, true);
-    var nonHelper = this.nameLookup('depth' + this.lastContext, name, 'context');
-
-    this.push(helper.name + ' || ' + nonHelper);
-    this.replaceStack(function(name) {
-      return name + ' ? ' + name + '.call(' +
-          helper.callParams + ") " + ": helperMissing.call(" +
-          helper.helperMissingParams + ")";
-    });
-  },
-
-  // [invokeKnownHelper]
-  //
-  // On stack, before: hash, inverse, program, params..., ...
-  // On stack, after: result of helper invocation
-  //
-  // This operation is used when the helper is known to exist,
-  // so a `helperMissing` fallback is not required.
-  invokeKnownHelper: function(paramSize, name) {
-    var helper = this.setupHelper(paramSize, name);
-    this.push(helper.name + ".call(" + helper.callParams + ")");
-  },
-
-  // [invokeAmbiguous]
-  //
-  // On stack, before: hash, inverse, program, params..., ...
-  // On stack, after: result of disambiguation
-  //
-  // This operation is used when an expression like `{{foo}}`
-  // is provided, but we don't know at compile-time whether it
-  // is a helper or a path.
-  //
-  // This operation emits more code than the other options,
-  // and can be avoided by passing the `knownHelpers` and
-  // `knownHelpersOnly` flags at compile-time.
-  invokeAmbiguous: function(name, helperCall) {
-    this.context.aliases.functionType = '"function"';
-
-    this.pushStackLiteral('{}');    // Hash value
-    var helper = this.setupHelper(0, name, helperCall);
-
-    var helperName = this.lastHelper = this.nameLookup('helpers', name, 'helper');
-
-    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 + '.apply(depth0) : ' + nextStack + '; }');
-  },
-
-  // [invokePartial]
-  //
-  // On stack, before: context, ...
-  // On stack after: result of partial invocation
-  //
-  // This operation pops off a context, invokes a partial with that context,
-  // and pushes the result of the invocation back.
-  invokePartial: function(name) {
-    var params = [this.nameLookup('partials', name, 'partial'), "'" + name + "'", this.popStack(), "helpers", "partials"];
-
-    if (this.options.data) {
-      params.push("data");
-    }
-
-    this.context.aliases.self = "this";
-    this.push("self.invokePartial(" + params.join(", ") + ")");
-  },
-
-  // [assignToHash]
-  //
-  // On stack, before: value, hash, ...
-  // On stack, after: hash, ...
-  //
-  // Pops a value and hash off the stack, assigns `hash[key] = value`
-  // and pushes the hash back onto the stack.
-  assignToHash: function(key) {
-    var value = this.popStack(),
-        context,
-        type;
-
-    if (this.options.stringParams) {
-      type = this.popStack();
-      context = this.popStack();
-    }
-
-    var hash = this.hash;
-    if (context) {
-      hash.contexts.push("'" + key + "': " + context);
-    }
-    if (type) {
-      hash.types.push("'" + key + "': " + type);
-    }
-    hash.values.push("'" + key + "': (" + value + ")");
-  },
-
-  // HELPERS
-
-  compiler: JavaScriptCompiler,
-
-  compileChildren: function(environment, options) {
-    var children = environment.children, child, compiler;
-
-    for(var i=0, l=children.length; i<l; i++) {
-      child = children[i];
-      compiler = new this.compiler();
-
-      var index = this.matchExistingProgram(child);
-
-      if (index == null) {
-        this.context.programs.push('');     // Placeholder to prevent name conflicts for nested children
-        index = this.context.programs.length;
-        child.index = index;
-        child.name = 'program' + index;
-        this.context.programs[index] = compiler.compile(child, options, this.context);
-        this.context.environments[index] = child;
-      } else {
-        child.index = index;
-        child.name = 'program' + index;
-      }
-    }
-  },
-  matchExistingProgram: function(child) {
-    for (var i = 0, len = this.context.environments.length; i < len; i++) {
-      var environment = this.context.environments[i];
-      if (environment && environment.equals(child)) {
-        return i;
-      }
-    }
-  },
-
-  programExpression: function(guid) {
-    this.context.aliases.self = "this";
-
-    if(guid == null) {
-      return "self.noop";
-    }
-
-    var child = this.environment.children[guid],
-        depths = child.depths.list, depth;
-
-    var programParams = [child.index, child.name, "data"];
-
-    for(var i=0, l = depths.length; i<l; i++) {
-      depth = depths[i];
-
-      if(depth === 1) { programParams.push("depth0"); }
-      else { programParams.push("depth" + (depth - 1)); }
-    }
-
-    return (depths.length === 0 ? "self.program(" : "self.programWithDepth(") + programParams.join(", ") + ")";
-  },
-
-  register: function(name, val) {
-    this.useRegister(name);
-    this.source.push(name + " = " + val + ";");
-  },
-
-  useRegister: function(name) {
-    if(!this.registers[name]) {
-      this.registers[name] = true;
-      this.registers.list.push(name);
-    }
-  },
-
-  pushStackLiteral: function(item) {
-    return this.push(new Literal(item));
-  },
-
-  pushStack: function(item) {
-    this.flushInline();
-
-    var stack = this.incrStack();
-    if (item) {
-      this.source.push(stack + " = " + item + ";");
-    }
-    this.compileStack.push(stack);
-    return stack;
-  },
-
-  replaceStack: function(callback) {
-    var prefix = '',
-        inline = this.isInline(),
-        stack;
-
-    // If we are currently inline then we want to merge the inline statement into the
-    // replacement statement via ','
-    if (inline) {
-      var top = this.popStack(true);
-
-      if (top instanceof Literal) {
-        // Literals do not need to be inlined
-        stack = top.value;
-      } else {
-        // Get or create the current stack name for use by the inline
-        var name = this.stackSlot ? this.topStackName() : this.incrStack();
-
-        prefix = '(' + this.push(name) + ' = ' + top + '),';
-        stack = this.topStack();
-      }
-    } else {
-      stack = this.topStack();
-    }
-
-    var item = callback.call(this, stack);
-
-    if (inline) {
-      if (this.inlineStack.length || this.compileStack.length) {
-        this.popStack();
-      }
-      this.push('(' + prefix + item + ')');
-    } else {
-      // Prevent modification of the context depth variable. Through replaceStack
-      if (!/^stack/.test(stack)) {
-        stack = this.nextStack();
-      }
-
-      this.source.push(stack + " = (" + prefix + item + ");");
-    }
-    return stack;
-  },
-
-  nextStack: function() {
-    return this.pushStack();
-  },
-
-  incrStack: function() {
-    this.stackSlot++;
-    if(this.stackSlot > 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<paramSize; i++) {
-      param = this.popStack();
-      params.push(param);
-
-      if(this.options.stringParams) {
-        types.push(this.popStack());
-        contexts.push(this.popStack());
-      }
-    }
-
-    if (this.options.stringParams) {
-      options.push("contexts:[" + contexts.join(",") + "]");
-      options.push("types:[" + types.join(",") + "]");
-      options.push("hashContexts:hashContexts");
-      options.push("hashTypes:hashTypes");
-    }
-
-    if(this.options.data) {
-      options.push("data:data");
-    }
-
-    options = "{" + options.join(",") + "}";
-    if (useRegister) {
-      this.register('options', options);
-      params.push('options');
-    } else {
-      params.push(options);
-    }
-    return params.join(", ");
-  }
-};
-
-var reservedWords = (
-  "break else new var" +
-  " case finally return void" +
-  " catch for switch while" +
-  " continue function this with" +
-  " default if throw" +
-  " delete in try" +
-  " do instanceof typeof" +
-  " abstract enum int short" +
-  " boolean export interface static" +
-  " byte extends long super" +
-  " char final native synchronized" +
-  " class float package throws" +
-  " const goto private transient" +
-  " debugger implements protected volatile" +
-  " double import public let yield"
-).split(" ");
-
-var compilerWords = JavaScriptCompiler.RESERVED_WORDS = {};
-
-for(var i=0, l=reservedWords.length; i<l; i++) {
-  compilerWords[reservedWords[i]] = true;
-}
-
-JavaScriptCompiler.isValidJavaScriptVariableName = function(name) {
-  if(!JavaScriptCompiler.RESERVED_WORDS[name] && /^[a-zA-Z_$][0-9a-zA-Z_$]+$/.test(name)) {
-    return true;
-  }
-  return false;
-};
-
-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 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 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/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);
-;
diff --git a/dist/handlebars.runtime.js b/dist/handlebars.runtime.js
deleted file mode 100644
index b9f4e4920..000000000
--- a/dist/handlebars.runtime.js
+++ /dev/null
@@ -1,362 +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<j; i++) {
-        if (data) { data.index = i; }
-        ret = ret + fn(context[i], { data: data });
-      }
-    } else {
-      for(var key in context) {
-        if(context.hasOwnProperty(key)) {
-          if(data) { data.key = key; }
-          ret = ret + fn(context[key], {data: data});
-          i++;
-        }
-      }
-    }
-  }
-
-  if(i === 0){
-    ret = inverse(this);
-  }
-
-  return ret;
-});
-
-Handlebars.registerHelper('if', function(conditional, options) {
-  var type = toString.call(conditional);
-  if(type === functionType) { conditional = conditional.call(this); }
-
-  if(!conditional || Handlebars.Utils.isEmpty(conditional)) {
-    return options.inverse(this);
-  } else {
-    return options.fn(this);
-  }
-});
-
-Handlebars.registerHelper('unless', function(conditional, options) {
-  return Handlebars.helpers['if'].call(this, conditional, {fn: options.inverse, inverse: options.fn});
-});
-
-Handlebars.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);
-});
-
-Handlebars.registerHelper('log', function(context, options) {
-  var level = options.data && options.data.level != null ? parseInt(options.data.level, 10) : 1;
-  Handlebars.log(level, context);
-});
-;
-// 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 = {
-  "&": "&amp;",
-  "<": "&lt;",
-  ">": "&gt;",
-  '"': "&quot;",
-  "'": "&#x27;",
-  "`": "&#x60;"
-};
-
-var badChars = /[&<>"'`]/g;
-var possible = /[&<>"'`]/;
-
-var escapeChar = function(chr) {
-  return escape[chr] || "&amp;";
-};
-
-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 <kpdecker@gmail.com>
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 <kpdecker@gmail.com>
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 <kpdecker@gmail.com>
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 <kpdecker@gmail.com>
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: "&\"'`\\<>"}, '&amp;&quot;&#x27;&#x60;\\&lt;&gt;',
+          "by default expressions should be escaped");
+
+   shouldCompileTo("{{awesome}}", {awesome: "Escaped, <b> looks like: &lt;b&gt;"}, 'Escaped, &lt;b&gt; looks like: &amp;lt;b&amp;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: {"<b>#1</b>": {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 = "&lt;b&gt;#1&lt;/b&gt;. goodbye! 2. GOODBYE! cruel world!";
+      var expected2 = "2. GOODBYE! &lt;b&gt;#1&lt;/b&gt;. 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 "<a href='" + prefix + "/" + this.url + "'>" + this.text + "</a>";
+    }};
+    shouldCompileTo(string, [hash, helpers], "<a href='/root/goodbye'>Goodbye</a>");
+  });
+
+  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 "<a href='" + prefix + "/" + this.url + "'>" + options.fn(this) + "</a>";
+    }};
+    shouldCompileToWithPartials(string, [hash, helpers], false, "<a href='/root/goodbye'>Goodbye</a>");
+  });
+
+  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 "<a href='" + prefix + "/" + this.url + "'>" + options.fn(this) + "</a>";
+    }};
+    shouldCompileToWithPartials(string, [hash, helpers], true, "<a href='/root/goodbye'>Goodbye</a>");
+  });
+
+  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}}<p>{{name}}</p>{{/form}}";
+    var template = CompilerContext.compile(string);
+
+    var result = template({name: "Yehuda"}, {helpers: {form: function(options) { return "<form>" + options.fn(this) + "</form>"; } }});
+    equal(result, "<form><p>Yehuda</p></form>", "Block helper executed with current context");
+  });
+
+  it("block helper should have context in this", function() {
+    var source = "<ul>{{#people}}<li>{{#link}}{{name}}{{/link}}</li>{{/people}}</ul>";
+    var link = function(options) {
+      return '<a href="/people/' + this.id + '">' + options.fn(this) + '</a>';
+    };
+    var data = { "people": [
+      { "name": "Alan", "id": 1 },
+      { "name": "Yehuda", "id": 2 }
+    ]};
+
+    shouldCompileTo(source, [data, {link: link}], "<ul><li><a href=\"/people/1\">Alan</a></li><li><a href=\"/people/2\">Yehuda</a></li></ul>");
+  });
+
+  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}}<p>{{name}}</p>{{/form}}";
+    var template = CompilerContext.compile(string);
+
+    var result = template({yehuda: {name: "Yehuda"}}, { helpers: {form: function(context, options) { return "<form>" + options.fn(context) + "</form>"; }}});
+    equal(result, "<form><p>Yehuda</p></form>", "Context variable resolved");
+  });
+
+  it("block helper passing a complex path context", function() {
+    var string   = "{{#form yehuda/cat}}<p>{{name}}</p>{{/form}}";
+    var template = CompilerContext.compile(string);
+
+    var result = template({yehuda: {name: "Yehuda", cat: {name: "Harold"}}}, { helpers: {form: function(context, options) { return "<form>" + options.fn(context) + "</form>"; }}});
+    equal(result, "<form><p>Harold</p></form>", "Complex path variable resolved");
+  });
+
+  it("nested block helpers", function() {
+    var string   = "{{#form yehuda}}<p>{{name}}</p>{{#link}}Hello{{/link}}{{/form}}";
+    var template = CompilerContext.compile(string);
+
+    var result = template({
+      yehuda: {name: "Yehuda" }
+    }, {
+      helpers: {
+        link: function(options) { return "<a href='" + this.name + "'>" + options.fn(this) + "</a>"; },
+        form: function(context, options) { return "<form>" + options.fn(context) + "</form>"; }
+      }
+    });
+    equal(result, "<form><p>Yehuda</p><a href='Yehuda'>Hello</a></form>", "Both blocks executed");
+  });
+
+  it("block helper inverted sections", function() {
+    var string = "{{#list people}}{{name}}{{^}}<em>Nobody's here</em>{{/list}}";
+    var list = function(context, options) {
+      if (context.length > 0) {
+        var out = "<ul>";
+        for(var i = 0,j=context.length; i < j; i++) {
+          out += "<li>";
+          out += options.fn(context[i]);
+          out += "</li>";
+        }
+        out += "</ul>";
+        return out;
+      } else {
+        return "<p>" + options.inverse(this) + "</p>";
+      }
+    };
+
+    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 }], "<ul><li>Alan</li><li>Yehuda</li></ul>", "an inverse wrapper is passed in as a new context");
+    shouldCompileTo(string, [empty, { list: list }], "<p><em>Nobody's here</em></p>", "an inverse wrapper can be optionally called");
+    shouldCompileTo(messageString, [rootMessage, { list: list }], "<p>Nobody&#x27;s here</p>", "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("<a>" + context + "</a>");
+          }
+        }
+      };
+
+      shouldCompileTo(string, [context, helpers], "Hello <a>world</a>");
+    });
+  });
+
+  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 = "<a href='{{url}}'>{{url}}</a>";
+    var hash = {dudes: [{name: "Yehuda", url: "http://yehuda"}, {name: "Alan", url: "http://alan"}]};
+    shouldCompileToWithPartials(string, [hash, {}, {dude: dude, url: url}], true, "Dudes: Yehuda <a href='http://yehuda'>http://yehuda</a> Alan <a href='http://alan'>http://alan</a> ", "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: "&\"'`\\<>"}, '&amp;&quot;&#x27;&#x60;\\&lt;&gt;',
-        "by default expressions should be escaped");
-
- shouldCompileTo("{{awesome}}", {awesome: "Escaped, <b> looks like: &lt;b&gt;"}, 'Escaped, &lt;b&gt; looks like: &amp;lt;b&amp;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 "<a href='" + prefix + "/" + this.url + "'>" + this.text + "</a>";
-  }};
-  shouldCompileTo(string, [hash, helpers], "<a href='/root/goodbye'>Goodbye</a>");
-});
-
-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 "<a href='" + prefix + "/" + this.url + "'>" + options.fn(this) + "</a>";
-  }};
-  shouldCompileToWithPartials(string, [hash, helpers], false, "<a href='/root/goodbye'>Goodbye</a>");
-});
-
-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 "<a href='" + prefix + "/" + this.url + "'>" + options.fn(this) + "</a>";
-  }};
-  shouldCompileToWithPartials(string, [hash, helpers], true, "<a href='/root/goodbye'>Goodbye</a>");
-});
-
-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}}<p>{{name}}</p>{{/form}}";
-  var template = CompilerContext.compile(string);
-
-  var result = template({name: "Yehuda"}, {helpers: {form: function(options) { return "<form>" + options.fn(this) + "</form>"; } }});
-  equal(result, "<form><p>Yehuda</p></form>", "Block helper executed with current context");
-});
-
-test("block helper should have context in this", function() {
-  var source = "<ul>{{#people}}<li>{{#link}}{{name}}{{/link}}</li>{{/people}}</ul>";
-  var link = function(options) {
-    return '<a href="/people/' + this.id + '">' + options.fn(this) + '</a>';
-  };
-  var data = { "people": [
-    { "name": "Alan", "id": 1 },
-    { "name": "Yehuda", "id": 2 }
-  ]};
-
-  shouldCompileTo(source, [data, {link: link}], "<ul><li><a href=\"/people/1\">Alan</a></li><li><a href=\"/people/2\">Yehuda</a></li></ul>");
-});
-
-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}}<p>{{name}}</p>{{/form}}";
-  var template = CompilerContext.compile(string);
-
-  var result = template({yehuda: {name: "Yehuda"}}, { helpers: {form: function(context, options) { return "<form>" + options.fn(context) + "</form>"; }}});
-  equal(result, "<form><p>Yehuda</p></form>", "Context variable resolved");
-});
-
-test("block helper passing a complex path context", function() {
-  var string   = "{{#form yehuda/cat}}<p>{{name}}</p>{{/form}}";
-  var template = CompilerContext.compile(string);
-
-  var result = template({yehuda: {name: "Yehuda", cat: {name: "Harold"}}}, { helpers: {form: function(context, options) { return "<form>" + options.fn(context) + "</form>"; }}});
-  equal(result, "<form><p>Harold</p></form>", "Complex path variable resolved");
-});
-
-test("nested block helpers", function() {
-  var string   = "{{#form yehuda}}<p>{{name}}</p>{{#link}}Hello{{/link}}{{/form}}";
-  var template = CompilerContext.compile(string);
-
-  var result = template({
-    yehuda: {name: "Yehuda" }
-  }, {
-    helpers: {
-      link: function(options) { return "<a href='" + this.name + "'>" + options.fn(this) + "</a>"; },
-      form: function(context, options) { return "<form>" + options.fn(context) + "</form>"; }
-    }
-  });
-  equal(result, "<form><p>Yehuda</p><a href='Yehuda'>Hello</a></form>", "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}}{{^}}<em>Nobody's here</em>{{/list}}";
-  var list = function(context, options) {
-    if (context.length > 0) {
-      var out = "<ul>";
-      for(var i = 0,j=context.length; i < j; i++) {
-        out += "<li>";
-        out += options.fn(context[i]);
-        out += "</li>";
-      }
-      out += "</ul>";
-      return out;
-    } else {
-      return "<p>" + options.inverse(this) + "</p>";
-    }
-  };
-
-  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 }], "<ul><li>Alan</li><li>Yehuda</li></ul>", "an inverse wrapper is passed in as a new context");
-  shouldCompileTo(string, [empty, { list: list }], "<p><em>Nobody's here</em></p>", "an inverse wrapper can be optionally called");
-  shouldCompileTo(messageString, [rootMessage, { list: list }], "<p>Nobody&#x27;s here</p>", "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 = "<a href='{{url}}'>{{url}}</a>";
-  var hash = {dudes: [{name: "Yehuda", url: "http://yehuda"}, {name: "Alan", url: "http://alan"}]};
-  shouldCompileToWithPartials(string, [hash, {}, {dude: dude, url: url}], true, "Dudes: Yehuda <a href='http://yehuda'>http://yehuda</a> Alan <a href='http://alan'>http://alan</a> ", "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("<em>Sean O&#x27;Malley</em>");
-
-  shouldCompileTo('{{name}}', [{ name: name }], "<em>Sean O&#x27;Malley</em>");
-});
-
-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("<a>" + context + "</a>");
-      }
-    }
-  };
-
-  shouldCompileTo(string, [context, helpers], "Hello <a>world</a>");
-});
-
-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: {"<b>#1</b>": {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 = "&lt;b&gt;#1&lt;/b&gt;. goodbye! 2. GOODBYE! cruel world!";
-  var expected2 = "2. GOODBYE! &lt;b&gt;#1&lt;/b&gt;. 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 = "<strong>This is a slightly more complicated {{thing}}.</strong>.\n{{! Just ignore this business. }}\nCheck this out:\n{{#hasThings}}\n<ul>\n{{#things}}\n<li class={{className}}>{{word}}</li>\n{{/things}}</ul>.\n{{/hasThings}}\n{{^hasThings}}\n\n<small>Nothing to check out...</small>\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 = "<strong>This is a slightly more complicated blah.</strong>.\n\nCheck this out:\n\n<ul>\n\n<li class=one>@fat</li>\n\n<li class=two>@dhg</li>\n\n<li class=three>@sayrer</li>\n</ul>.\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&lt;&amp;&quot;&#x27;&gt;');
-  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 = "<strong>This is a slightly more complicated {{thing}}.</strong>.\n{{! Just ignore this business. }}\nCheck this out:\n{{#hasThings}}\n<ul>\n{{#things}}\n<li class={{className}}>{{word}}</li>\n{{/things}}</ul>.\n{{/hasThings}}\n{{^hasThings}}\n\n<small>Nothing to check out...</small>\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 = "<strong>This is a slightly more complicated blah.</strong>.\n\nCheck this out:\n\n<ul>\n\n<li class=one>@fat</li>\n\n<li class=two>@dhg</li>\n\n<li class=three>@sayrer</li>\n</ul>.\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("<em>Sean O&#x27;Malley</em>");
+
+      shouldCompileTo('{{name}}', [{ name: name }], "<em>Sean O&#x27;Malley</em>");
+    });
+  });
+
+  describe('#escapeExpression', function() {
+    it('shouhld escape html', function() {
+      Handlebars.Utils.escapeExpression('foo<&"\'>').should.equal('foo&lt;&amp;&quot;&#x27;&gt;');
+    });
+    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 <kpdecker@gmail.com>
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 <kpdecker@gmail.com>
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 <kpdecker@gmail.com>
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 <kpdecker@gmail.com>
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 <kpdecker@gmail.com>
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 <kpdecker@gmail.com>
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 <kpdecker@gmail.com>
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 <kpdecker@gmail.com>
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 <kpdecker@gmail.com>
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>/, "<version>#{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 <kpdecker@gmail.com>
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>/, "<version>#{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 <kpdecker@gmail.com>
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 <kpdecker@gmail.com>
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 <kpdecker@gmail.com>
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 <kpdecker@gmail.com>
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>/, "<version>#{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 <kpdecker@gmail.com>
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 <kpdecker@gmail.com>
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 <kpdecker@gmail.com>
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 <kpdecker@gmail.com>
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 <kpdecker@gmail.com>
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 <wycats@gmail.com>
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 <kpdecker@gmail.com>
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<l; this.i++) {
-      opcode = opcodes[this.i];
-
-      if(opcode.opcode === 'DECLARE') {
-        this[opcode.name] = opcode.value;
-      } else {
-        this[opcode.opcode].apply(this, opcode.args);
-      }
-    }
-
-    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 = [];
-
-    if (!this.isChild) {
-      var namespace = this.namespace;
-
-      var copies = "helpers = this.merge(helpers, " + namespace + ".helpers);";
-      if (this.environment.usePartial) { copies = copies + " partials = this.merge(partials, " + namespace + ".partials);"; }
-      if (this.options.data) { copies = copies + " data = data || {};"; }
-      out.push(copies);
-    } else {
-      out.push('');
-    }
-
-    if (!this.environment.isSimple) {
-      out.push(", buffer = " + this.initializeBuffer());
-    } else {
-      out.push("");
-    }
-
-    // track the last context pushed into place to allow skipping the
-    // getContext opcode when it would be a noop
-    this.lastContext = 0;
-    this.source = out;
-  },
-
-  createFunctionContext: function(asObject) {
-    var locals = this.stackVars.concat(this.registers.list);
-
-    if(locals.length > 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<l; i++) {
-      params.push("depth" + this.environment.depths.list[i]);
-    }
-
-    // Perform a second pass over the output to merge content when possible
-    var source = this.mergeSource();
-
-    if (!this.isChild) {
-      var revision = Handlebars.COMPILER_REVISION,
-          versions = Handlebars.REVISION_CHANGES[revision];
-      source = "this.compilerInfo = ["+revision+",'"+versions+"'];\n"+source;
-    }
-
-    if (asObject) {
-      params.push(source);
-
-      return Function.apply(this, params);
-    } else {
-      var functionSource = 'function ' + (this.name || '') + '(' + params.join(',') + ') {\n  ' + source + '}';
-      Handlebars.log(Handlebars.logger.DEBUG, functionSource + "\n\n");
-      return functionSource;
-    }
-  },
-  mergeSource: function() {
-    // WARN: We are not handling the case where buffer is still populated as the source should
-    // not have buffer append operations as their final action.
-    var source = '',
-        buffer;
-    for (var i = 0, len = this.source.length; i < len; i++) {
-      var line = this.source[i];
-      if (line.appendToBuffer) {
-        if (buffer) {
-          buffer = buffer + '\n    + ' + line.content;
-        } else {
-          buffer = line.content;
-        }
-      } else {
-        if (buffer) {
-          source += 'buffer += ' + buffer + ';\n  ';
-          buffer = undefined;
-        }
-        source += line + '\n  ';
-      }
-    }
-    return source;
-  },
-
-  // [blockValue]
-  //
-  // On stack, before: hash, inverse, program, value
-  // On stack, after: return value of blockHelperMissing
-  //
-  // The purpose of this opcode is to take a block of the form
-  // `{{#foo}}...{{/foo}}`, resolve the value of `foo`, and
-  // replace it on the stack with the result of properly
-  // invoking blockHelperMissing.
-  blockValue: function() {
-    this.context.aliases.blockHelperMissing = 'helpers.blockHelperMissing';
-
-    var params = ["depth0"];
-    this.setupParams(0, params);
-
-    this.replaceStack(function(current) {
-      params.splice(1, 0, current);
-      return "blockHelperMissing.call(" + params.join(", ") + ")";
-    });
-  },
-
-  // [ambiguousBlockValue]
-  //
-  // On stack, before: hash, inverse, program, value
-  // Compiler value, before: lastHelper=value of last found helper, if any
-  // On stack, after, if no lastHelper: same as [blockValue]
-  // On stack, after, if lastHelper: value
-  ambiguousBlockValue: function() {
-    this.context.aliases.blockHelperMissing = 'helpers.blockHelperMissing';
-
-    var params = ["depth0"];
-    this.setupParams(0, params);
-
-    var current = this.topStack();
-    params.splice(1, 0, current);
-
-    // Use the options value generated from the invocation
-    params[params.length-1] = 'options';
-
-    this.source.push("if (!" + this.lastHelper + ") { " + current + " = blockHelperMissing.call(" + params.join(", ") + "); }");
-  },
-
-  // [appendContent]
-  //
-  // On stack, before: ...
-  // On stack, after: ...
-  //
-  // Appends the string value of `content` to the current buffer
-  appendContent: function(content) {
-    this.source.push(this.appendToBuffer(this.quotedString(content)));
-  },
-
-  // [append]
-  //
-  // On stack, before: value, ...
-  // On stack, after: ...
-  //
-  // Coerces `value` to a String and appends it to the current buffer.
-  //
-  // If `value` is truthy, or 0, it is coerced into a string and appended
-  // Otherwise, the empty string is appended
-  append: function() {
-    // Force anything that is inlined onto the stack so we don't have duplication
-    // when we examine local
-    this.flushInline();
-    var local = this.popStack();
-    this.source.push("if(" + local + " || " + local + " === 0) { " + this.appendToBuffer(local) + " }");
-    if (this.environment.isSimple) {
-      this.source.push("else { " + this.appendToBuffer("''") + " }");
-    }
-  },
-
-  // [appendEscaped]
-  //
-  // On stack, before: value, ...
-  // On stack, after: ...
-  //
-  // Escape `value` and append it to the buffer
-  appendEscaped: function() {
-    this.context.aliases.escapeExpression = 'this.escapeExpression';
-
-    this.source.push(this.appendToBuffer("escapeExpression(" + this.popStack() + ")"));
-  },
-
-  // [getContext]
-  //
-  // On stack, before: ...
-  // On stack, after: ...
-  // Compiler value, after: lastContext=depth
-  //
-  // Set the value of the `lastContext` compiler value to the depth
-  getContext: function(depth) {
-    if(this.lastContext !== depth) {
-      this.lastContext = depth;
-    }
-  },
-
-  // [lookupOnContext]
-  //
-  // On stack, before: ...
-  // On stack, after: currentContext[name], ...
-  //
-  // Looks up the value of `name` on the current context and pushes
-  // it onto the stack.
-  lookupOnContext: function(name) {
-    this.push(this.nameLookup('depth' + this.lastContext, name, 'context'));
-  },
-
-  // [pushContext]
-  //
-  // On stack, before: ...
-  // On stack, after: currentContext, ...
-  //
-  // Pushes the value of the current context onto the stack.
-  pushContext: function() {
-    this.pushStackLiteral('depth' + this.lastContext);
-  },
-
-  // [resolvePossibleLambda]
-  //
-  // On stack, before: value, ...
-  // On stack, after: resolved value, ...
-  //
-  // If the `value` is a lambda, replace it on the stack by
-  // the return value of the lambda
-  resolvePossibleLambda: function() {
-    this.context.aliases.functionType = '"function"';
-
-    this.replaceStack(function(current) {
-      return "typeof " + current + " === functionType ? " + current + ".apply(depth0) : " + current;
-    });
-  },
-
-  // [lookup]
-  //
-  // On stack, before: value, ...
-  // On stack, after: value[name], ...
-  //
-  // Replace the value on the stack with the result of looking
-  // up `name` on `value`
-  lookup: function(name) {
-    this.replaceStack(function(current) {
-      return current + " == null || " + current + " === false ? " + current + " : " + this.nameLookup(current, name, 'context');
-    });
-  },
-
-  // [lookupData]
-  //
-  // On stack, before: ...
-  // On stack, after: data[id], ...
-  //
-  // Push the result of looking up `id` on the current data
-  lookupData: function(id) {
-    this.push('data');
-  },
-
-  // [pushStringParam]
-  //
-  // On stack, before: ...
-  // On stack, after: string, currentContext, ...
-  //
-  // This opcode is designed for use in string mode, which
-  // provides the string value of a parameter along with its
-  // depth rather than resolving it immediately.
-  pushStringParam: function(string, type) {
-    this.pushStackLiteral('depth' + this.lastContext);
-
-    this.pushString(type);
-
-    if (typeof string === 'string') {
-      this.pushString(string);
-    } else {
-      this.pushStackLiteral(string);
-    }
-  },
-
-  emptyHash: function() {
-    this.pushStackLiteral('{}');
-
-    if (this.options.stringParams) {
-      this.register('hashTypes', '{}');
-      this.register('hashContexts', '{}');
-    }
-  },
-  pushHash: function() {
-    this.hash = {values: [], types: [], contexts: []};
-  },
-  popHash: function() {
-    var hash = this.hash;
-    this.hash = undefined;
-
-    if (this.options.stringParams) {
-      this.register('hashContexts', '{' + hash.contexts.join(',') + '}');
-      this.register('hashTypes', '{' + hash.types.join(',') + '}');
-    }
-    this.push('{\n    ' + hash.values.join(',\n    ') + '\n  }');
-  },
-
-  // [pushString]
-  //
-  // On stack, before: ...
-  // On stack, after: quotedString(string), ...
-  //
-  // Push a quoted version of `string` onto the stack
-  pushString: function(string) {
-    this.pushStackLiteral(this.quotedString(string));
-  },
-
-  // [push]
-  //
-  // On stack, before: ...
-  // On stack, after: expr, ...
-  //
-  // Push an expression onto the stack
-  push: function(expr) {
-    this.inlineStack.push(expr);
-    return expr;
-  },
-
-  // [pushLiteral]
-  //
-  // On stack, before: ...
-  // On stack, after: value, ...
-  //
-  // Pushes a value onto the stack. This operation prevents
-  // the compiler from creating a temporary variable to hold
-  // it.
-  pushLiteral: function(value) {
-    this.pushStackLiteral(value);
-  },
-
-  // [pushProgram]
-  //
-  // On stack, before: ...
-  // On stack, after: program(guid), ...
-  //
-  // Push a program expression onto the stack. This takes
-  // a compile-time guid and converts it into a runtime-accessible
-  // expression.
-  pushProgram: function(guid) {
-    if (guid != null) {
-      this.pushStackLiteral(this.programExpression(guid));
-    } else {
-      this.pushStackLiteral(null);
-    }
-  },
-
-  // [invokeHelper]
-  //
-  // On stack, before: hash, inverse, program, params..., ...
-  // On stack, after: result of helper invocation
-  //
-  // Pops off the helper's parameters, invokes the helper,
-  // and pushes the helper's return value onto the stack.
-  //
-  // If the helper is not found, `helperMissing` is called.
-  invokeHelper: function(paramSize, name) {
-    this.context.aliases.helperMissing = 'helpers.helperMissing';
-
-    var helper = this.lastHelper = this.setupHelper(paramSize, name, true);
-    var nonHelper = this.nameLookup('depth' + this.lastContext, name, 'context');
-
-    this.push(helper.name + ' || ' + nonHelper);
-    this.replaceStack(function(name) {
-      return name + ' ? ' + name + '.call(' +
-          helper.callParams + ") " + ": helperMissing.call(" +
-          helper.helperMissingParams + ")";
-    });
-  },
-
-  // [invokeKnownHelper]
-  //
-  // On stack, before: hash, inverse, program, params..., ...
-  // On stack, after: result of helper invocation
-  //
-  // This operation is used when the helper is known to exist,
-  // so a `helperMissing` fallback is not required.
-  invokeKnownHelper: function(paramSize, name) {
-    var helper = this.setupHelper(paramSize, name);
-    this.push(helper.name + ".call(" + helper.callParams + ")");
-  },
-
-  // [invokeAmbiguous]
-  //
-  // On stack, before: hash, inverse, program, params..., ...
-  // On stack, after: result of disambiguation
-  //
-  // This operation is used when an expression like `{{foo}}`
-  // is provided, but we don't know at compile-time whether it
-  // is a helper or a path.
-  //
-  // This operation emits more code than the other options,
-  // and can be avoided by passing the `knownHelpers` and
-  // `knownHelpersOnly` flags at compile-time.
-  invokeAmbiguous: function(name, helperCall) {
-    this.context.aliases.functionType = '"function"';
-
-    this.pushStackLiteral('{}');    // Hash value
-    var helper = this.setupHelper(0, name, helperCall);
-
-    var helperName = this.lastHelper = this.nameLookup('helpers', name, 'helper');
-
-    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 + '.apply(depth0) : ' + nextStack + '; }');
-  },
-
-  // [invokePartial]
-  //
-  // On stack, before: context, ...
-  // On stack after: result of partial invocation
-  //
-  // This operation pops off a context, invokes a partial with that context,
-  // and pushes the result of the invocation back.
-  invokePartial: function(name) {
-    var params = [this.nameLookup('partials', name, 'partial'), "'" + name + "'", this.popStack(), "helpers", "partials"];
-
-    if (this.options.data) {
-      params.push("data");
-    }
-
-    this.context.aliases.self = "this";
-    this.push("self.invokePartial(" + params.join(", ") + ")");
-  },
-
-  // [assignToHash]
-  //
-  // On stack, before: value, hash, ...
-  // On stack, after: hash, ...
-  //
-  // Pops a value and hash off the stack, assigns `hash[key] = value`
-  // and pushes the hash back onto the stack.
-  assignToHash: function(key) {
-    var value = this.popStack(),
-        context,
-        type;
-
-    if (this.options.stringParams) {
-      type = this.popStack();
-      context = this.popStack();
-    }
-
-    var hash = this.hash;
-    if (context) {
-      hash.contexts.push("'" + key + "': " + context);
-    }
-    if (type) {
-      hash.types.push("'" + key + "': " + type);
-    }
-    hash.values.push("'" + key + "': (" + value + ")");
-  },
-
-  // HELPERS
-
-  compiler: JavaScriptCompiler,
-
-  compileChildren: function(environment, options) {
-    var children = environment.children, child, compiler;
-
-    for(var i=0, l=children.length; i<l; i++) {
-      child = children[i];
-      compiler = new this.compiler();
-
-      var index = this.matchExistingProgram(child);
-
-      if (index == null) {
-        this.context.programs.push('');     // Placeholder to prevent name conflicts for nested children
-        index = this.context.programs.length;
-        child.index = index;
-        child.name = 'program' + index;
-        this.context.programs[index] = compiler.compile(child, options, this.context);
-        this.context.environments[index] = child;
-      } else {
-        child.index = index;
-        child.name = 'program' + index;
-      }
-    }
-  },
-  matchExistingProgram: function(child) {
-    for (var i = 0, len = this.context.environments.length; i < len; i++) {
-      var environment = this.context.environments[i];
-      if (environment && environment.equals(child)) {
-        return i;
-      }
-    }
-  },
-
-  programExpression: function(guid) {
-    this.context.aliases.self = "this";
-
-    if(guid == null) {
-      return "self.noop";
-    }
-
-    var child = this.environment.children[guid],
-        depths = child.depths.list, depth;
-
-    var programParams = [child.index, child.name, "data"];
-
-    for(var i=0, l = depths.length; i<l; i++) {
-      depth = depths[i];
-
-      if(depth === 1) { programParams.push("depth0"); }
-      else { programParams.push("depth" + (depth - 1)); }
-    }
-
-    return (depths.length === 0 ? "self.program(" : "self.programWithDepth(") + programParams.join(", ") + ")";
-  },
-
-  register: function(name, val) {
-    this.useRegister(name);
-    this.source.push(name + " = " + val + ";");
-  },
-
-  useRegister: function(name) {
-    if(!this.registers[name]) {
-      this.registers[name] = true;
-      this.registers.list.push(name);
-    }
-  },
-
-  pushStackLiteral: function(item) {
-    return this.push(new Literal(item));
-  },
-
-  pushStack: function(item) {
-    this.flushInline();
-
-    var stack = this.incrStack();
-    if (item) {
-      this.source.push(stack + " = " + item + ";");
-    }
-    this.compileStack.push(stack);
-    return stack;
-  },
-
-  replaceStack: function(callback) {
-    var prefix = '',
-        inline = this.isInline(),
-        stack;
-
-    // If we are currently inline then we want to merge the inline statement into the
-    // replacement statement via ','
-    if (inline) {
-      var top = this.popStack(true);
-
-      if (top instanceof Literal) {
-        // Literals do not need to be inlined
-        stack = top.value;
-      } else {
-        // Get or create the current stack name for use by the inline
-        var name = this.stackSlot ? this.topStackName() : this.incrStack();
-
-        prefix = '(' + this.push(name) + ' = ' + top + '),';
-        stack = this.topStack();
-      }
-    } else {
-      stack = this.topStack();
-    }
-
-    var item = callback.call(this, stack);
-
-    if (inline) {
-      if (this.inlineStack.length || this.compileStack.length) {
-        this.popStack();
-      }
-      this.push('(' + prefix + item + ')');
-    } else {
-      // Prevent modification of the context depth variable. Through replaceStack
-      if (!/^stack/.test(stack)) {
-        stack = this.nextStack();
-      }
-
-      this.source.push(stack + " = (" + prefix + item + ");");
-    }
-    return stack;
-  },
-
-  nextStack: function() {
-    return this.pushStack();
-  },
-
-  incrStack: function() {
-    this.stackSlot++;
-    if(this.stackSlot > 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<paramSize; i++) {
-      param = this.popStack();
-      params.push(param);
-
-      if(this.options.stringParams) {
-        types.push(this.popStack());
-        contexts.push(this.popStack());
-      }
-    }
-
-    if (this.options.stringParams) {
-      options.push("contexts:[" + contexts.join(",") + "]");
-      options.push("types:[" + types.join(",") + "]");
-      options.push("hashContexts:hashContexts");
-      options.push("hashTypes:hashTypes");
-    }
-
-    if(this.options.data) {
-      options.push("data:data");
-    }
-
-    options = "{" + options.join(",") + "}";
-    if (useRegister) {
-      this.register('options', options);
-      params.push('options');
-    } else {
-      params.push(options);
-    }
-    return params.join(", ");
-  }
-};
-
-var reservedWords = (
-  "break else new var" +
-  " case finally return void" +
-  " catch for switch while" +
-  " continue function this with" +
-  " default if throw" +
-  " delete in try" +
-  " do instanceof typeof" +
-  " abstract enum int short" +
-  " boolean export interface static" +
-  " byte extends long super" +
-  " char final native synchronized" +
-  " class float package throws" +
-  " const goto private transient" +
-  " debugger implements protected volatile" +
-  " double import public let yield"
-).split(" ");
-
-var compilerWords = JavaScriptCompiler.RESERVED_WORDS = {};
-
-for(var i=0, l=reservedWords.length; i<l; i++) {
-  compilerWords[reservedWords[i]] = true;
-}
-
-JavaScriptCompiler.isValidJavaScriptVariableName = function(name) {
-  if(!JavaScriptCompiler.RESERVED_WORDS[name] && /^[a-zA-Z_$][0-9a-zA-Z_$]+$/.test(name)) {
-    return true;
-  }
-  return false;
-};
-
 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);
@@ -1266,7 +426,7 @@ Handlebars.precompile = function(input, options) {
   }
   var ast = Handlebars.parse(input);
   var environment = new Compiler().compile(ast, options);
-  return new JavaScriptCompiler().compile(environment, options);
+  return new Handlebars.JavaScriptCompiler().compile(environment, options);
 };
 
 Handlebars.compile = function(input, options) {
@@ -1282,7 +442,7 @@ Handlebars.compile = function(input, options) {
   function compile() {
     var ast = Handlebars.parse(input);
     var environment = new Compiler().compile(ast, options);
-    var templateSpec = new JavaScriptCompiler().compile(environment, options, undefined, true);
+    var templateSpec = new Handlebars.JavaScriptCompiler().compile(environment, options, undefined, true);
     return Handlebars.template(templateSpec);
   }
 
@@ -1301,5 +461,3 @@ Handlebars.compile = function(input, options) {
 return Handlebars;
 
 };
-
-
diff --git a/lib/handlebars/compiler/index.js b/lib/handlebars/compiler/index.js
index 6ed8d2190..0cc5c017a 100644
--- a/lib/handlebars/compiler/index.js
+++ b/lib/handlebars/compiler/index.js
@@ -4,12 +4,14 @@ module.exports.attach = function(Handlebars) {
 var visitor = require("./visitor"),
     printer = require("./printer"),
     ast = require("./ast"),
-    compiler = require("./compiler");
+    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/javascript-compiler.js b/lib/handlebars/compiler/javascript-compiler.js
new file mode 100644
index 000000000..4548c6aeb
--- /dev/null
+++ b/lib/handlebars/compiler/javascript-compiler.js
@@ -0,0 +1,856 @@
+var compilerbase = require("./base");
+
+exports.attach = function(Handlebars) {
+
+compilerbase.attach(Handlebars);
+
+// BEGIN(BROWSER)
+/*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
+  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(var l=opcodes.length; this.i<l; this.i++) {
+      opcode = opcodes[this.i];
+
+      if(opcode.opcode === 'DECLARE') {
+        this[opcode.name] = opcode.value;
+      } else {
+        this[opcode.opcode].apply(this, opcode.args);
+      }
+    }
+
+    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 = [];
+
+    if (!this.isChild) {
+      var namespace = this.namespace;
+
+      var copies = "helpers = this.merge(helpers, " + namespace + ".helpers);";
+      if (this.environment.usePartial) { copies = copies + " partials = this.merge(partials, " + namespace + ".partials);"; }
+      if (this.options.data) { copies = copies + " data = data || {};"; }
+      out.push(copies);
+    } else {
+      out.push('');
+    }
+
+    if (!this.environment.isSimple) {
+      out.push(", buffer = " + this.initializeBuffer());
+    } else {
+      out.push("");
+    }
+
+    // track the last context pushed into place to allow skipping the
+    // getContext opcode when it would be a noop
+    this.lastContext = 0;
+    this.source = out;
+  },
+
+  createFunctionContext: function(asObject) {
+    var locals = this.stackVars.concat(this.registers.list);
+
+    if(locals.length > 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<l; i++) {
+      params.push("depth" + this.environment.depths.list[i]);
+    }
+
+    // Perform a second pass over the output to merge content when possible
+    var source = this.mergeSource();
+
+    if (!this.isChild) {
+      var revision = Handlebars.COMPILER_REVISION,
+          versions = Handlebars.REVISION_CHANGES[revision];
+      source = "this.compilerInfo = ["+revision+",'"+versions+"'];\n"+source;
+    }
+
+    if (asObject) {
+      params.push(source);
+
+      return Function.apply(this, params);
+    } else {
+      var functionSource = 'function ' + (this.name || '') + '(' + params.join(',') + ') {\n  ' + source + '}';
+      Handlebars.log(Handlebars.logger.DEBUG, functionSource + "\n\n");
+      return functionSource;
+    }
+  },
+  mergeSource: function() {
+    // WARN: We are not handling the case where buffer is still populated as the source should
+    // not have buffer append operations as their final action.
+    var source = '',
+        buffer;
+    for (var i = 0, len = this.source.length; i < len; i++) {
+      var line = this.source[i];
+      if (line.appendToBuffer) {
+        if (buffer) {
+          buffer = buffer + '\n    + ' + line.content;
+        } else {
+          buffer = line.content;
+        }
+      } else {
+        if (buffer) {
+          source += 'buffer += ' + buffer + ';\n  ';
+          buffer = undefined;
+        }
+        source += line + '\n  ';
+      }
+    }
+    return source;
+  },
+
+  // [blockValue]
+  //
+  // On stack, before: hash, inverse, program, value
+  // On stack, after: return value of blockHelperMissing
+  //
+  // The purpose of this opcode is to take a block of the form
+  // `{{#foo}}...{{/foo}}`, resolve the value of `foo`, and
+  // replace it on the stack with the result of properly
+  // invoking blockHelperMissing.
+  blockValue: function() {
+    this.context.aliases.blockHelperMissing = 'helpers.blockHelperMissing';
+
+    var params = ["depth0"];
+    this.setupParams(0, params);
+
+    this.replaceStack(function(current) {
+      params.splice(1, 0, current);
+      return "blockHelperMissing.call(" + params.join(", ") + ")";
+    });
+  },
+
+  // [ambiguousBlockValue]
+  //
+  // On stack, before: hash, inverse, program, value
+  // Compiler value, before: lastHelper=value of last found helper, if any
+  // On stack, after, if no lastHelper: same as [blockValue]
+  // On stack, after, if lastHelper: value
+  ambiguousBlockValue: function() {
+    this.context.aliases.blockHelperMissing = 'helpers.blockHelperMissing';
+
+    var params = ["depth0"];
+    this.setupParams(0, params);
+
+    var current = this.topStack();
+    params.splice(1, 0, current);
+
+    // Use the options value generated from the invocation
+    params[params.length-1] = 'options';
+
+    this.source.push("if (!" + this.lastHelper + ") { " + current + " = blockHelperMissing.call(" + params.join(", ") + "); }");
+  },
+
+  // [appendContent]
+  //
+  // On stack, before: ...
+  // On stack, after: ...
+  //
+  // Appends the string value of `content` to the current buffer
+  appendContent: function(content) {
+    this.source.push(this.appendToBuffer(this.quotedString(content)));
+  },
+
+  // [append]
+  //
+  // On stack, before: value, ...
+  // On stack, after: ...
+  //
+  // Coerces `value` to a String and appends it to the current buffer.
+  //
+  // If `value` is truthy, or 0, it is coerced into a string and appended
+  // Otherwise, the empty string is appended
+  append: function() {
+    // Force anything that is inlined onto the stack so we don't have duplication
+    // when we examine local
+    this.flushInline();
+    var local = this.popStack();
+    this.source.push("if(" + local + " || " + local + " === 0) { " + this.appendToBuffer(local) + " }");
+    if (this.environment.isSimple) {
+      this.source.push("else { " + this.appendToBuffer("''") + " }");
+    }
+  },
+
+  // [appendEscaped]
+  //
+  // On stack, before: value, ...
+  // On stack, after: ...
+  //
+  // Escape `value` and append it to the buffer
+  appendEscaped: function() {
+    this.context.aliases.escapeExpression = 'this.escapeExpression';
+
+    this.source.push(this.appendToBuffer("escapeExpression(" + this.popStack() + ")"));
+  },
+
+  // [getContext]
+  //
+  // On stack, before: ...
+  // On stack, after: ...
+  // Compiler value, after: lastContext=depth
+  //
+  // Set the value of the `lastContext` compiler value to the depth
+  getContext: function(depth) {
+    if(this.lastContext !== depth) {
+      this.lastContext = depth;
+    }
+  },
+
+  // [lookupOnContext]
+  //
+  // On stack, before: ...
+  // On stack, after: currentContext[name], ...
+  //
+  // Looks up the value of `name` on the current context and pushes
+  // it onto the stack.
+  lookupOnContext: function(name) {
+    this.push(this.nameLookup('depth' + this.lastContext, name, 'context'));
+  },
+
+  // [pushContext]
+  //
+  // On stack, before: ...
+  // On stack, after: currentContext, ...
+  //
+  // Pushes the value of the current context onto the stack.
+  pushContext: function() {
+    this.pushStackLiteral('depth' + this.lastContext);
+  },
+
+  // [resolvePossibleLambda]
+  //
+  // On stack, before: value, ...
+  // On stack, after: resolved value, ...
+  //
+  // If the `value` is a lambda, replace it on the stack by
+  // the return value of the lambda
+  resolvePossibleLambda: function() {
+    this.context.aliases.functionType = '"function"';
+
+    this.replaceStack(function(current) {
+      return "typeof " + current + " === functionType ? " + current + ".apply(depth0) : " + current;
+    });
+  },
+
+  // [lookup]
+  //
+  // On stack, before: value, ...
+  // On stack, after: value[name], ...
+  //
+  // Replace the value on the stack with the result of looking
+  // up `name` on `value`
+  lookup: function(name) {
+    this.replaceStack(function(current) {
+      return current + " == null || " + current + " === false ? " + current + " : " + this.nameLookup(current, name, 'context');
+    });
+  },
+
+  // [lookupData]
+  //
+  // On stack, before: ...
+  // On stack, after: data[id], ...
+  //
+  // Push the result of looking up `id` on the current data
+  lookupData: function(id) {
+    this.push('data');
+  },
+
+  // [pushStringParam]
+  //
+  // On stack, before: ...
+  // On stack, after: string, currentContext, ...
+  //
+  // This opcode is designed for use in string mode, which
+  // provides the string value of a parameter along with its
+  // depth rather than resolving it immediately.
+  pushStringParam: function(string, type) {
+    this.pushStackLiteral('depth' + this.lastContext);
+
+    this.pushString(type);
+
+    if (typeof string === 'string') {
+      this.pushString(string);
+    } else {
+      this.pushStackLiteral(string);
+    }
+  },
+
+  emptyHash: function() {
+    this.pushStackLiteral('{}');
+
+    if (this.options.stringParams) {
+      this.register('hashTypes', '{}');
+      this.register('hashContexts', '{}');
+    }
+  },
+  pushHash: function() {
+    this.hash = {values: [], types: [], contexts: []};
+  },
+  popHash: function() {
+    var hash = this.hash;
+    this.hash = undefined;
+
+    if (this.options.stringParams) {
+      this.register('hashContexts', '{' + hash.contexts.join(',') + '}');
+      this.register('hashTypes', '{' + hash.types.join(',') + '}');
+    }
+    this.push('{\n    ' + hash.values.join(',\n    ') + '\n  }');
+  },
+
+  // [pushString]
+  //
+  // On stack, before: ...
+  // On stack, after: quotedString(string), ...
+  //
+  // Push a quoted version of `string` onto the stack
+  pushString: function(string) {
+    this.pushStackLiteral(this.quotedString(string));
+  },
+
+  // [push]
+  //
+  // On stack, before: ...
+  // On stack, after: expr, ...
+  //
+  // Push an expression onto the stack
+  push: function(expr) {
+    this.inlineStack.push(expr);
+    return expr;
+  },
+
+  // [pushLiteral]
+  //
+  // On stack, before: ...
+  // On stack, after: value, ...
+  //
+  // Pushes a value onto the stack. This operation prevents
+  // the compiler from creating a temporary variable to hold
+  // it.
+  pushLiteral: function(value) {
+    this.pushStackLiteral(value);
+  },
+
+  // [pushProgram]
+  //
+  // On stack, before: ...
+  // On stack, after: program(guid), ...
+  //
+  // Push a program expression onto the stack. This takes
+  // a compile-time guid and converts it into a runtime-accessible
+  // expression.
+  pushProgram: function(guid) {
+    if (guid != null) {
+      this.pushStackLiteral(this.programExpression(guid));
+    } else {
+      this.pushStackLiteral(null);
+    }
+  },
+
+  // [invokeHelper]
+  //
+  // On stack, before: hash, inverse, program, params..., ...
+  // On stack, after: result of helper invocation
+  //
+  // Pops off the helper's parameters, invokes the helper,
+  // and pushes the helper's return value onto the stack.
+  //
+  // If the helper is not found, `helperMissing` is called.
+  invokeHelper: function(paramSize, name) {
+    this.context.aliases.helperMissing = 'helpers.helperMissing';
+
+    var helper = this.lastHelper = this.setupHelper(paramSize, name, true);
+    var nonHelper = this.nameLookup('depth' + this.lastContext, name, 'context');
+
+    this.push(helper.name + ' || ' + nonHelper);
+    this.replaceStack(function(name) {
+      return name + ' ? ' + name + '.call(' +
+          helper.callParams + ") " + ": helperMissing.call(" +
+          helper.helperMissingParams + ")";
+    });
+  },
+
+  // [invokeKnownHelper]
+  //
+  // On stack, before: hash, inverse, program, params..., ...
+  // On stack, after: result of helper invocation
+  //
+  // This operation is used when the helper is known to exist,
+  // so a `helperMissing` fallback is not required.
+  invokeKnownHelper: function(paramSize, name) {
+    var helper = this.setupHelper(paramSize, name);
+    this.push(helper.name + ".call(" + helper.callParams + ")");
+  },
+
+  // [invokeAmbiguous]
+  //
+  // On stack, before: hash, inverse, program, params..., ...
+  // On stack, after: result of disambiguation
+  //
+  // This operation is used when an expression like `{{foo}}`
+  // is provided, but we don't know at compile-time whether it
+  // is a helper or a path.
+  //
+  // This operation emits more code than the other options,
+  // and can be avoided by passing the `knownHelpers` and
+  // `knownHelpersOnly` flags at compile-time.
+  invokeAmbiguous: function(name, helperCall) {
+    this.context.aliases.functionType = '"function"';
+
+    this.pushStackLiteral('{}');    // Hash value
+    var helper = this.setupHelper(0, name, helperCall);
+
+    var helperName = this.lastHelper = this.nameLookup('helpers', name, 'helper');
+
+    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 + '.apply(depth0) : ' + nextStack + '; }');
+  },
+
+  // [invokePartial]
+  //
+  // On stack, before: context, ...
+  // On stack after: result of partial invocation
+  //
+  // This operation pops off a context, invokes a partial with that context,
+  // and pushes the result of the invocation back.
+  invokePartial: function(name) {
+    var params = [this.nameLookup('partials', name, 'partial'), "'" + name + "'", this.popStack(), "helpers", "partials"];
+
+    if (this.options.data) {
+      params.push("data");
+    }
+
+    this.context.aliases.self = "this";
+    this.push("self.invokePartial(" + params.join(", ") + ")");
+  },
+
+  // [assignToHash]
+  //
+  // On stack, before: value, hash, ...
+  // On stack, after: hash, ...
+  //
+  // Pops a value and hash off the stack, assigns `hash[key] = value`
+  // and pushes the hash back onto the stack.
+  assignToHash: function(key) {
+    var value = this.popStack(),
+        context,
+        type;
+
+    if (this.options.stringParams) {
+      type = this.popStack();
+      context = this.popStack();
+    }
+
+    var hash = this.hash;
+    if (context) {
+      hash.contexts.push("'" + key + "': " + context);
+    }
+    if (type) {
+      hash.types.push("'" + key + "': " + type);
+    }
+    hash.values.push("'" + key + "': (" + value + ")");
+  },
+
+  // HELPERS
+
+  compiler: JavaScriptCompiler,
+
+  compileChildren: function(environment, options) {
+    var children = environment.children, child, compiler;
+
+    for(var i=0, l=children.length; i<l; i++) {
+      child = children[i];
+      compiler = new this.compiler();
+
+      var index = this.matchExistingProgram(child);
+
+      if (index == null) {
+        this.context.programs.push('');     // Placeholder to prevent name conflicts for nested children
+        index = this.context.programs.length;
+        child.index = index;
+        child.name = 'program' + index;
+        this.context.programs[index] = compiler.compile(child, options, this.context);
+        this.context.environments[index] = child;
+      } else {
+        child.index = index;
+        child.name = 'program' + index;
+      }
+    }
+  },
+  matchExistingProgram: function(child) {
+    for (var i = 0, len = this.context.environments.length; i < len; i++) {
+      var environment = this.context.environments[i];
+      if (environment && environment.equals(child)) {
+        return i;
+      }
+    }
+  },
+
+  programExpression: function(guid) {
+    this.context.aliases.self = "this";
+
+    if(guid == null) {
+      return "self.noop";
+    }
+
+    var child = this.environment.children[guid],
+        depths = child.depths.list, depth;
+
+    var programParams = [child.index, child.name, "data"];
+
+    for(var i=0, l = depths.length; i<l; i++) {
+      depth = depths[i];
+
+      if(depth === 1) { programParams.push("depth0"); }
+      else { programParams.push("depth" + (depth - 1)); }
+    }
+
+    return (depths.length === 0 ? "self.program(" : "self.programWithDepth(") + programParams.join(", ") + ")";
+  },
+
+  register: function(name, val) {
+    this.useRegister(name);
+    this.source.push(name + " = " + val + ";");
+  },
+
+  useRegister: function(name) {
+    if(!this.registers[name]) {
+      this.registers[name] = true;
+      this.registers.list.push(name);
+    }
+  },
+
+  pushStackLiteral: function(item) {
+    return this.push(new Literal(item));
+  },
+
+  pushStack: function(item) {
+    this.flushInline();
+
+    var stack = this.incrStack();
+    if (item) {
+      this.source.push(stack + " = " + item + ";");
+    }
+    this.compileStack.push(stack);
+    return stack;
+  },
+
+  replaceStack: function(callback) {
+    var prefix = '',
+        inline = this.isInline(),
+        stack;
+
+    // If we are currently inline then we want to merge the inline statement into the
+    // replacement statement via ','
+    if (inline) {
+      var top = this.popStack(true);
+
+      if (top instanceof Literal) {
+        // Literals do not need to be inlined
+        stack = top.value;
+      } else {
+        // Get or create the current stack name for use by the inline
+        var name = this.stackSlot ? this.topStackName() : this.incrStack();
+
+        prefix = '(' + this.push(name) + ' = ' + top + '),';
+        stack = this.topStack();
+      }
+    } else {
+      stack = this.topStack();
+    }
+
+    var item = callback.call(this, stack);
+
+    if (inline) {
+      if (this.inlineStack.length || this.compileStack.length) {
+        this.popStack();
+      }
+      this.push('(' + prefix + item + ')');
+    } else {
+      // Prevent modification of the context depth variable. Through replaceStack
+      if (!/^stack/.test(stack)) {
+        stack = this.nextStack();
+      }
+
+      this.source.push(stack + " = (" + prefix + item + ");");
+    }
+    return stack;
+  },
+
+  nextStack: function() {
+    return this.pushStack();
+  },
+
+  incrStack: function() {
+    this.stackSlot++;
+    if(this.stackSlot > 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<paramSize; i++) {
+      param = this.popStack();
+      params.push(param);
+
+      if(this.options.stringParams) {
+        types.push(this.popStack());
+        contexts.push(this.popStack());
+      }
+    }
+
+    if (this.options.stringParams) {
+      options.push("contexts:[" + contexts.join(",") + "]");
+      options.push("types:[" + types.join(",") + "]");
+      options.push("hashContexts:hashContexts");
+      options.push("hashTypes:hashTypes");
+    }
+
+    if(this.options.data) {
+      options.push("data:data");
+    }
+
+    options = "{" + options.join(",") + "}";
+    if (useRegister) {
+      this.register('options', options);
+      params.push('options');
+    } else {
+      params.push(options);
+    }
+    return params.join(", ");
+  }
+};
+
+var reservedWords = (
+  "break else new var" +
+  " case finally return void" +
+  " catch for switch while" +
+  " continue function this with" +
+  " default if throw" +
+  " delete in try" +
+  " do instanceof typeof" +
+  " abstract enum int short" +
+  " boolean export interface static" +
+  " byte extends long super" +
+  " char final native synchronized" +
+  " class float package throws" +
+  " const goto private transient" +
+  " debugger implements protected volatile" +
+  " double import public let yield"
+).split(" ");
+
+var compilerWords = JavaScriptCompiler.RESERVED_WORDS = {};
+
+for(var i=0, l=reservedWords.length; i<l; i++) {
+  compilerWords[reservedWords[i]] = true;
+}
+
+JavaScriptCompiler.isValidJavaScriptVariableName = function(name) {
+  if(!JavaScriptCompiler.RESERVED_WORDS[name] && /^[a-zA-Z_$][0-9a-zA-Z_$]+$/.test(name)) {
+    return true;
+  }
+  return false;
+};
+
+// END(BROWSER)
+
+return Handlebars;
+
+};

From 0f278e1dfc31225777a8e36f1865032f75ecdf32 Mon Sep 17 00:00:00 2001
From: kpdecker <kpdecker@gmail.com>
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<l; this.i++) {
+    for(var l=opcodes.length; this.i<l; this.i++) {
       opcode = opcodes[this.i];
 
       if(opcode.opcode === 'DECLARE') {
@@ -2132,47 +2175,6 @@ JavaScriptCompiler.isValidJavaScriptVariableName = function(name) {
   }
   return false;
 };
-
-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 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 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/runtime.js
 

From 7538b8556c6e7875dcb5fe7cfff945d7a4da0bd0 Mon Sep 17 00:00:00 2001
From: Blessan Mathew <blessenm@gmail.com>
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 <tricknotes.rs@gmail.com>
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 <blessenm@gmail.com>
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]
 </pre>
 
 If using the precompiler's normal mode, the resulting templates will be

From e4929df45473e200e103e2fe86d5fb820131bfa4 Mon Sep 17 00:00:00 2001
From: Daniel Marcotte <dmarcotte@gmail.com>
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 <a71104@gmail.com>
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 <wycats@gmail.com>
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<j; i++) {
-        if (data) { data.index = i; }
-        ret = ret + fn(context[i], { data: data });
-      }
-    } else {
-      for(var key in context) {
-        if(context.hasOwnProperty(key)) {
-          if(data) { data.key = key; }
-          ret = ret + fn(context[key], {data: data});
-          i++;
+    if(context && typeof context === 'object') {
+      if(context instanceof Array){
+        for(var j = context.length; i<j; i++) {
+          if (data) { data.index = i; }
+          ret = ret + fn(context[i], { data: data });
+        }
+      } else {
+        for(var key in context) {
+          if(context.hasOwnProperty(key)) {
+            if(data) { data.key = key; }
+            ret = ret + fn(context[key], {data: data});
+            i++;
+          }
         }
       }
     }
-  }
 
-  if(i === 0){
-    ret = inverse(this);
-  }
+    if(i === 0){
+      ret = inverse(this);
+    }
 
-  return ret;
-});
+    return ret;
+  });
 
-Handlebars.registerHelper('if', function(conditional, options) {
-  var type = toString.call(conditional);
-  if(type === functionType) { conditional = conditional.call(this); }
+  exports.registerHelper('if', function(conditional, options) {
+    var type = toString.call(conditional);
+    if(type === functionType) { conditional = conditional.call(this); }
 
-  if(!conditional || Handlebars.Utils.isEmpty(conditional)) {
-    return options.inverse(this);
-  } else {
-    return options.fn(this);
-  }
-});
+    if(!conditional || Handlebars.Utils.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});
+  });
+
+  exports.registerHelper('with', function(context, options) {
+    var type = toString.call(context);
+    if(type === functionType) { context = context.call(this); }
 
-Handlebars.registerHelper('unless', function(conditional, options) {
-  return Handlebars.helpers['if'].call(this, conditional, {fn: options.inverse, inverse: options.fn});
-});
+    if (!Handlebars.Utils.isEmpty(context)) return options.fn(context);
+  });
 
-Handlebars.registerHelper('with', function(context, options) {
-  var type = toString.call(context);
-  if(type === functionType) { context = context.call(this); }
+  exports.registerHelper('log', function(context, options) {
+    var level = options.data && options.data.level != null ? parseInt(options.data.level, 10) : 1;
+    Handlebars.log(level, context);
+  });
 
-  if (!Handlebars.Utils.isEmpty(context)) return options.fn(context);
-});
+  return Handlebars;
+}
 
-Handlebars.registerHelper('log', function(context, options) {
-  var level = options.data && options.data.level != null ? parseInt(options.data.level, 10) : 1;
-  Handlebars.log(level, context);
-});
+var levels = {
+  DEBUG: 0, INFO: 1, WARN: 2, ERROR: 3, level: 3
+}
 
-// END(BROWSER)
+var methodMap = { 0: 'debug', 1: 'info', 2: 'warn', 3: 'error' };
 
-return Handlebars;
+export logger = {
+  // 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);
+      }
+    }
+  }
+};
+
+export function log(level, obj) { logger.log(level, obj); };
+
+export createFrame = Object.create || function(object) {
+  K.prototype = object;
+  var obj = new K();
+  K.prototype = null;
+  return obj;
 };
diff --git a/lib/handlebars/compiler/ast.js b/lib/handlebars/compiler/ast.js
index 567e29747..fcf5c6e87 100644
--- a/lib/handlebars/compiler/ast.js
+++ b/lib/handlebars/compiler/ast.js
@@ -1,15 +1,12 @@
-exports.attach = function(Handlebars) {
+import { Exception } from "handlebars/utils";
 
-// BEGIN(BROWSER)
-Handlebars.AST = {};
-
-Handlebars.AST.ProgramNode = function(statements, inverse) {
+export function ProgramNode(statements, inverse) {
   this.type = "program";
   this.statements = statements;
   if(inverse) { this.inverse = new Handlebars.AST.ProgramNode(inverse); }
 };
 
-Handlebars.AST.MustacheNode = function(rawParams, hash, unescaped) {
+export function MustacheNode(rawParams, hash, unescaped) {
   this.type = "mustache";
   this.escaped = !unescaped;
   this.hash = hash;
@@ -31,15 +28,15 @@ Handlebars.AST.MustacheNode = function(rawParams, hash, unescaped) {
   // pass or at runtime.
 };
 
-Handlebars.AST.PartialNode = function(partialName, context) {
+export function PartialNode(partialName, context) {
   this.type         = "partial";
   this.partialName  = partialName;
   this.context      = context;
 };
 
-Handlebars.AST.BlockNode = function(mustache, program, inverse, close) {
+export function BlockNode(mustache, program, inverse, close) {
   if(mustache.id.original !== close.original) {
-    throw new Handlebars.Exception(mustache.id.original + " doesn't match " + close.original);
+    throw new Exception(mustache.id.original + " doesn't match " + close.original);
   }
 
   this.type = "block";
@@ -52,17 +49,17 @@ Handlebars.AST.BlockNode = function(mustache, program, inverse, close) {
   }
 };
 
-Handlebars.AST.ContentNode = function(string) {
+export function ContentNode(string) {
   this.type = "content";
   this.string = string;
 };
 
-Handlebars.AST.HashNode = function(pairs) {
+export function HashNode(pairs) {
   this.type = "hash";
   this.pairs = pairs;
 };
 
-Handlebars.AST.IdNode = function(parts) {
+export function IdNode(part) {
   this.type = "ID";
 
   var original = "",
@@ -93,43 +90,37 @@ Handlebars.AST.IdNode = function(parts) {
   this.stringModeValue = this.string;
 };
 
-Handlebars.AST.PartialNameNode = function(name) {
+export function PartialNameNode(name) {
   this.type = "PARTIAL_NAME";
   this.name = name.original;
 };
 
-Handlebars.AST.DataNode = function(id) {
+export function DataNode(id) {
   this.type = "DATA";
   this.id = id;
 };
 
-Handlebars.AST.StringNode = function(string) {
+export function StringNode(string) {
   this.type = "STRING";
   this.original =
     this.string =
     this.stringModeValue = string;
 };
 
-Handlebars.AST.IntegerNode = function(integer) {
+export function IntegerNode(integer) {
   this.type = "INTEGER";
   this.original =
     this.integer = integer;
   this.stringModeValue = Number(integer);
 };
 
-Handlebars.AST.BooleanNode = function(bool) {
+export function BooleanNode(bool) {
   this.type = "BOOLEAN";
   this.bool = bool;
   this.stringModeValue = bool === "true";
 };
 
-Handlebars.AST.CommentNode = function(comment) {
+export function CommentNode(comment) {
   this.type = "comment";
   this.comment = comment;
 };
-
-// END(BROWSER)
-
-return Handlebars;
-};
-
diff --git a/lib/handlebars/compiler/base.js b/lib/handlebars/compiler/base.js
index 759445154..525cd5bfa 100644
--- a/lib/handlebars/compiler/base.js
+++ b/lib/handlebars/compiler/base.js
@@ -1,21 +1,12 @@
-var handlebars = require("./parser");
+import { parser } from "handlebars/compiler/parser";
+module AST from "handlebars/compiler/ast":
 
-exports.attach = function(Handlebars) {
-
-// BEGIN(BROWSER)
-
-Handlebars.Parser = handlebars;
-
-Handlebars.parse = function(input) {
+export Parser = parser;
 
+export 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);
-};
-
-// END(BROWSER)
+  if(input.constructor === AST.ProgramNode) { return input; }
 
-return Handlebars;
-};
+  parser.yy = AST;
+  return parser.parse(input);
+}
diff --git a/lib/handlebars/compiler/compiler.js b/lib/handlebars/compiler/compiler.js
index 98e12b1dc..a21ccc0d9 100644
--- a/lib/handlebars/compiler/compiler.js
+++ b/lib/handlebars/compiler/compiler.js
@@ -1,13 +1,11 @@
-var compilerbase = require("./base");
-
-exports.attach = function(Handlebars) {
-
-compilerbase.attach(Handlebars);
-
-// BEGIN(BROWSER)
+import { Exception } from "handlebars/utils";
+import parse from "handlebars/compiler/base";
+import { template } from "handlebars/runtime";
 
 /*jshint eqnull:true*/
-var Compiler = Handlebars.Compiler = function() {};
+
+var Compiler = function() {};
+export default Compiler;
 
 // the foundHelper register will disambiguate helper lookup from finding a
 // function in a context. This is necessary for mustache compatibility, which
@@ -40,6 +38,7 @@ Compiler.prototype = {
 
     return out.join("\n");
   },
+
   equals: function(other) {
     var len = this.opcodes.length;
     if (other.opcodes.length !== len) {
@@ -301,7 +300,7 @@ Compiler.prototype = {
   DATA: function(data) {
     this.options.data = true;
     if (data.id.isScoped || data.id.depth) {
-      throw new Handlebars.Exception('Scoped data references are not supported: ' + data.original);
+      throw new Exception('Scoped data references are not supported: ' + data.original);
     }
 
     this.opcode('lookupData');
@@ -415,35 +414,39 @@ Compiler.prototype = {
   }
 };
 
-Handlebars.precompile = function(input, options) {
+export function precompile(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);
+    throw new 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 ast = parse(input);
   var environment = new Compiler().compile(ast, options);
-  return new Handlebars.JavaScriptCompiler().compile(environment, options);
+  return new JavaScriptCompiler().compile(environment, options);
 };
 
-Handlebars.compile = function(input, options) {
+export function 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);
+    throw new 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 ast = parse(input);
     var environment = new Compiler().compile(ast, options);
-    var templateSpec = new Handlebars.JavaScriptCompiler().compile(environment, options, undefined, true);
-    return Handlebars.template(templateSpec);
+    var templateSpec = new JavaScriptCompiler().compile(environment, options, undefined, true);
+    return template(templateSpec);
   }
 
   // Template is only compiled on first use and cached after that point.
@@ -454,10 +457,3 @@ Handlebars.compile = function(input, options) {
     return compiled.call(this, context, options);
   };
 };
-
-
-// END(BROWSER)
-
-return Handlebars;
-
-};
diff --git a/lib/handlebars/compiler/javascript-compiler.js b/lib/handlebars/compiler/javascript-compiler.js
index 4548c6aeb..49660ffdd 100644
--- a/lib/handlebars/compiler/javascript-compiler.js
+++ b/lib/handlebars/compiler/javascript-compiler.js
@@ -1,18 +1,12 @@
-var compilerbase = require("./base");
+import { COMPILER_REVISION, REVISION_CHANGES } from "handlebars/base";
 
-exports.attach = function(Handlebars) {
-
-compilerbase.attach(Handlebars);
-
-// BEGIN(BROWSER)
-/*jshint eqnull:true*/
-
-var Literal = function(value) {
+function Literal(value) {
   this.value = value;
 };
 
 
-var JavaScriptCompiler = Handlebars.JavaScriptCompiler = function() {};
+function JavaScriptCompiler() {};
+export default JavaScriptCompiler;
 
 JavaScriptCompiler.prototype = {
   // PUBLIC API: You can override these methods in a subclass to provide
@@ -162,8 +156,8 @@ JavaScriptCompiler.prototype = {
     var source = this.mergeSource();
 
     if (!this.isChild) {
-      var revision = Handlebars.COMPILER_REVISION,
-          versions = Handlebars.REVISION_CHANGES[revision];
+      var revision = COMPILER_REVISION,
+          versions = REVISION_CHANGES[revision];
       source = "this.compilerInfo = ["+revision+",'"+versions+"'];\n"+source;
     }
 
@@ -848,9 +842,3 @@ JavaScriptCompiler.isValidJavaScriptVariableName = function(name) {
   }
   return false;
 };
-
-// END(BROWSER)
-
-return Handlebars;
-
-};
diff --git a/lib/handlebars/runtime.js b/lib/handlebars/runtime.js
index 2e845c4cc..2585ef3e0 100644
--- a/lib/handlebars/runtime.js
+++ b/lib/handlebars/runtime.js
@@ -1,106 +1,102 @@
-exports.attach = function(Handlebars) {
-
-// BEGIN(BROWSER)
-
-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]+").";
-        }
+import { escapeExpression, extend, Exceptions } from "handlebars/utils";
+import { COMPILER_REVISION, REVISION_CHANGES } from "handlebars/base";
+
+// TODO: Deal with the fact that compile is necessary for on-the-fly partial
+// compilation but won't be present for precompiled templates.
+
+export function template(templateSpec) {
+  // Just add water
+  var container = {
+    escapeExpression: escapeExpression,
+    invokePartial: invokePartial,
+    programs: [],
+    program: function(i, fn, data) {
+      var programWrapper = this.programs[i];
+      if(data) {
+        programWrapper = program(i, fn, data);
+      } else if (!programWrapper) {
+        programWrapper = this.programs[i] = program(i, fn);
+      }
+      return programWrapper;
+    },
+    merge: function(param, common) {
+      var ret = param || common;
+
+      if (param && common) {
+        ret = {};
+        extend(ret, common);
+        extend(ret, param);
+      }
+      return ret;
+    },
+    programWithDepth: programWithDepth,
+    noop: noop,
+    compilerInfo: null
+  };
+
+  return function(context, options) {
+    options = options || {};
+    // 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 compilerInfo = container.compilerInfo || [],
+        compilerRevision = compilerInfo[0] || 1,
+        currentRevision = COMPILER_REVISION;
+
+    if (compilerRevision !== currentRevision) {
+      if (compilerRevision < currentRevision) {
+        var runtimeVersions = REVISION_CHANGES[currentRevision],
+            compilerVersions = 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;
-
-// END(BROWSER)
-
-return Handlebars;
-
-};
+    return result;
+  };
+}
+
+export function programWithDepth(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;
+}
+
+export function program(i, fn, data) {
+  var program = function(context, options) {
+    options = options || {};
+
+    return fn(context, options.data || data);
+  };
+  program.program = i;
+  program.depth = 0;
+  return program;
+}
+
+export function noop() { return ""; }
+
+export function invokePartial(partial, name, context, helpers, partials, data) {
+  var options = { helpers: helpers, partials: partials, data: data };
+
+  if(partial === undefined) {
+    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);
+  }
+}
diff --git a/lib/handlebars/utils.js b/lib/handlebars/utils.js
index 1e0e4c902..d14795c2e 100644
--- a/lib/handlebars/utils.js
+++ b/lib/handlebars/utils.js
@@ -1,12 +1,8 @@
-exports.attach = function(Handlebars) {
-
 var toString = Object.prototype.toString;
 
-// BEGIN(BROWSER)
-
 var errorProps = ['description', 'fileName', 'lineNumber', 'message', 'name', 'number', 'stack'];
 
-Handlebars.Exception = function(message) {
+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.
@@ -14,13 +10,15 @@ Handlebars.Exception = function(message) {
     this[errorProps[idx]] = tmp[errorProps[idx]];
   }
 };
-Handlebars.Exception.prototype = new Error();
+
+Exception.prototype = new Error();
 
 // Build out our basic SafeString type
-Handlebars.SafeString = function(string) {
+export function SafeString(string) {
   this.string = string;
 };
-Handlebars.SafeString.prototype.toString = function() {
+
+SafeString.prototype.toString = function() {
   return this.string.toString();
 };
 
@@ -40,44 +38,37 @@ var escapeChar = function(chr) {
   return escape[chr] || "&amp;";
 };
 
-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;
+export function extend(obj, value) {
+  for(var key in value) {
+    if(value.hasOwnProperty(key)) {
+      obj[key] = value[key];
     }
   }
-};
-
-// END(BROWSER)
+}
+
+export function escapeExpression(string) {
+  // don't escape SafeStrings, since they're already safe
+  if (string instanceof SafeString) {
+    return string.toString();
+  } else if (string == null || string === false) {
+    return "";
+  }
 
-return Handlebars;
-};
+  // 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);
+}
+
+export function isEmpty(value) {
+  if (!value && value !== 0) {
+    return true;
+  } else if(toString.call(value) === "[object Array]" && value.length === 0) {
+    return true;
+  } else {
+    return false;
+  }
+}
diff --git a/package.json b/package.json
index 13fdc74ee..024ba0a82 100644
--- a/package.json
+++ b/package.json
@@ -1,39 +1,27 @@
 {
-  "name": "handlebars",
-  "description": "Extension of the Mustache logicless template language",
-  "version": "1.0.12",
-  "homepage": "http://www.handlebarsjs.com/",
-  "keywords": [
-    "handlebars",
-    "mustache",
-    "template",
-    "html"
-  ],
+  "name": "handlebars.js",
+  "barename": "handlebars",
+  "version": "1.1.0",
+  "description": "Handlebars provides the power necessary to let you build semantic templates effectively with no frustration",
+  "main": "index.js",
+  "scripts": {
+    "test": "echo \"Error: no test specified\" && exit 1"
+  },
   "repository": {
     "type": "git",
-    "url": "git://github.com/wycats/handlebars.js.git"
-  },
-  "engines": {
-    "node": ">=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 <wycats@gmail.com>
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<l; i++) {
@@ -22,7 +20,7 @@ Handlebars.PrintVisitor.prototype.pad = function(string, newline) {
   return out;
 };
 
-Handlebars.PrintVisitor.prototype.program = function(program) {
+PrintVisitor.prototype.program = function(program) {
   var out = "",
       statements = program.statements,
       inverse = program.inverse,
@@ -37,7 +35,7 @@ Handlebars.PrintVisitor.prototype.program = function(program) {
   return out;
 };
 
-Handlebars.PrintVisitor.prototype.block = function(block) {
+PrintVisitor.prototype.block = function(block) {
   var out = "";
 
   out = out + this.pad("BLOCK:");
@@ -76,13 +74,13 @@ Handlebars.PrintVisitor.prototype.mustache = function(mustache) {
   return this.pad("{{ " + this.accept(mustache.id) + " " + params + hash + " }}");
 };
 
-Handlebars.PrintVisitor.prototype.partial = function(partial) {
+PrintVisitor.prototype.partial = function(partial) {
   var content = this.accept(partial.partialName);
   if(partial.context) { content = content + " " + this.accept(partial.context); }
   return this.pad("{{> " + 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 <dariosn@gmail.com>
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 <wycats@gmail.com>
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<j; i++) {
-        if (data) { data.index = i; }
-        ret = ret + fn(context[i], { data: data });
-      }
-    } else {
-      for(var key in context) {
-        if(context.hasOwnProperty(key)) {
-          if(data) { data.key = key; }
-          ret = ret + fn(context[key], {data: data});
-          i++;
-        }
-      }
-    }
-  }
-
-  if(i === 0){
-    ret = inverse(this);
-  }
-
-  return ret;
-});
-
-Handlebars.registerHelper('if', function(conditional, options) {
-  var type = toString.call(conditional);
-  if(type === functionType) { conditional = conditional.call(this); }
-
-  if(!conditional || Handlebars.Utils.isEmpty(conditional)) {
-    return options.inverse(this);
-  } else {
-    return options.fn(this);
-  }
-});
-
-Handlebars.registerHelper('unless', function(conditional, options) {
-  return Handlebars.helpers['if'].call(this, conditional, {fn: options.inverse, inverse: options.fn});
-});
-
-Handlebars.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);
-});
-
-Handlebars.registerHelper('log', function(context, options) {
-  var level = options.data && options.data.level != null ? parseInt(options.data.level, 10) : 1;
-  Handlebars.log(level, context);
-});
-;
-// lib/handlebars/compiler/parser.js
-/* Jison generated parser */
-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},
-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]],
-performAction: function anonymous(yytext,yyleng,yylineno,yy,yystate,$$,_$) {
-
-var $0 = $$.length - 1;
-switch (yystate) {
-case 1: return $$[$0-1]; 
-break;
-case 2: this.$ = new yy.ProgramNode([], $$[$0]); 
-break;
-case 3: this.$ = new yy.ProgramNode($$[$0-2], $$[$0]); 
-break;
-case 4: this.$ = new yy.ProgramNode($$[$0-1], []); 
-break;
-case 5: this.$ = new yy.ProgramNode($$[$0]); 
-break;
-case 6: this.$ = new yy.ProgramNode([], []); 
-break;
-case 7: this.$ = new yy.ProgramNode([]); 
-break;
-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]); 
-break;
-case 11: this.$ = new yy.BlockNode($$[$0-2], $$[$0-1], $$[$0-1].inverse, $$[$0]); 
-break;
-case 12: this.$ = $$[$0]; 
-break;
-case 13: this.$ = $$[$0]; 
-break;
-case 14: this.$ = new yy.ContentNode($$[$0]); 
-break;
-case 15: this.$ = new yy.CommentNode($$[$0]); 
-break;
-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]); 
-break;
-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); 
-break;
-case 21: this.$ = new yy.PartialNode($$[$0-1]); 
-break;
-case 22: this.$ = new yy.PartialNode($$[$0-2], $$[$0-1]); 
-break;
-case 23: 
-break;
-case 24: this.$ = [[$$[$0-2]].concat($$[$0-1]), $$[$0]]; 
-break;
-case 25: this.$ = [[$$[$0-1]].concat($$[$0]), null]; 
-break;
-case 26: this.$ = [[$$[$0-1]], $$[$0]]; 
-break;
-case 27: this.$ = [[$$[$0]], null]; 
-break;
-case 28: this.$ = [[$$[$0]], null]; 
-break;
-case 29: $$[$0-1].push($$[$0]); this.$ = $$[$0-1]; 
-break;
-case 30: this.$ = [$$[$0]]; 
-break;
-case 31: this.$ = $$[$0]; 
-break;
-case 32: this.$ = new yy.StringNode($$[$0]); 
-break;
-case 33: this.$ = new yy.IntegerNode($$[$0]); 
-break;
-case 34: this.$ = new yy.BooleanNode($$[$0]); 
-break;
-case 35: this.$ = $$[$0]; 
-break;
-case 36: this.$ = new yy.HashNode($$[$0]); 
-break;
-case 37: $$[$0-1].push($$[$0]); this.$ = $$[$0-1]; 
-break;
-case 38: this.$ = [$$[$0]]; 
-break;
-case 39: this.$ = [$$[$0-2], $$[$0]]; 
-break;
-case 40: this.$ = [$$[$0-2], new yy.StringNode($$[$0])]; 
-break;
-case 41: this.$ = [$$[$0-2], new yy.IntegerNode($$[$0])]; 
-break;
-case 42: this.$ = [$$[$0-2], new yy.BooleanNode($$[$0])]; 
-break;
-case 43: this.$ = [$$[$0-2], $$[$0]]; 
-break;
-case 44: this.$ = new yy.PartialNameNode($$[$0]); 
-break;
-case 45: this.$ = new yy.PartialNameNode(new yy.StringNode($$[$0])); 
-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]}]; 
-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]}],
-defaultActions: {17:[2,1]},
-parseError: function parseError(str, hash) {
-    throw new Error(str);
-},
-parse: function parse(input) {
-    var self = this, stack = [0], vstack = [null], lstack = [], table = this.table, yytext = "", yylineno = 0, yyleng = 0, recovering = 0, TERROR = 2, EOF = 1;
-    this.lexer.setInput(input);
-    this.lexer.yy = this.yy;
-    this.yy.lexer = this.lexer;
-    this.yy.parser = this;
-    if (typeof this.lexer.yylloc == "undefined")
-        this.lexer.yylloc = {};
-    var yyloc = this.lexer.yylloc;
-    lstack.push(yyloc);
-    var ranges = this.lexer.options && this.lexer.options.ranges;
-    if (typeof this.yy.parseError === "function")
-        this.parseError = this.yy.parseError;
-    function popStack(n) {
-        stack.length = stack.length - 2 * n;
-        vstack.length = vstack.length - n;
-        lstack.length = lstack.length - n;
-    }
-    function lex() {
-        var token;
-        token = self.lexer.lex() || 1;
-        if (typeof token !== "number") {
-            token = self.symbols_[token] || token;
-        }
-        return token;
-    }
-    var symbol, preErrorSymbol, state, action, a, r, yyval = {}, p, len, newState, expected;
-    while (true) {
-        state = stack[stack.length - 1];
-        if (this.defaultActions[state]) {
-            action = this.defaultActions[state];
-        } else {
-            if (symbol === null || typeof symbol == "undefined") {
-                symbol = lex();
-            }
-            action = table[state] && table[state][symbol];
-        }
-        if (typeof action === "undefined" || !action.length || !action[0]) {
-            var errStr = "";
-            if (!recovering) {
-                expected = [];
-                for (p in table[state])
-                    if (this.terminals_[p] && p > 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<l; i++) {
-    var part = parts[i].part;
-    original += (parts[i].separator || '') + part;
-
-    if (part === ".." || part === "." || part === "this") {
-      if (dig.length > 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 = {
-  "&": "&amp;",
-  "<": "&lt;",
-  ">": "&gt;",
-  '"': "&quot;",
-  "'": "&#x27;",
-  "`": "&#x60;"
-};
-
-var badChars = /[&<>"'`]/g;
-var possible = /[&<>"'`]/;
-
-var escapeChar = function(chr) {
-  return escape[chr] || "&amp;";
-};
-
-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<l; i++) {
-      opcode = opcodes[i];
-
-      if (opcode.opcode === 'DECLARE') {
-        out.push("DECLARE " + opcode.name + "=" + opcode.value);
-      } else {
-        params = [];
-        for (var j=0; j<opcode.args.length; j++) {
-          param = opcode.args[j];
-          if (typeof param === "string") {
-            param = "\"" + param.replace("\n", "\\n") + "\"";
-          }
-          params.push(param);
-        }
-        out.push(opcode.opcode + " " + params.join(" "));
-      }
-    }
-
-    return out.join("\n");
-  },
-  equals: function(other) {
-    var len = this.opcodes.length;
-    if (other.opcodes.length !== len) {
-      return false;
-    }
-
-    for (var i = 0; i < len; i++) {
-      var opcode = this.opcodes[i],
-          otherOpcode = other.opcodes[i];
-      if (opcode.opcode !== otherOpcode.opcode || opcode.args.length !== otherOpcode.args.length) {
-        return false;
-      }
-      for (var j = 0; j < opcode.args.length; j++) {
-        if (opcode.args[j] !== otherOpcode.args[j]) {
-          return false;
-        }
-      }
-    }
-
-    len = this.children.length;
-    if (other.children.length !== len) {
-      return false;
-    }
-    for (i = 0; i < len; i++) {
-      if (!this.children[i].equals(other.children[i])) {
-        return false;
-      }
-    }
-
-    return true;
-  },
-
-  guid: 0,
-
-  compile: function(program, options) {
-    this.children = [];
-    this.depths = {list: []};
-    this.options = options;
-
-    // These changes will propagate to the other compiler components
-    var knownHelpers = this.options.knownHelpers;
-    this.options.knownHelpers = {
-      'helperMissing': true,
-      'blockHelperMissing': true,
-      'each': true,
-      'if': true,
-      'unless': true,
-      'with': true,
-      'log': true
-    };
-    if (knownHelpers) {
-      for (var name in knownHelpers) {
-        this.options.knownHelpers[name] = knownHelpers[name];
-      }
-    }
-
-    return this.program(program);
-  },
-
-  accept: function(node) {
-    return this[node.type](node);
-  },
-
-  program: function(program) {
-    var statements = program.statements, statement;
-    this.opcodes = [];
-
-    for(var i=0, l=statements.length; i<l; i++) {
-      statement = statements[i];
-      this[statement.type](statement);
-    }
-    this.isSimple = l === 1;
-
-    this.depths.list = this.depths.list.sort(function(a, b) {
-      return a - b;
-    });
-
-    return this;
-  },
-
-  compileProgram: function(program) {
-    var result = new this.compiler().compile(program, this.options);
-    var guid = this.guid++, depth;
-
-    this.usePartial = this.usePartial || result.usePartial;
-
-    this.children[guid] = result;
-
-    for(var i=0, l=result.depths.list.length; i<l; i++) {
-      depth = result.depths.list[i];
-
-      if(depth < 2) { continue; }
-      else { this.addDepth(depth - 1); }
-    }
-
-    return guid;
-  },
-
-  block: function(block) {
-    var mustache = block.mustache,
-        program = block.program,
-        inverse = block.inverse;
-
-    if (program) {
-      program = this.compileProgram(program);
-    }
-
-    if (inverse) {
-      inverse = this.compileProgram(inverse);
-    }
-
-    var type = this.classifyMustache(mustache);
-
-    if (type === "helper") {
-      this.helperMustache(mustache, program, inverse);
-    } else if (type === "simple") {
-      this.simpleMustache(mustache);
-
-      // now that the simple mustache is resolved, we need to
-      // evaluate it by executing `blockHelperMissing`
-      this.opcode('pushProgram', program);
-      this.opcode('pushProgram', inverse);
-      this.opcode('emptyHash');
-      this.opcode('blockValue');
-    } else {
-      this.ambiguousMustache(mustache, program, inverse);
-
-      // now that the simple mustache is resolved, we need to
-      // evaluate it by executing `blockHelperMissing`
-      this.opcode('pushProgram', program);
-      this.opcode('pushProgram', inverse);
-      this.opcode('emptyHash');
-      this.opcode('ambiguousBlockValue');
-    }
-
-    this.opcode('append');
-  },
-
-  hash: function(hash) {
-    var pairs = hash.pairs, pair, val;
-
-    this.opcode('pushHash');
-
-    for(var i=0, l=pairs.length; i<l; i++) {
-      pair = pairs[i];
-      val  = pair[1];
-
-      if (this.options.stringParams) {
-        if(val.depth) {
-          this.addDepth(val.depth);
-        }
-        this.opcode('getContext', val.depth || 0);
-        this.opcode('pushStringParam', val.stringModeValue, val.type);
-      } else {
-        this.accept(val);
-      }
-
-      this.opcode('assignToHash', pair[0]);
-    }
-    this.opcode('popHash');
-  },
-
-  partial: function(partial) {
-    var partialName = partial.partialName;
-    this.usePartial = true;
-
-    if(partial.context) {
-      this.ID(partial.context);
-    } else {
-      this.opcode('push', 'depth0');
-    }
-
-    this.opcode('invokePartial', partialName.name);
-    this.opcode('append');
-  },
-
-  content: function(content) {
-    this.opcode('appendContent', content.string);
-  },
-
-  mustache: function(mustache) {
-    var options = this.options;
-    var type = this.classifyMustache(mustache);
-
-    if (type === "simple") {
-      this.simpleMustache(mustache);
-    } else if (type === "helper") {
-      this.helperMustache(mustache);
-    } else {
-      this.ambiguousMustache(mustache);
-    }
-
-    if(mustache.escaped && !options.noEscape) {
-      this.opcode('appendEscaped');
-    } else {
-      this.opcode('append');
-    }
-  },
-
-  ambiguousMustache: function(mustache, program, inverse) {
-    var id = mustache.id,
-        name = id.parts[0],
-        isBlock = program != null || inverse != null;
-
-    this.opcode('getContext', id.depth);
-
-    this.opcode('pushProgram', program);
-    this.opcode('pushProgram', inverse);
-
-    this.opcode('invokeAmbiguous', name, isBlock);
-  },
-
-  simpleMustache: function(mustache) {
-    var id = mustache.id;
-
-    if (id.type === 'DATA') {
-      this.DATA(id);
-    } else if (id.parts.length) {
-      this.ID(id);
-    } else {
-      // Simplified ID for `this`
-      this.addDepth(id.depth);
-      this.opcode('getContext', id.depth);
-      this.opcode('pushContext');
-    }
-
-    this.opcode('resolvePossibleLambda');
-  },
-
-  helperMustache: function(mustache, program, inverse) {
-    var params = this.setupFullMustacheParams(mustache, program, inverse),
-        name = mustache.id.parts[0];
-
-    if (this.options.knownHelpers[name]) {
-      this.opcode('invokeKnownHelper', params.length, name);
-    } else if (this.options.knownHelpersOnly) {
-      throw new Error("You specified knownHelpersOnly, but used the unknown helper " + name);
-    } else {
-      this.opcode('invokeHelper', params.length, name);
-    }
-  },
-
-  ID: function(id) {
-    this.addDepth(id.depth);
-    this.opcode('getContext', id.depth);
-
-    var name = id.parts[0];
-    if (!name) {
-      this.opcode('pushContext');
-    } else {
-      this.opcode('lookupOnContext', id.parts[0]);
-    }
-
-    for(var i=1, l=id.parts.length; i<l; i++) {
-      this.opcode('lookup', id.parts[i]);
-    }
-  },
-
-  DATA: function(data) {
-    this.options.data = true;
-    if (data.id.isScoped || data.id.depth) {
-      throw new Handlebars.Exception('Scoped data references are not supported: ' + data.original);
-    }
-
-    this.opcode('lookupData');
-    var parts = data.id.parts;
-    for(var i=0, l=parts.length; i<l; i++) {
-      this.opcode('lookup', parts[i]);
-    }
-  },
-
-  STRING: function(string) {
-    this.opcode('pushString', string.string);
-  },
-
-  INTEGER: function(integer) {
-    this.opcode('pushLiteral', integer.integer);
-  },
-
-  BOOLEAN: function(bool) {
-    this.opcode('pushLiteral', bool.bool);
-  },
-
-  comment: function() {},
-
-  // HELPERS
-  opcode: function(name) {
-    this.opcodes.push({ opcode: name, args: [].slice.call(arguments, 1) });
-  },
-
-  declare: function(name, value) {
-    this.opcodes.push({ opcode: 'DECLARE', name: name, value: value });
-  },
-
-  addDepth: function(depth) {
-    if(isNaN(depth)) { throw new Error("EWOT"); }
-    if(depth === 0) { return; }
-
-    if(!this.depths[depth]) {
-      this.depths[depth] = true;
-      this.depths.list.push(depth);
-    }
-  },
-
-  classifyMustache: function(mustache) {
-    var isHelper   = mustache.isHelper;
-    var isEligible = mustache.eligibleHelper;
-    var options    = this.options;
-
-    // if ambiguous, we can possibly resolve the ambiguity now
-    if (isEligible && !isHelper) {
-      var name = mustache.id.parts[0];
-
-      if (options.knownHelpers[name]) {
-        isHelper = true;
-      } else if (options.knownHelpersOnly) {
-        isEligible = false;
-      }
-    }
-
-    if (isHelper) { return "helper"; }
-    else if (isEligible) { return "ambiguous"; }
-    else { return "simple"; }
-  },
-
-  pushParams: function(params) {
-    var i = params.length, param;
-
-    while(i--) {
-      param = params[i];
-
-      if(this.options.stringParams) {
-        if(param.depth) {
-          this.addDepth(param.depth);
-        }
-
-        this.opcode('getContext', param.depth || 0);
-        this.opcode('pushStringParam', param.stringModeValue, param.type);
-      } else {
-        this[param.type](param);
-      }
-    }
-  },
-
-  setupMustacheParams: function(mustache) {
-    var params = mustache.params;
-    this.pushParams(params);
-
-    if(mustache.hash) {
-      this.hash(mustache.hash);
-    } else {
-      this.opcode('emptyHash');
-    }
-
-    return params;
-  },
-
-  // this will replace setupMustacheParams when we're done
-  setupFullMustacheParams: function(mustache, program, inverse) {
-    var params = mustache.params;
-    this.pushParams(params);
-
-    this.opcode('pushProgram', program);
-    this.opcode('pushProgram', inverse);
-
-    if(mustache.hash) {
-      this.hash(mustache.hash);
-    } else {
-      this.opcode('emptyHash');
-    }
-
-    return params;
-  }
-};
-
-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
-  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(var l=opcodes.length; this.i<l; this.i++) {
-      opcode = opcodes[this.i];
-
-      if(opcode.opcode === 'DECLARE') {
-        this[opcode.name] = opcode.value;
-      } else {
-        this[opcode.opcode].apply(this, opcode.args);
-      }
-    }
-
-    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 = [];
-
-    if (!this.isChild) {
-      var namespace = this.namespace;
-
-      var copies = "helpers = this.merge(helpers, " + namespace + ".helpers);";
-      if (this.environment.usePartial) { copies = copies + " partials = this.merge(partials, " + namespace + ".partials);"; }
-      if (this.options.data) { copies = copies + " data = data || {};"; }
-      out.push(copies);
-    } else {
-      out.push('');
-    }
-
-    if (!this.environment.isSimple) {
-      out.push(", buffer = " + this.initializeBuffer());
-    } else {
-      out.push("");
-    }
-
-    // track the last context pushed into place to allow skipping the
-    // getContext opcode when it would be a noop
-    this.lastContext = 0;
-    this.source = out;
-  },
-
-  createFunctionContext: function(asObject) {
-    var locals = this.stackVars.concat(this.registers.list);
-
-    if(locals.length > 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<l; i++) {
-      params.push("depth" + this.environment.depths.list[i]);
-    }
-
-    // Perform a second pass over the output to merge content when possible
-    var source = this.mergeSource();
-
-    if (!this.isChild) {
-      var revision = Handlebars.COMPILER_REVISION,
-          versions = Handlebars.REVISION_CHANGES[revision];
-      source = "this.compilerInfo = ["+revision+",'"+versions+"'];\n"+source;
-    }
-
-    if (asObject) {
-      params.push(source);
-
-      return Function.apply(this, params);
-    } else {
-      var functionSource = 'function ' + (this.name || '') + '(' + params.join(',') + ') {\n  ' + source + '}';
-      Handlebars.log(Handlebars.logger.DEBUG, functionSource + "\n\n");
-      return functionSource;
-    }
-  },
-  mergeSource: function() {
-    // WARN: We are not handling the case where buffer is still populated as the source should
-    // not have buffer append operations as their final action.
-    var source = '',
-        buffer;
-    for (var i = 0, len = this.source.length; i < len; i++) {
-      var line = this.source[i];
-      if (line.appendToBuffer) {
-        if (buffer) {
-          buffer = buffer + '\n    + ' + line.content;
-        } else {
-          buffer = line.content;
-        }
-      } else {
-        if (buffer) {
-          source += 'buffer += ' + buffer + ';\n  ';
-          buffer = undefined;
-        }
-        source += line + '\n  ';
-      }
-    }
-    return source;
-  },
-
-  // [blockValue]
-  //
-  // On stack, before: hash, inverse, program, value
-  // On stack, after: return value of blockHelperMissing
-  //
-  // The purpose of this opcode is to take a block of the form
-  // `{{#foo}}...{{/foo}}`, resolve the value of `foo`, and
-  // replace it on the stack with the result of properly
-  // invoking blockHelperMissing.
-  blockValue: function() {
-    this.context.aliases.blockHelperMissing = 'helpers.blockHelperMissing';
-
-    var params = ["depth0"];
-    this.setupParams(0, params);
-
-    this.replaceStack(function(current) {
-      params.splice(1, 0, current);
-      return "blockHelperMissing.call(" + params.join(", ") + ")";
-    });
-  },
-
-  // [ambiguousBlockValue]
-  //
-  // On stack, before: hash, inverse, program, value
-  // Compiler value, before: lastHelper=value of last found helper, if any
-  // On stack, after, if no lastHelper: same as [blockValue]
-  // On stack, after, if lastHelper: value
-  ambiguousBlockValue: function() {
-    this.context.aliases.blockHelperMissing = 'helpers.blockHelperMissing';
-
-    var params = ["depth0"];
-    this.setupParams(0, params);
-
-    var current = this.topStack();
-    params.splice(1, 0, current);
-
-    // Use the options value generated from the invocation
-    params[params.length-1] = 'options';
-
-    this.source.push("if (!" + this.lastHelper + ") { " + current + " = blockHelperMissing.call(" + params.join(", ") + "); }");
-  },
-
-  // [appendContent]
-  //
-  // On stack, before: ...
-  // On stack, after: ...
-  //
-  // Appends the string value of `content` to the current buffer
-  appendContent: function(content) {
-    this.source.push(this.appendToBuffer(this.quotedString(content)));
-  },
-
-  // [append]
-  //
-  // On stack, before: value, ...
-  // On stack, after: ...
-  //
-  // Coerces `value` to a String and appends it to the current buffer.
-  //
-  // If `value` is truthy, or 0, it is coerced into a string and appended
-  // Otherwise, the empty string is appended
-  append: function() {
-    // Force anything that is inlined onto the stack so we don't have duplication
-    // when we examine local
-    this.flushInline();
-    var local = this.popStack();
-    this.source.push("if(" + local + " || " + local + " === 0) { " + this.appendToBuffer(local) + " }");
-    if (this.environment.isSimple) {
-      this.source.push("else { " + this.appendToBuffer("''") + " }");
-    }
-  },
-
-  // [appendEscaped]
-  //
-  // On stack, before: value, ...
-  // On stack, after: ...
-  //
-  // Escape `value` and append it to the buffer
-  appendEscaped: function() {
-    this.context.aliases.escapeExpression = 'this.escapeExpression';
-
-    this.source.push(this.appendToBuffer("escapeExpression(" + this.popStack() + ")"));
-  },
-
-  // [getContext]
-  //
-  // On stack, before: ...
-  // On stack, after: ...
-  // Compiler value, after: lastContext=depth
-  //
-  // Set the value of the `lastContext` compiler value to the depth
-  getContext: function(depth) {
-    if(this.lastContext !== depth) {
-      this.lastContext = depth;
-    }
-  },
-
-  // [lookupOnContext]
-  //
-  // On stack, before: ...
-  // On stack, after: currentContext[name], ...
-  //
-  // Looks up the value of `name` on the current context and pushes
-  // it onto the stack.
-  lookupOnContext: function(name) {
-    this.push(this.nameLookup('depth' + this.lastContext, name, 'context'));
-  },
-
-  // [pushContext]
-  //
-  // On stack, before: ...
-  // On stack, after: currentContext, ...
-  //
-  // Pushes the value of the current context onto the stack.
-  pushContext: function() {
-    this.pushStackLiteral('depth' + this.lastContext);
-  },
-
-  // [resolvePossibleLambda]
-  //
-  // On stack, before: value, ...
-  // On stack, after: resolved value, ...
-  //
-  // If the `value` is a lambda, replace it on the stack by
-  // the return value of the lambda
-  resolvePossibleLambda: function() {
-    this.context.aliases.functionType = '"function"';
-
-    this.replaceStack(function(current) {
-      return "typeof " + current + " === functionType ? " + current + ".apply(depth0) : " + current;
-    });
-  },
-
-  // [lookup]
-  //
-  // On stack, before: value, ...
-  // On stack, after: value[name], ...
-  //
-  // Replace the value on the stack with the result of looking
-  // up `name` on `value`
-  lookup: function(name) {
-    this.replaceStack(function(current) {
-      return current + " == null || " + current + " === false ? " + current + " : " + this.nameLookup(current, name, 'context');
-    });
-  },
-
-  // [lookupData]
-  //
-  // On stack, before: ...
-  // On stack, after: data[id], ...
-  //
-  // Push the result of looking up `id` on the current data
-  lookupData: function(id) {
-    this.push('data');
-  },
-
-  // [pushStringParam]
-  //
-  // On stack, before: ...
-  // On stack, after: string, currentContext, ...
-  //
-  // This opcode is designed for use in string mode, which
-  // provides the string value of a parameter along with its
-  // depth rather than resolving it immediately.
-  pushStringParam: function(string, type) {
-    this.pushStackLiteral('depth' + this.lastContext);
-
-    this.pushString(type);
-
-    if (typeof string === 'string') {
-      this.pushString(string);
-    } else {
-      this.pushStackLiteral(string);
-    }
-  },
-
-  emptyHash: function() {
-    this.pushStackLiteral('{}');
-
-    if (this.options.stringParams) {
-      this.register('hashTypes', '{}');
-      this.register('hashContexts', '{}');
-    }
-  },
-  pushHash: function() {
-    this.hash = {values: [], types: [], contexts: []};
-  },
-  popHash: function() {
-    var hash = this.hash;
-    this.hash = undefined;
-
-    if (this.options.stringParams) {
-      this.register('hashContexts', '{' + hash.contexts.join(',') + '}');
-      this.register('hashTypes', '{' + hash.types.join(',') + '}');
-    }
-    this.push('{\n    ' + hash.values.join(',\n    ') + '\n  }');
-  },
-
-  // [pushString]
-  //
-  // On stack, before: ...
-  // On stack, after: quotedString(string), ...
-  //
-  // Push a quoted version of `string` onto the stack
-  pushString: function(string) {
-    this.pushStackLiteral(this.quotedString(string));
-  },
-
-  // [push]
-  //
-  // On stack, before: ...
-  // On stack, after: expr, ...
-  //
-  // Push an expression onto the stack
-  push: function(expr) {
-    this.inlineStack.push(expr);
-    return expr;
-  },
-
-  // [pushLiteral]
-  //
-  // On stack, before: ...
-  // On stack, after: value, ...
-  //
-  // Pushes a value onto the stack. This operation prevents
-  // the compiler from creating a temporary variable to hold
-  // it.
-  pushLiteral: function(value) {
-    this.pushStackLiteral(value);
-  },
-
-  // [pushProgram]
-  //
-  // On stack, before: ...
-  // On stack, after: program(guid), ...
-  //
-  // Push a program expression onto the stack. This takes
-  // a compile-time guid and converts it into a runtime-accessible
-  // expression.
-  pushProgram: function(guid) {
-    if (guid != null) {
-      this.pushStackLiteral(this.programExpression(guid));
-    } else {
-      this.pushStackLiteral(null);
-    }
-  },
-
-  // [invokeHelper]
-  //
-  // On stack, before: hash, inverse, program, params..., ...
-  // On stack, after: result of helper invocation
-  //
-  // Pops off the helper's parameters, invokes the helper,
-  // and pushes the helper's return value onto the stack.
-  //
-  // If the helper is not found, `helperMissing` is called.
-  invokeHelper: function(paramSize, name) {
-    this.context.aliases.helperMissing = 'helpers.helperMissing';
-
-    var helper = this.lastHelper = this.setupHelper(paramSize, name, true);
-    var nonHelper = this.nameLookup('depth' + this.lastContext, name, 'context');
-
-    this.push(helper.name + ' || ' + nonHelper);
-    this.replaceStack(function(name) {
-      return name + ' ? ' + name + '.call(' +
-          helper.callParams + ") " + ": helperMissing.call(" +
-          helper.helperMissingParams + ")";
-    });
-  },
-
-  // [invokeKnownHelper]
-  //
-  // On stack, before: hash, inverse, program, params..., ...
-  // On stack, after: result of helper invocation
-  //
-  // This operation is used when the helper is known to exist,
-  // so a `helperMissing` fallback is not required.
-  invokeKnownHelper: function(paramSize, name) {
-    var helper = this.setupHelper(paramSize, name);
-    this.push(helper.name + ".call(" + helper.callParams + ")");
-  },
-
-  // [invokeAmbiguous]
-  //
-  // On stack, before: hash, inverse, program, params..., ...
-  // On stack, after: result of disambiguation
-  //
-  // This operation is used when an expression like `{{foo}}`
-  // is provided, but we don't know at compile-time whether it
-  // is a helper or a path.
-  //
-  // This operation emits more code than the other options,
-  // and can be avoided by passing the `knownHelpers` and
-  // `knownHelpersOnly` flags at compile-time.
-  invokeAmbiguous: function(name, helperCall) {
-    this.context.aliases.functionType = '"function"';
-
-    this.pushStackLiteral('{}');    // Hash value
-    var helper = this.setupHelper(0, name, helperCall);
-
-    var helperName = this.lastHelper = this.nameLookup('helpers', name, 'helper');
-
-    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 + '.apply(depth0) : ' + nextStack + '; }');
-  },
-
-  // [invokePartial]
-  //
-  // On stack, before: context, ...
-  // On stack after: result of partial invocation
-  //
-  // This operation pops off a context, invokes a partial with that context,
-  // and pushes the result of the invocation back.
-  invokePartial: function(name) {
-    var params = [this.nameLookup('partials', name, 'partial'), "'" + name + "'", this.popStack(), "helpers", "partials"];
-
-    if (this.options.data) {
-      params.push("data");
-    }
-
-    this.context.aliases.self = "this";
-    this.push("self.invokePartial(" + params.join(", ") + ")");
-  },
-
-  // [assignToHash]
-  //
-  // On stack, before: value, hash, ...
-  // On stack, after: hash, ...
-  //
-  // Pops a value and hash off the stack, assigns `hash[key] = value`
-  // and pushes the hash back onto the stack.
-  assignToHash: function(key) {
-    var value = this.popStack(),
-        context,
-        type;
-
-    if (this.options.stringParams) {
-      type = this.popStack();
-      context = this.popStack();
-    }
-
-    var hash = this.hash;
-    if (context) {
-      hash.contexts.push("'" + key + "': " + context);
-    }
-    if (type) {
-      hash.types.push("'" + key + "': " + type);
-    }
-    hash.values.push("'" + key + "': (" + value + ")");
-  },
-
-  // HELPERS
-
-  compiler: JavaScriptCompiler,
-
-  compileChildren: function(environment, options) {
-    var children = environment.children, child, compiler;
-
-    for(var i=0, l=children.length; i<l; i++) {
-      child = children[i];
-      compiler = new this.compiler();
-
-      var index = this.matchExistingProgram(child);
-
-      if (index == null) {
-        this.context.programs.push('');     // Placeholder to prevent name conflicts for nested children
-        index = this.context.programs.length;
-        child.index = index;
-        child.name = 'program' + index;
-        this.context.programs[index] = compiler.compile(child, options, this.context);
-        this.context.environments[index] = child;
-      } else {
-        child.index = index;
-        child.name = 'program' + index;
-      }
-    }
-  },
-  matchExistingProgram: function(child) {
-    for (var i = 0, len = this.context.environments.length; i < len; i++) {
-      var environment = this.context.environments[i];
-      if (environment && environment.equals(child)) {
-        return i;
-      }
-    }
-  },
-
-  programExpression: function(guid) {
-    this.context.aliases.self = "this";
-
-    if(guid == null) {
-      return "self.noop";
-    }
-
-    var child = this.environment.children[guid],
-        depths = child.depths.list, depth;
-
-    var programParams = [child.index, child.name, "data"];
-
-    for(var i=0, l = depths.length; i<l; i++) {
-      depth = depths[i];
-
-      if(depth === 1) { programParams.push("depth0"); }
-      else { programParams.push("depth" + (depth - 1)); }
-    }
-
-    return (depths.length === 0 ? "self.program(" : "self.programWithDepth(") + programParams.join(", ") + ")";
-  },
-
-  register: function(name, val) {
-    this.useRegister(name);
-    this.source.push(name + " = " + val + ";");
-  },
-
-  useRegister: function(name) {
-    if(!this.registers[name]) {
-      this.registers[name] = true;
-      this.registers.list.push(name);
-    }
-  },
-
-  pushStackLiteral: function(item) {
-    return this.push(new Literal(item));
-  },
-
-  pushStack: function(item) {
-    this.flushInline();
-
-    var stack = this.incrStack();
-    if (item) {
-      this.source.push(stack + " = " + item + ";");
-    }
-    this.compileStack.push(stack);
-    return stack;
-  },
-
-  replaceStack: function(callback) {
-    var prefix = '',
-        inline = this.isInline(),
-        stack;
-
-    // If we are currently inline then we want to merge the inline statement into the
-    // replacement statement via ','
-    if (inline) {
-      var top = this.popStack(true);
-
-      if (top instanceof Literal) {
-        // Literals do not need to be inlined
-        stack = top.value;
-      } else {
-        // Get or create the current stack name for use by the inline
-        var name = this.stackSlot ? this.topStackName() : this.incrStack();
-
-        prefix = '(' + this.push(name) + ' = ' + top + '),';
-        stack = this.topStack();
-      }
-    } else {
-      stack = this.topStack();
-    }
-
-    var item = callback.call(this, stack);
-
-    if (inline) {
-      if (this.inlineStack.length || this.compileStack.length) {
-        this.popStack();
-      }
-      this.push('(' + prefix + item + ')');
-    } else {
-      // Prevent modification of the context depth variable. Through replaceStack
-      if (!/^stack/.test(stack)) {
-        stack = this.nextStack();
-      }
-
-      this.source.push(stack + " = (" + prefix + item + ");");
-    }
-    return stack;
-  },
-
-  nextStack: function() {
-    return this.pushStack();
-  },
-
-  incrStack: function() {
-    this.stackSlot++;
-    if(this.stackSlot > 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<paramSize; i++) {
-      param = this.popStack();
-      params.push(param);
-
-      if(this.options.stringParams) {
-        types.push(this.popStack());
-        contexts.push(this.popStack());
-      }
-    }
-
-    if (this.options.stringParams) {
-      options.push("contexts:[" + contexts.join(",") + "]");
-      options.push("types:[" + types.join(",") + "]");
-      options.push("hashContexts:hashContexts");
-      options.push("hashTypes:hashTypes");
-    }
-
-    if(this.options.data) {
-      options.push("data:data");
-    }
-
-    options = "{" + options.join(",") + "}";
-    if (useRegister) {
-      this.register('options', options);
-      params.push('options');
-    } else {
-      params.push(options);
-    }
-    return params.join(", ");
-  }
-};
-
-var reservedWords = (
-  "break else new var" +
-  " case finally return void" +
-  " catch for switch while" +
-  " continue function this with" +
-  " default if throw" +
-  " delete in try" +
-  " do instanceof typeof" +
-  " abstract enum int short" +
-  " boolean export interface static" +
-  " byte extends long super" +
-  " char final native synchronized" +
-  " class float package throws" +
-  " const goto private transient" +
-  " debugger implements protected volatile" +
-  " double import public let yield"
-).split(" ");
-
-var compilerWords = JavaScriptCompiler.RESERVED_WORDS = {};
-
-for(var i=0, l=reservedWords.length; i<l; i++) {
-  compilerWords[reservedWords[i]] = true;
-}
-
-JavaScriptCompiler.isValidJavaScriptVariableName = function(name) {
-  if(!JavaScriptCompiler.RESERVED_WORDS[name] && /^[a-zA-Z_$][0-9a-zA-Z_$]+$/.test(name)) {
-    return true;
-  }
-  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/dist/handlebars.runtime.js b/dist/handlebars.runtime.js
deleted file mode 100644
index fac2cc5a4..000000000
--- a/dist/handlebars.runtime.js
+++ /dev/null
@@ -1,374 +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<j; i++) {
-        if (data) { data.index = i; }
-        ret = ret + fn(context[i], { data: data });
-      }
-    } else {
-      for(var key in context) {
-        if(context.hasOwnProperty(key)) {
-          if(data) { data.key = key; }
-          ret = ret + fn(context[key], {data: data});
-          i++;
-        }
-      }
-    }
-  }
-
-  if(i === 0){
-    ret = inverse(this);
-  }
-
-  return ret;
-});
-
-Handlebars.registerHelper('if', function(conditional, options) {
-  var type = toString.call(conditional);
-  if(type === functionType) { conditional = conditional.call(this); }
-
-  if(!conditional || Handlebars.Utils.isEmpty(conditional)) {
-    return options.inverse(this);
-  } else {
-    return options.fn(this);
-  }
-});
-
-Handlebars.registerHelper('unless', function(conditional, options) {
-  return Handlebars.helpers['if'].call(this, conditional, {fn: options.inverse, inverse: options.fn});
-});
-
-Handlebars.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);
-});
-
-Handlebars.registerHelper('log', function(context, options) {
-  var level = options.data && options.data.level != null ? parseInt(options.data.level, 10) : 1;
-  Handlebars.log(level, context);
-});
-;
-// 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 = {
-  "&": "&amp;",
-  "<": "&lt;",
-  ">": "&gt;",
-  '"': "&quot;",
-  "'": "&#x27;",
-  "`": "&#x60;"
-};
-
-var badChars = /[&<>"'`]/g;
-var possible = /[&<>"'`]/;
-
-var escapeChar = function(chr) {
-  return escape[chr] || "&amp;";
-};
-
-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 <wycats@gmail.com>
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 <kpdecker@gmail.com>
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 <kpdecker@gmail.com>
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 <kpdecker@gmail.com>
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 <kpdecker@gmail.com>
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 <kpdecker@gmail.com>
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 <kpdecker@gmail.com>
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';
 
 <emu>[^\x00]{2,}?/("{{"|<<EOF>>) {
                                    if(yytext.slice(-1) !== "\\") this.popState();
@@ -18,32 +18,32 @@
                                    return 'CONTENT';
                                  }
 
-<com>[\s\S]*?"--}}"              { yytext = yytext.substr(0, yyleng-4); this.popState(); return 'COMMENT'; }
-
-<mu>"{{>"                        { return 'OPEN_PARTIAL'; }
-<mu>"{{#"                        { return 'OPEN_BLOCK'; }
-<mu>"{{/"                        { return 'OPEN_ENDBLOCK'; }
-<mu>"{{^"                        { return 'OPEN_INVERSE'; }
-<mu>"{{"\s*"else"                { return 'OPEN_INVERSE'; }
-<mu>"{{{"                        { return 'OPEN_UNESCAPED'; }
-<mu>"{{&"                        { return 'OPEN'; }
-<mu>"{{!--"                      { this.popState(); this.begin('com'); }
-<mu>"{{!"[\s\S]*?"}}"            { yytext = yytext.substr(3,yyleng-5); this.popState(); return 'COMMENT'; }
-<mu>"{{"                         { return 'OPEN'; }
-
-<mu>"="                          { return 'EQUALS'; }
-<mu>"."/[}\/ ]                   { return 'ID'; }
-<mu>".."                         { return 'ID'; }
-<mu>[\/.]                        { return 'SEP'; }
-<mu>\s+                          { /*ignore whitespace*/ }
-<mu>"}}}"                        { this.popState(); return 'CLOSE_UNESCAPED'; }
-<mu>"}}"                         { this.popState(); return 'CLOSE'; }
-<mu>'"'("\\"["]|[^"])*'"'        { yytext = yytext.substr(1,yyleng-2).replace(/\\"/g,'"'); return 'STRING'; }
-<mu>"'"("\\"[']|[^'])*"'"        { yytext = yytext.substr(1,yyleng-2).replace(/\\'/g,"'"); return 'STRING'; }
-<mu>"@"                          { return 'DATA'; }
-<mu>"true"/[}\s]                 { return 'BOOLEAN'; }
-<mu>"false"/[}\s]                { return 'BOOLEAN'; }
-<mu>\-?[0-9]+/[}\s]              { return 'INTEGER'; }
+<com>[\s\S]*?"--}}"              yytext = yytext.substr(0, yyleng-4); this.popState(); return 'COMMENT';
+
+<mu>"{{>"                        return 'OPEN_PARTIAL';
+<mu>"{{#"                        return 'OPEN_BLOCK';
+<mu>"{{/"                        return 'OPEN_ENDBLOCK';
+<mu>"{{^"                        return 'OPEN_INVERSE';
+<mu>"{{"\s*"else"                return 'OPEN_INVERSE';
+<mu>"{{{"                        return 'OPEN_UNESCAPED';
+<mu>"{{&"                        return 'OPEN';
+<mu>"{{!--"                      this.popState(); this.begin('com');
+<mu>"{{!"[\s\S]*?"}}"            yytext = yytext.substr(3,yyleng-5); this.popState(); return 'COMMENT';
+<mu>"{{"                         return 'OPEN';
+
+<mu>"="                          return 'EQUALS';
+<mu>"."/[}\/ ]                   return 'ID';
+<mu>".."                         return 'ID';
+<mu>[\/.]                        return 'SEP';
+<mu>\s+                          /*ignore whitespace*/
+<mu>"}}}"                        this.popState(); return 'CLOSE_UNESCAPED';
+<mu>"}}"                         this.popState(); return 'CLOSE';
+<mu>'"'("\\"["]|[^"])*'"'        yytext = yytext.substr(1,yyleng-2).replace(/\\"/g,'"'); return 'STRING';
+<mu>"'"("\\"[']|[^'])*"'"        yytext = yytext.substr(1,yyleng-2).replace(/\\'/g,"'"); return 'STRING';
+<mu>"@"                          return 'DATA';
+<mu>"true"/[}\s]                 return 'BOOLEAN';
+<mu>"false"/[}\s]                return 'BOOLEAN';
+<mu>\-?[0-9]+/[}\s]              return 'INTEGER';
 
 /*
 ID is the inverse of control characters.
@@ -54,10 +54,10 @@ Control characters ranges:
   [\[-\^`]      [, \, ], ^, `,                          Exceptions in range: _
   [\{-~]        {, |, }, ~
 */
-<mu>[^\s!"#%-,\.\/;->@\[-\^`\{-~]+/[=}\s\/.]   { return 'ID'; }
+<mu>[^\s!"#%-,\.\/;->@\[-\^`\{-~]+/[=}\s\/.]   return 'ID';
 
-<mu>'['[^\]]*']'                 { yytext = yytext.substr(1, yyleng-2); return 'ID'; }
-<mu>.                            { return 'INVALID'; }
+<mu>'['[^\]]*']'                 yytext = yytext.substr(1, yyleng-2); return 'ID';
+<mu>.                            return 'INVALID';
 
-<INITIAL,mu><<EOF>>              { return 'EOF'; }
+<INITIAL,mu><<EOF>>              return 'EOF';
 

From dcbc3a55a4a7c129cc9aea0ef2003c1c3c86fe29 Mon Sep 17 00:00:00 2001
From: kpdecker <kpdecker@gmail.com>
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 <wycats@gmail.com>
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 <wycats@gmail.com>
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 <wycats@gmail.com>
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 <kpdecker@gmail.com>
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 @@
 <mu>"false"/[}\s]                return 'BOOLEAN';
 <mu>\-?[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: _
-  [\{-~]        {, |, }, ~
-*/
-<mu>[^\s!"#%-,\.\/;->@\[-\^`\{-~]+/[=}\s\/.]   return 'ID';
+<mu>{ID}                         return 'ID';
 
 <mu>'['[^\]]*']'                 yytext = yytext.substr(1, yyleng-2); return 'ID';
 <mu>.                            return 'INVALID';

From 03310df95e91880ea647f38805dc9694a3258a65 Mon Sep 17 00:00:00 2001
From: kpdecker <kpdecker@gmail.com>
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\/.]
 
 <emu>[^\x00]{2,}?/("{{"|<<EOF>>) {
                                    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';
                                  }
 
-<com>[\s\S]*?"--}}"              yytext = yytext.substr(0, yyleng-4); this.popState(); return 'COMMENT';
+<com>[\s\S]*?"--}}"              strip(0,4); this.popState(); return 'COMMENT';
 
 <mu>"{{>"                        return 'OPEN_PARTIAL';
 <mu>"{{#"                        return 'OPEN_BLOCK';
@@ -39,7 +48,7 @@ ID    [^\s!"#%-,\.\/;->@\[-\^`\{-~]+/[=}\s\/.]
 <mu>"{{{"                        return 'OPEN_UNESCAPED';
 <mu>"{{&"                        return 'OPEN';
 <mu>"{{!--"                      this.popState(); this.begin('com');
-<mu>"{{!"[\s\S]*?"}}"            yytext = yytext.substr(3,yyleng-5); this.popState(); return 'COMMENT';
+<mu>"{{!"[\s\S]*?"}}"            strip(3,5); this.popState(); return 'COMMENT';
 <mu>"{{"                         return 'OPEN';
 
 <mu>"="                          return 'EQUALS';
@@ -49,8 +58,8 @@ ID    [^\s!"#%-,\.\/;->@\[-\^`\{-~]+/[=}\s\/.]
 <mu>\s+                          /*ignore whitespace*/
 <mu>"}}}"                        this.popState(); return 'CLOSE_UNESCAPED';
 <mu>"}}"                         this.popState(); return 'CLOSE';
-<mu>'"'("\\"["]|[^"])*'"'        yytext = yytext.substr(1,yyleng-2).replace(/\\"/g,'"'); return 'STRING';
-<mu>"'"("\\"[']|[^'])*"'"        yytext = yytext.substr(1,yyleng-2).replace(/\\'/g,"'"); return 'STRING';
+<mu>'"'("\\"["]|[^"])*'"'        yytext = strip(1,2).replace(/\\"/g,'"'); return 'STRING';
+<mu>"'"("\\"[']|[^'])*"'"        yytext = strip(1,2).replace(/\\'/g,"'"); return 'STRING';
 <mu>"@"                          return 'DATA';
 <mu>"true"/[}\s]                 return 'BOOLEAN';
 <mu>"false"/[}\s]                return 'BOOLEAN';
@@ -58,8 +67,7 @@ ID    [^\s!"#%-,\.\/;->@\[-\^`\{-~]+/[=}\s\/.]
 
 <mu>{ID}                         return 'ID';
 
-<mu>'['[^\]]*']'                 yytext = yytext.substr(1, yyleng-2); return 'ID';
+<mu>'['[^\]]*']'                 yytext = strip(1,2); return 'ID';
 <mu>.                            return 'INVALID';
 
 <INITIAL,mu><<EOF>>              return 'EOF';
-

From 9ca4f9c606cf562372d1123def592f27ba1a4928 Mon Sep 17 00:00:00 2001
From: kpdecker <kpdecker@gmail.com>
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 <kpdecker@gmail.com>
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 <kpdecker@gmail.com>
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 <dev@sellside.com>
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 <kpdecker@gmail.com>
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 <tuomas.palenius@gmail.com>
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 <parker@sorentwo.com>
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 <kpdecker@gmail.com>
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 <kpdecker@gmail.com>
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 <kpdecker@gmail.com>
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 <kpdecker@gmail.com>
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 <kpdecker@gmail.com>
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 <kpdecker@gmail.com>
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 <kpdecker@gmail.com>
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 <kpdecker@gmail.com>
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 <elving.pr@gmail.com>
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 <kpdecker@gmail.com>
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 <kpdecker@gmail.com>
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 <kpdecker@gmail.com>
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 <kpdecker@gmail.com>
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 <kpdecker@gmail.com>
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>/, "<version>#{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>/, '<version>' + 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 <kpdecker@gmail.com>
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 <kpdecker@gmail.com>
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 <kpdecker@gmail.com>
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 <kpdecker@gmail.com>
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<l; i++) {
       print(names[i] + new Array(this.benchSize - names[i].length + 1).join(" "));
       horSize = horSize + this.benchSize;
@@ -75,6 +75,8 @@ BenchWarmer.prototype = {
     Benchmark.invoke(this.benchmarks, {
       name: "run",
       onComplete: function() {
+        self.startLine('');
+
         var errors = false, prop, bench;
         for(prop in self.errors) { if(self.errors.hasOwnProperty(prop)) { errors = true; break; } }
 
@@ -106,7 +108,7 @@ BenchWarmer.prototype = {
 
     print(winners.join(", "));
     print("\n");
-    var padding = this.nameSize - name.length + 1;
+    var padding = Math.max(this.benchSize - name.length + 1, 0);
     name = name + new Array(padding).join(" ");
     print(name);
   },

From c40a9eacb2ef7f4c146ae58d988bca5553fe4e72 Mon Sep 17 00:00:00 2001
From: kpdecker <kpdecker@gmail.com>
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 <kpdecker@gmail.com>
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 <kpdecker@gmail.com>
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 <kpdecker@gmail.com>
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 <kpdecker@gmail.com>
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 <kpdecker@gmail.com>
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<j; i++) {
         if (data) { data.index = i; }
         ret = ret + fn(context[i], { data: data });
@@ -133,8 +143,7 @@ Handlebars.registerHelper('each', function(context, options) {
 });
 
 Handlebars.registerHelper('if', function(conditional, options) {
-  var type = toString.call(conditional);
-  if(type === functionType) { conditional = conditional.call(this); }
+  if (isFunction(conditional)) { conditional = conditional.call(this); }
 
   if(Handlebars.Utils.isEmpty(conditional)) {
     return options.inverse(this);
@@ -148,8 +157,7 @@ Handlebars.registerHelper('unless', function(conditional, options) {
 });
 
 Handlebars.registerHelper('with', function(context, options) {
-  var type = toString.call(context);
-  if(type === functionType) { context = context.call(this); }
+  if (isFunction(context)) { context = context.call(this); }
 
   if (!Handlebars.Utils.isEmpty(context)) return options.fn(context);
 });
diff --git a/lib/handlebars/utils.js b/lib/handlebars/utils.js
index 9a4ff7724..0887f8512 100644
--- a/lib/handlebars/utils.js
+++ b/lib/handlebars/utils.js
@@ -1,6 +1,7 @@
 exports.attach = function(Handlebars) {
 
-var toString = Object.prototype.toString;
+var toString = Object.prototype.toString,
+    isArray = Array.isArray;
 
 // BEGIN(BROWSER)
 
@@ -71,7 +72,7 @@ Handlebars.Utils = {
   isEmpty: function(value) {
     if (!value && value !== 0) {
       return true;
-    } else if(toString.call(value) === "[object Array]" && value.length === 0) {
+    } else if (isArray(value) && value.length === 0) {
       return true;
     } else {
       return false;

From 623fdad59fb2b17351cc24c87ab46325ebf0916c Mon Sep 17 00:00:00 2001
From: kpdecker <kpdecker@gmail.com>
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 <kpdecker@gmail.com>
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<j; i++) {
         if (data) { data.index = i; }
         ret = ret + fn(context[i], { data: data });
@@ -156,8 +166,7 @@ Handlebars.registerHelper('each', function(context, options) {
 });
 
 Handlebars.registerHelper('if', function(conditional, options) {
-  var type = toString.call(conditional);
-  if(type === functionType) { conditional = conditional.call(this); }
+  if (isFunction(conditional)) { conditional = conditional.call(this); }
 
   if(Handlebars.Utils.isEmpty(conditional)) {
     return options.inverse(this);
@@ -171,8 +180,7 @@ Handlebars.registerHelper('unless', function(conditional, options) {
 });
 
 Handlebars.registerHelper('with', function(context, options) {
-  var type = toString.call(context);
-  if(type === functionType) { context = context.call(this); }
+  if (isFunction(context)) { context = context.call(this); }
 
   if (!Handlebars.Utils.isEmpty(context)) return options.fn(context);
 });
@@ -846,7 +854,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 "";
     }
 
@@ -862,7 +870,7 @@ Handlebars.Utils = {
   isEmpty: function(value) {
     if (!value && value !== 0) {
       return true;
-    } else if(toString.call(value) === "[object Array]" && value.length === 0) {
+    } else if (isArray(value) && value.length === 0) {
       return true;
     } else {
       return false;
@@ -2169,6 +2177,24 @@ JavaScriptCompiler.isValidJavaScriptVariableName = function(name) {
 ;
 // lib/handlebars/runtime.js
 
+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
@@ -2188,7 +2214,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);
@@ -2202,23 +2228,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;
@@ -2249,7 +2275,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");
diff --git a/dist/handlebars.runtime.js b/dist/handlebars.runtime.js
index 2372cb5b7..15278c7fa 100644
--- a/dist/handlebars.runtime.js
+++ b/dist/handlebars.runtime.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<j; i++) {
         if (data) { data.index = i; }
         ret = ret + fn(context[i], { data: data });
@@ -156,8 +166,7 @@ Handlebars.registerHelper('each', function(context, options) {
 });
 
 Handlebars.registerHelper('if', function(conditional, options) {
-  var type = toString.call(conditional);
-  if(type === functionType) { conditional = conditional.call(this); }
+  if (isFunction(conditional)) { conditional = conditional.call(this); }
 
   if(Handlebars.Utils.isEmpty(conditional)) {
     return options.inverse(this);
@@ -171,8 +180,7 @@ Handlebars.registerHelper('unless', function(conditional, options) {
 });
 
 Handlebars.registerHelper('with', function(context, options) {
-  var type = toString.call(context);
-  if(type === functionType) { context = context.call(this); }
+  if (isFunction(context)) { context = context.call(this); }
 
   if (!Handlebars.Utils.isEmpty(context)) return options.fn(context);
 });
@@ -235,7 +243,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 "";
     }
 
@@ -251,7 +259,7 @@ Handlebars.Utils = {
   isEmpty: function(value) {
     if (!value && value !== 0) {
       return true;
-    } else if(toString.call(value) === "[object Array]" && value.length === 0) {
+    } else if (isArray(value) && value.length === 0) {
       return true;
     } else {
       return false;
@@ -261,6 +269,24 @@ Handlebars.Utils = {
 ;
 // lib/handlebars/runtime.js
 
+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
@@ -280,7 +306,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);
@@ -294,23 +320,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;
@@ -341,7 +367,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 da3788384b4d7e632bde58c92a3a8c8ab6da197c Mon Sep 17 00:00:00 2001
From: kpdecker <kpdecker@gmail.com>
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 <kpdecker@gmail.com>
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 <kpdecker@gmail.com>
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 <kpdecker@gmail.com>
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 <kpdecker@gmail.com>
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 <kpdecker@gmail.com>
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: "<h1>{{header}}</h1>{{#if items}}<ul>{{#each items}}{{#if current}}" +
-                "<li><strong>{{name}}</strong></li>{{^}}" +
-                "<li><a href=\"{{url}}\">{{name}}</a></li>{{/if}}" +
-                "{{/each}}</ul>{{^}}<p>The list is empty.</p>{{/if}}",
-
-    dust:       "<h1>{header}</h1>\n"                             +
-                "{?items}\n"                                      +
-                "  <ul>\n"                                        +
-                "    {#items}\n"                                  +
-                "      {#current}\n"                              +
-                "        <li><strong>{name}</strong></li>\n"      +
-                "      {:else}\n"                                 +
-                "        <li><a href=\"{url}\">{name}</a></li>\n" +
-                "      {/current}\n"                              +
-                "    {/items}\n"                                  +
-                "  </ul>\n"                                       +
-                "{:else}\n"                                       +
-                "  <p>The list is empty.</p>\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 @@
+<h1>{{header}}</h1>
+{{#if items}}
+  <ul>
+    {{#each items}}
+      {{#if current}}
+        <li><strong>{{name}}</strong></li>
+      {{^}}
+        <li><a href=\"{{url}}\">{{name}}</a></li>
+      {{/if}}
+    {{/each}}
+  </ul>
+{{^}}
+  <p>The list is empty.</p>
+{{/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:       "<h1>{header}</h1>\n"                             +
+              "{?items}\n"                                      +
+              "  <ul>\n"                                        +
+              "    {#items}\n"                                  +
+              "      {#current}\n"                              +
+              "        <li><strong>{name}</strong></li>\n"      +
+              "      {:else}\n"                                 +
+              "        <li><a href=\"{url}\">{name}</a></li>\n" +
+              "      {/current}\n"                              +
+              "    {/items}\n"                                  +
+              "  </ul>\n"                                       +
+              "{:else}\n"                                       +
+              "  <p>The list is empty.</p>\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 <kpdecker@gmail.com>
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 <kpdecker@gmail.com>
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<l; i++) {
@@ -99,6 +103,8 @@ BenchWarmer.prototype = {
             }
           }
         }
+
+        callback();
       }
     });
 
@@ -125,6 +131,9 @@ BenchWarmer.prototype = {
           moe   = count * bench.stats.rme / 100;
 
       out = Math.round(count / 1000) + " ±" + Math.round(moe / 1000) + " (" + bench.cycles + ")";
+
+
+      this.times[bench.suiteName][bench.benchName] = Math.round(count / 1000);
     } else {
       if (bench.error.message === 'EWOT') {
         out = 'NA';

From 672aeda17f9309ad7e4d31ef1c3b9a302fbb5baf Mon Sep 17 00:00:00 2001
From: kpdecker <kpdecker@gmail.com>
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 <kpdecker@gmail.com>
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 <kpdecker@gmail.com>
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 <kpdecker@gmail.com>
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 <kpdecker@gmail.com>
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 <kpdecker@gmail.com>
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 <kpdecker@gmail.com>
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 <kpdecker@gmail.com>
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}}
         <li><strong>{{name}}</strong></li>
       {{^}}
-        <li><a href=\"{{url}}\">{{name}}</a></li>
+        <li><a href="{{url}}">{{name}}</a></li>
       {{/if}}
     {{/each}}
   </ul>

From 709d57b3c15aa0d766152362b031ee9d27c054f4 Mon Sep 17 00:00:00 2001
From: kpdecker <kpdecker@gmail.com>
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 <kpdecker@gmail.com>
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 <kpdecker@gmail.com>
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 @@
+<h1>{header}</h1>
+{?items}
+  <ul>
+    {#items}
+      {#current}
+        <li><strong>{name}</strong></li>
+      {:else}
+        <li><a href="{url}">{name}</a></li>
+      {/current}
+    {/items}
+  </ul>
+{:else}
+  <p>The list is empty.</p>
+{/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 @@
+<h1><%= @header() %></h1>
+<% if @items.length: %>
+  <ul>
+    <% for item in @items: %>
+      <% if item.current: %>
+        <li><strong><%= item.name %></strong></li>
+      <% else: %>
+        <li><a href="<%= item.url %>"><%= item.name %></a></li>
+      <% end %>
+    <% end %>
+  </ul>
+<% else: %>
+  <p>The list is empty.</p>
+<% 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:       "<h1>{header}</h1>\n"                             +
-              "{?items}\n"                                      +
-              "  <ul>\n"                                        +
-              "    {#items}\n"                                  +
-              "      {#current}\n"                              +
-              "        <li><strong>{name}</strong></li>\n"      +
-              "      {:else}\n"                                 +
-              "        <li><a href=\"{url}\">{name}</a></li>\n" +
-              "      {/current}\n"                              +
-              "    {/items}\n"                                  +
-              "  </ul>\n"                                       +
-              "{:else}\n"                                       +
-              "  <p>The list is empty.</p>\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 @@
+<h1>{{header}}</h1>
+{{#hasItems}}
+  <ul>
+    {{#items}}
+      {{#current}}
+        <li><strong>{{name}}</strong></li>
+      {{/current}}
+      {{^current}}
+        <li><a href="{{url}}">{{name}}</a></li>
+      {{/current}}
+    {{/items}}
+  </ul>
+{{/hasItems}}

From 2dac9c978a16ee353661c003769266dde337ff41 Mon Sep 17 00:00:00 2001
From: kpdecker <kpdecker@gmail.com>
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 <kpdecker@gmail.com>
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 <kpdecker@gmail.com>
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 <kpdecker@gmail.com>
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 <kpdecker@gmail.com>
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 <kpdecker@gmail.com>
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 <kpdecker@gmail.com>
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 <kpdecker@gmail.com>
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 <kpdecker@gmail.com>
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 <laurie.harper.ca@gmail.com>
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 <kpdecker@gmail.com>
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 <kpdecker@gmail.com>
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<l; i++) {
-      var name = names[i];
-
-      if(benchSize < name.length) { benchSize = name.length; }
-    }
-
-    this.nameSize = benchSize + 2;
-    this.benchSize = 20;
-    var horSize = 0;
-
-    this.startLine("ops/msec");
-    horSize = horSize + this.benchSize;
-    for(i=0, l=names.length; i<l; i++) {
-      print(names[i] + new Array(this.benchSize - names[i].length + 1).join(" "));
-      horSize = horSize + this.benchSize;
-    }
 
-    print("WINNER(S)");
-    horSize = horSize + "WINNER(S)".length;
+  bench: function(callback) {
+    var self = this;
 
-    print("\n" + new Array(horSize + 1).join("-"));
+    this.printHeader('ops/msec', true);
 
     Benchmark.invoke(this.benchmarks, {
       name: "run",
       onComplete: function() {
+        self.scaleTimes();
+
         self.startLine('');
 
+        print('\n');
+        self.printHeader('scaled');
+        _.each(self.scaled, function(value, name) {
+          self.startLine(name);
+
+          _.each(self.names, function(lang) {
+            self.writeValue(value[lang] || '');
+          });
+        });
+
         var errors = false, prop, bench;
         for(prop in self.errors) {
           if (self.errors.hasOwnProperty(prop)
@@ -112,6 +105,46 @@ BenchWarmer.prototype = {
 
     print("\n");
   },
+
+  scaleTimes: function() {
+    var scaled = this.scaled = {};
+    _.each(this.times, function(times, name) {
+      var output = scaled[name] = {};
+
+      _.each(times, function(time, lang) {
+        output[lang] = ((time - this.minimum) / (this.maximum - this.minimum) * 100).toFixed(2);
+      }, this);
+    }, this);
+  },
+
+  printHeader: function(title, winners) {
+    var benchSize = 0, names = this.names, i, l;
+
+    for(i=0, l=names.length; i<l; i++) {
+      var name = names[i];
+
+      if(benchSize < name.length) { benchSize = name.length; }
+    }
+
+    this.nameSize = benchSize + 2;
+    this.benchSize = 20;
+    var horSize = 0;
+
+    this.startLine(title);
+    horSize = horSize + this.benchSize;
+    for(i=0, l=names.length; i<l; i++) {
+      this.writeValue(names[i]);
+      horSize = horSize + this.benchSize;
+    }
+
+    if (winners) {
+      print("WINNER(S)");
+      horSize = horSize + "WINNER(S)".length;
+    }
+
+    print("\n" + new Array(horSize + 1).join("-"));
+  },
+
   startLine: function(name) {
     var winners = Benchmark.map(this.winners(this.currentBenches), function(bench) {
       return bench.name.split(": ")[1];
@@ -121,9 +154,10 @@ BenchWarmer.prototype = {
 
     print(winners.join(", "));
     print("\n");
-    var padding = Math.max(this.benchSize - name.length + 1, 0);
-    name = name + new Array(padding).join(" ");
-    print(name);
+
+    if (name) {
+      this.writeValue(name);
+    }
   },
   writeBench: function(bench) {
     var out;
@@ -151,7 +185,10 @@ BenchWarmer.prototype = {
         out = 'E';
       }
     }
+    this.writeValue(out);
+  },
 
+  writeValue: function(out) {
     var padding = this.benchSize - out.length + 1;
     out = out + new Array(padding).join(" ");
     print(out);
diff --git a/bench/util/template-runner.js b/bench/util/template-runner.js
new file mode 100644
index 000000000..8863ccd6f
--- /dev/null
+++ b/bench/util/template-runner.js
@@ -0,0 +1,27 @@
+var _ = require('underscore'),
+    BenchWarmer = require('./benchwarmer'),
+    templates = require('../templates');
+
+module.exports = function(grunt, makeSuite, 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 || (grep && !grep.test(name))) {
+      return;
+    }
+
+    warmer.suite(name, function(bench) {
+      makeSuite(bench, name, template, handlebarsOnly);
+    });
+  });
+
+  warmer.bench(function() {
+    callback && callback(warmer.times, warmer.scaled);
+  });
+};

From 0fe78f379ab85e586381e167aecd7d5527984697 Mon Sep 17 00:00:00 2001
From: kpdecker <kpdecker@gmail.com>
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 <kpdecker@gmail.com>
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 <kpdecker@gmail.com>
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 <kpdecker@gmail.com>
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 <kpdecker@gmail.com>
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 <kpdecker@gmail.com>
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 <kpdecker@gmail.com>
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<l; i++) {

From 6a23391a9a72e263248f6ffa2535cfae67a2b135 Mon Sep 17 00:00:00 2001
From: kpdecker <kpdecker@gmail.com>
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 <kpdecker@gmail.com>
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 = {
   "&": "&amp;",
   "<": "&lt;",
@@ -22,9 +15,9 @@ var escape = {
 var badChars = /[&<>"'`]/g;
 var possible = /[&<>"'`]/;
 
-var escapeChar = function(chr) {
+function escapeChar(chr) {
   return escape[chr] || "&amp;";
-};
+}
 
 export function extend(obj, value) {
   for(var key in value) {

From e676e43dc5e22dfb3847ff28321b29863975cc21 Mon Sep 17 00:00:00 2001
From: kpdecker <kpdecker@gmail.com>
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 <kpdecker@gmail.com>
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 <kpdecker@gmail.com>
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 <kpdecker@gmail.com>
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 <kpdecker@gmail.com>
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 <kpdecker@gmail.com>
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 <justin@todaymade.com>
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 <kpdecker@gmail.com>
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 <alex.navasardyan@yahoo.com>
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 <kpdecker@gmail.com>
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 <kpdecker@gmail.com>
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 <kpdecker@gmail.com>
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 <kpdecker@gmail.com>
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 <kpdecker@gmail.com>
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 <kpdecker@gmail.com>
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 <kpdecker@gmail.com>
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 <kpdecker@gmail.com>
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 <kpdecker@gmail.com>
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 <kpdecker@gmail.com>
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 <kpdecker@gmail.com>
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 = {
   "&": "&amp;",

From e5a4889b36b94598e2ee80177bc0b339abda9dd2 Mon Sep 17 00:00:00 2001
From: kpdecker <kpdecker@gmail.com>
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 <kpdecker@gmail.com>
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 <kpdecker@gmail.com>
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 <kpdecker@gmail.com>
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 <kpdecker@gmail.com>
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 <kpdecker@gmail.com>
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 <kpdecker@gmail.com>
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 <kpdecker@gmail.com>
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 <kpdecker@gmail.com>
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 <kpdecker@gmail.com>
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 <dennis.kuczynski@gmail.com>
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<j; i++) {
-          if (data) { data.index = i; }
+          if (data) {
+            data.index = i;
+            data.first = (i === 0) ? true : false;
+            data.last  = (i === (context.length-1)) ? true : false;
+          }
           ret = ret + fn(context[i], { data: data });
         }
       } else {
diff --git a/spec/builtins.js b/spec/builtins.js
index c6789645e..140f5f510 100644
--- a/spec/builtins.js
+++ b/spec/builtins.js
@@ -95,6 +95,46 @@ describe('builtin helpers', function() {
       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 @first", function() {
+      var string = "{{#each goodbyes}}{{#if @first}}{{text}}! {{/if}}{{/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, "goodbye! cruel world!", "The @first variable is used");
+    });
+
+    it("each with nested @first", function() {
+      var string = "{{#each goodbyes}}({{#if @first}}{{text}}! {{/if}}{{#each ../goodbyes}}{{#if @first}}{{text}}!{{/if}}{{/each}}{{#if @first}} {{text}}!{{/if}}) {{/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, "(goodbye! goodbye! goodbye!) (goodbye!) (goodbye!) cruel world!", "The @first variable is used");
+    });
+
+    it("each with @last", function() {
+      var string = "{{#each goodbyes}}{{#if @last}}{{text}}! {{/if}}{{/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, "GOODBYE! cruel world!", "The @last variable is used");
+    });
+
+    it("each with nested @last", function() {
+      var string = "{{#each goodbyes}}({{#if @last}}{{text}}! {{/if}}{{#each ../goodbyes}}{{#if @last}}{{text}}!{{/if}}{{/each}}{{#if @last}} {{text}}!{{/if}}) {{/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, "(GOODBYE!) (GOODBYE!) (GOODBYE! GOODBYE! GOODBYE!) cruel world!", "The @last 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"};

From 3150ef15da341d9fff4722ea4528e1d5f85fab19 Mon Sep 17 00:00:00 2001
From: Dennis Kuczynski <dennis.kuczynski@gmail.com>
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<j; i++) {
           if (data) {
             data.index = i;
-            data.first = (i === 0) ? true : false;
-            data.last  = (i === (context.length-1)) ? true : false;
+            data.first = (i === 0)
+            data.last  = (i === (context.length-1));
           }
           ret = ret + fn(context[i], { data: data });
         }

From e9350f29327289b3c9a5950d6392254d43767065 Mon Sep 17 00:00:00 2001
From: kpdecker <kpdecker@gmail.com>
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 <dmarcotte@gmail.com>
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 <kpdecker@gmail.com>
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 <kpdecker@gmail.com>
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 <kpdecker@gmail.com>
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 <kpdecker@gmail.com>
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 <kpdecker@gmail.com>
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 <kpdecker@gmail.com>
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 <kpdecker@gmail.com>
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 <kpdecker@gmail.com>
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 <kpdecker@gmail.com>
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 <kpdecker@gmail.com>
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 <kpdecker@gmail.com>
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 <kpdecker@gmail.com>
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 <kpdecker@gmail.com>
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 <kpdecker@gmail.com>
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\/.]
 <mu>"{{"                         return 'OPEN';
 
 <mu>"="                          return 'EQUALS';
-<mu>"."/[}\/ ]                   return 'ID';
 <mu>".."                         return 'ID';
+<mu>"."/{LOOKAHEAD}             return 'ID';
 <mu>[\/.]                        return 'SEP';
 <mu>\s+                          /*ignore whitespace*/
 <mu>"}}}"                        this.popState(); return 'CLOSE_UNESCAPED';
@@ -67,9 +70,9 @@ ID    [^\s!"#%-,\.\/;->@\[-\^`\{-~]+/[=}\s\/.]
 <mu>'"'("\\"["]|[^"])*'"'        yytext = strip(1,2).replace(/\\"/g,'"'); return 'STRING';
 <mu>"'"("\\"[']|[^'])*"'"        yytext = strip(1,2).replace(/\\'/g,"'"); return 'STRING';
 <mu>"@"                          return 'DATA';
-<mu>"true"/[}\s]                 return 'BOOLEAN';
-<mu>"false"/[}\s]                return 'BOOLEAN';
-<mu>\-?[0-9]+/[}\s]              return 'INTEGER';
+<mu>"true"/{LITERAL_LOOKAHEAD}   return 'BOOLEAN';
+<mu>"false"/{LITERAL_LOOKAHEAD}  return 'BOOLEAN';
+<mu>\-?[0-9]+/{LITERAL_LOOKAHEAD} return 'INTEGER';
 
 <mu>{ID}                         return 'ID';
 

From 5b483ff78decee27c221fabdbccffec614cedadd Mon Sep 17 00:00:00 2001
From: kpdecker <kpdecker@gmail.com>
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&lt;');
+    shouldCompileTo(' {{(foo}} ', hash, 'bar&lt; ');
+    shouldCompileTo(' {{foo)}} ', hash, ' bar&lt;');
+
+    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 <kpdecker@gmail.com>
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 <kpdecker@gmail.com>
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<l; i++) {
-      statement = statements[i];
-      this[statement.type](statement);
+      this.accept(statements[i]);
     }
     this.isSimple = l === 1;
 

From d5544d896997d50ad7d045128b4a43596323e429 Mon Sep 17 00:00:00 2001
From: kpdecker <kpdecker@gmail.com>
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}
 
 <mu>"="                          return 'EQUALS';
 <mu>".."                         return 'ID';
-<mu>"."/{LOOKAHEAD}             return 'ID';
+<mu>"."/{LOOKAHEAD}              return 'ID';
 <mu>[\/.]                        return 'SEP';
 <mu>\s+                          /*ignore whitespace*/
 <mu>"}}}"                        this.popState(); return 'CLOSE_UNESCAPED';

From 4e4920cc78b1ad68ca325d9677b2fb8f3dfbaa3a Mon Sep 17 00:00:00 2001
From: kpdecker <kpdecker@gmail.com>
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}
 
 <com>[\s\S]*?"--}}"              strip(0,4); this.popState(); return 'COMMENT';
 
-<mu>"{{>"                        return 'OPEN_PARTIAL';
-<mu>"{{#"                        return 'OPEN_BLOCK';
-<mu>"{{/"                        return 'OPEN_ENDBLOCK';
-<mu>"{{^"                        return 'OPEN_INVERSE';
-<mu>"{{"\s*"else"                return 'OPEN_INVERSE';
-<mu>"{{{"                        return 'OPEN_UNESCAPED';
-<mu>"{{&"                        return 'OPEN';
+<mu>"{{"{LEFT_STRIP}?">"         return 'OPEN_PARTIAL';
+<mu>"{{"{LEFT_STRIP}?"#"         return 'OPEN_BLOCK';
+<mu>"{{"{LEFT_STRIP}?"/"         return 'OPEN_ENDBLOCK';
+<mu>"{{"{LEFT_STRIP}?"^"         return 'OPEN_INVERSE';
+<mu>"{{"{LEFT_STRIP}?\s*"else"   return 'OPEN_INVERSE';
+<mu>"{{"{LEFT_STRIP}?"{"         return 'OPEN_UNESCAPED';
+<mu>"{{"{LEFT_STRIP}?"&"         return 'OPEN';
 <mu>"{{!--"                      this.popState(); this.begin('com');
 <mu>"{{!"[\s\S]*?"}}"            strip(3,5); this.popState(); return 'COMMENT';
-<mu>"{{"                         return 'OPEN';
+<mu>"{{"{LEFT_STRIP}?            return 'OPEN';
 
 <mu>"="                          return 'EQUALS';
 <mu>".."                         return 'ID';
 <mu>"."/{LOOKAHEAD}              return 'ID';
 <mu>[\/.]                        return 'SEP';
 <mu>\s+                          /*ignore whitespace*/
-<mu>"}}}"                        this.popState(); return 'CLOSE_UNESCAPED';
-<mu>"}}"                         this.popState(); return 'CLOSE';
+<mu>"}"{RIGHT_STRIP}?"}}"        this.popState(); return 'CLOSE_UNESCAPED';
+<mu>{RIGHT_STRIP}?"}}"           this.popState(); return 'CLOSE';
 <mu>'"'("\\"["]|[^"])*'"'        yytext = strip(1,2).replace(/\\"/g,'"'); return 'STRING';
 <mu>"'"("\\"[']|[^'])*"'"        yytext = strip(1,2).replace(/\\'/g,"'"); return 'STRING';
 <mu>"@"                          return 'DATA';

From 151e6d49c05dd93305a6ee1cad57154a01396ea0 Mon Sep 17 00:00:00 2001
From: kpdecker <kpdecker@gmail.com>
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<l; i++) {
       this.accept(statements[i]);

From 1de6cee1b247f5dd133e1ff3141b893fb427ff94 Mon Sep 17 00:00:00 2001
From: kpdecker <kpdecker@gmail.com>
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 <kpdecker@gmail.com>
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 <kpdecker@gmail.com>
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 <kpdecker@gmail.com>
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&lt;');
-    shouldCompileTo(' {{(foo}} ', hash, 'bar&lt; ');
-    shouldCompileTo(' {{foo)}} ', hash, ' bar&lt;');
+    shouldCompileTo(' {{~foo~}} ', hash, 'bar&lt;');
+    shouldCompileTo(' {{~foo}} ', hash, 'bar&lt; ');
+    shouldCompileTo(' {{foo~}} ', hash, ' bar&lt;');
 
-    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 <christian@cwesselhoeft.com>
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 <kpdecker@gmail.com>
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 <kpdecker@gmail.com>
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 <kpdecker@gmail.com>
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 @@
 		<tags>handlebars mustache template html</tags>
 	</metadata>
 	<files>
-		<file src="dist\handlebars.js" target="Content\Scripts" />
+		<file src="handlebars.js" target="Content\Scripts" />
 	</files>
 </package>
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>/, '<version>' + version + '</version>']
+          ['lib/handlebars/base.js', /var VERSION = "(.*)";/, 'var VERSION = "' + version + '";'],
+          ['components/bower.json', /"version":.*/, '"version": "' + version + '",'],
+          ['components/handlebars.js.nuspec', /<version>.*<\/version>/, '<version>' + version + '</version>']
         ],
         function(args, callback) {
           replace.apply(undefined, args);

From 23ff9829d5d526fa51288671f53b43ac9f39e1de Mon Sep 17 00:00:00 2001
From: kpdecker <kpdecker@gmail.com>
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 <kpdecker@gmail.com>
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>/, '<version>' + 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>/, '<version>' + 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 <kpdecker@gmail.com>
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 <kpdecker@gmail.com>
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 <kpdecker@gmail.com>
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 <kpdecker@gmail.com>
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 <kpdecker@gmail.com>
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 <kpdecker@gmail.com>
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 <kpdecker@gmail.com>
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 <kpdecker@gmail.com>
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 <kpdecker@gmail.com>
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 @@
 <package>
 	<metadata>
 		<id>handlebars.js</id>
-		<version>1.0.0</version>
+		<version>1.1.0</version>
 		<authors>handlebars.js Authors</authors>
 		<licenseUrl>https://github.com/wycats/handlebars.js/blob/master/LICENSE</licenseUrl>
 		<projectUrl>https://github.com/wycats/handlebars.js/</projectUrl>
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 <kpdecker@gmail.com>
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 <kpdecker@gmail.com>
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 <kpdecker@gmail.com>
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": {}
 }
