From a1df862f0633b26ab2517949d5333412915c9a6a Mon Sep 17 00:00:00 2001
From: Joe Wilm <jdwilm@gmail.com>
Date: Wed, 1 Jan 2014 20:21:27 -0800
Subject: [PATCH 01/53] Add koa-hbs to "handlebars in the wild" list

---
 README.markdown | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/README.markdown b/README.markdown
index 0297c3589..beefd41ef 100644
--- a/README.markdown
+++ b/README.markdown
@@ -371,6 +371,8 @@ Handlebars in the Wild
   with 100+ handlebars helpers.
 * [hbs](http://github.com/donpark/hbs): An Express.js view engine adapter for Handlebars.js,
   from Don Park.
+* [koa-hbs](https://github.com/jwilm/koa-hbs): [koa](https://github.com/koajs/koa) generator based
+  renderer for Handlebars.js.
 * [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.
 * [jQuery plugin](http://71104.github.io/jquery-handlebars/): allows you to use

From 70b27bcef6648e563c8252ea3b68af50dc1afb9c Mon Sep 17 00:00:00 2001
From: Brett Bim <bbim@onetechnologies.net>
Date: Thu, 2 Jan 2014 11:36:01 -0600
Subject: [PATCH 02/53] Fix for issue #673 - Parser task did not execute
 correctly on windows environments because of forward/backward slash issues

---
 tasks/parser.js | 8 +++++++-
 1 file changed, 7 insertions(+), 1 deletion(-)

diff --git a/tasks/parser.js b/tasks/parser.js
index f6a72c617..47533cedf 100644
--- a/tasks/parser.js
+++ b/tasks/parser.js
@@ -4,7 +4,13 @@ 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'});
+    var cmd = './node_modules/.bin/jison';
+
+    if(process.platform === 'win32'){
+        cmd = 'node_modules\\.bin\\jison.cmd';
+    }
+
+    var child = childProcess.spawn(cmd, ['-m', 'js', 'src/handlebars.yy', 'src/handlebars.l'], {stdio: 'inherit'});
     child.on('exit', function(code) {
       if (code != 0) {
         grunt.fatal('Jison failure: ' + code);

From 6d996ef2706c6f0f9fd60e18ea90b5565096c568 Mon Sep 17 00:00:00 2001
From: kpdecker <kpdecker@gmail.com>
Date: Sat, 4 Jan 2014 09:14:22 -0600
Subject: [PATCH 03/53] Simplify ambiguous code

Remove if conditional in favor of boolean failover.
---
 lib/handlebars/compiler/compiler.js            |  1 +
 lib/handlebars/compiler/javascript-compiler.js | 18 +++++++++---------
 2 files changed, 10 insertions(+), 9 deletions(-)

diff --git a/lib/handlebars/compiler/compiler.js b/lib/handlebars/compiler/compiler.js
index 461f98b65..8389f8482 100644
--- a/lib/handlebars/compiler/compiler.js
+++ b/lib/handlebars/compiler/compiler.js
@@ -364,6 +364,7 @@ Compiler.prototype = {
     var options    = this.options;
 
     // if ambiguous, we can possibly resolve the ambiguity now
+    // An eligible helper is one that does not have a complex path, i.e. `this.foo`, `../foo` etc.
     if (isEligible && !isHelper) {
       var name = sexpr.id.parts[0];
 
diff --git a/lib/handlebars/compiler/javascript-compiler.js b/lib/handlebars/compiler/javascript-compiler.js
index 75390685f..cdf16935e 100644
--- a/lib/handlebars/compiler/javascript-compiler.js
+++ b/lib/handlebars/compiler/javascript-compiler.js
@@ -220,7 +220,7 @@ JavaScriptCompiler.prototype = {
   // 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
+  // `{{#this.foo}}...{{/this.foo}}`, resolve the value of `foo`, and
   // replace it on the stack with the result of properly
   // invoking blockHelperMissing.
   blockValue: function() {
@@ -244,8 +244,11 @@ JavaScriptCompiler.prototype = {
   ambiguousBlockValue: function() {
     this.context.aliases.blockHelperMissing = 'helpers.blockHelperMissing';
 
+    // We're being a bit cheeky and reusing the options value from the prior exec
     var params = ["depth0"];
-    this.setupParams(0, params);
+    this.setupParams(0, params, true);
+
+    this.flushInline();
 
     var current = this.topStack();
     params.splice(1, 0, current);
@@ -555,15 +558,12 @@ JavaScriptCompiler.prototype = {
     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();
 
-    if (helper.paramsInit) {
-      this.pushSource(helper.paramsInit);
-    }
-    this.pushSource('if (helper = ' + helperName + ') { ' + nextStack + ' = helper.call(' + helper.callParams + '); }');
-    this.pushSource('else { helper = ' + nonHelper + '; ' + nextStack + ' = typeof helper === functionType ? helper.call(' + helper.callParams + ') : helper; }');
+    this.push(
+      '((helper = ' + helperName + ' || ' + nonHelper
+        + (helper.paramsInit ? '),(' + helper.paramsInit : '') + '),'
+      + '(typeof helper === functionType ? helper.call(' + helper.callParams + ') : helper))');
   },
 
   // [invokePartial]

From 3d9685c6a2a6bd40bea9946454312e8f77e4cca2 Mon Sep 17 00:00:00 2001
From: Blake Embrey <hello@blakeembrey.com>
Date: Sun, 5 Jan 2014 12:14:24 -0500
Subject: [PATCH 04/53] Remove redundant conditions.

---
 lib/handlebars/compiler/javascript-compiler.js | 5 +----
 1 file changed, 1 insertion(+), 4 deletions(-)

diff --git a/lib/handlebars/compiler/javascript-compiler.js b/lib/handlebars/compiler/javascript-compiler.js
index cdf16935e..8cbb9e4f2 100644
--- a/lib/handlebars/compiler/javascript-compiler.js
+++ b/lib/handlebars/compiler/javascript-compiler.js
@@ -930,10 +930,7 @@ for(var i=0, l=reservedWords.length; i<l; i++) {
 }
 
 JavaScriptCompiler.isValidJavaScriptVariableName = function(name) {
-  if(!JavaScriptCompiler.RESERVED_WORDS[name] && /^[a-zA-Z_$][0-9a-zA-Z_$]*$/.test(name)) {
-    return true;
-  }
-  return false;
+  return !JavaScriptCompiler.RESERVED_WORDS[name] && /^[a-zA-Z_$][0-9a-zA-Z_$]*$/.test(name);
 };
 
 export default JavaScriptCompiler;

From fc60f16475c3a204849371f485c9316af40765e7 Mon Sep 17 00:00:00 2001
From: kpdecker <kpdecker@gmail.com>
Date: Mon, 6 Jan 2014 02:40:43 -0600
Subject: [PATCH 05/53] Fix DATA value in stringParams mode

Fixes #699
---
 lib/handlebars/compiler/ast.js |  1 +
 spec/string-params.js          | 15 +++++++++++++++
 2 files changed, 16 insertions(+)

diff --git a/lib/handlebars/compiler/ast.js b/lib/handlebars/compiler/ast.js
index 002fd3be6..dda682e7e 100644
--- a/lib/handlebars/compiler/ast.js
+++ b/lib/handlebars/compiler/ast.js
@@ -189,6 +189,7 @@ var AST = {
     LocationInfo.call(this, locInfo);
     this.type = "DATA";
     this.id = id;
+    this.stringModeValue = id.stringModeValue;
   },
 
   StringNode: function(string, locInfo) {
diff --git a/spec/string-params.js b/spec/string-params.js
index 920b85592..1cebc6f08 100644
--- a/spec/string-params.js
+++ b/spec/string-params.js
@@ -158,4 +158,19 @@ describe('string params mode', function() {
     var result = template({}, {helpers: helpers});
     equals(result, "WITH");
   });
+
+  it('should handle DATA', function() {
+    var template = CompilerContext.compile('{{foo @bar}}', { stringParams: true });
+
+    var helpers = {
+      foo: function(bar, options) {
+        equal(bar, 'bar');
+        equal(options.types[0], 'DATA');
+        return 'Foo!';
+      }
+    };
+
+    var result = template({}, { helpers: helpers });
+    equal(result, 'Foo!');
+  });
 });

From 7197289364cee84a902a2c5d551f0e69637182d5 Mon Sep 17 00:00:00 2001
From: kpdecker <kpdecker@gmail.com>
Date: Mon, 6 Jan 2014 02:42:08 -0600
Subject: [PATCH 06/53] Update release instructions

---
 README.markdown | 3 +--
 1 file changed, 1 insertion(+), 2 deletions(-)

diff --git a/README.markdown b/README.markdown
index beefd41ef..63edee879 100644
--- a/README.markdown
+++ b/README.markdown
@@ -437,8 +437,7 @@ Handlebars utilizes the [release yeoman generator][generator-release] to perform
 A full release may be completed with the following:
 
 ```
-yo release:notes patch
-yo release:release patch
+yo release
 npm publish
 yo release:publish cdnjs handlebars.js dist/cdnjs/
 yo release:publish components handlebars.js dist/components/

From 607748af3bd3135fc3113bdbfdd285e5d9edb97b Mon Sep 17 00:00:00 2001
From: kpdecker <kpdecker@gmail.com>
Date: Mon, 6 Jan 2014 02:45:02 -0600
Subject: [PATCH 07/53] Add better logging of test compile errors

---
 spec/amd.html       | 11 ++++++++---
 spec/env/browser.js |  7 ++++++-
 spec/env/node.js    |  7 ++++++-
 spec/env/runtime.js |  7 ++++++-
 spec/index.html     | 11 ++++++++---
 5 files changed, 34 insertions(+), 9 deletions(-)

diff --git a/spec/amd.html b/spec/amd.html
index 544b57611..a095650f4 100644
--- a/spec/amd.html
+++ b/spec/amd.html
@@ -45,9 +45,14 @@
       };
 
       function safeEval(templateSpec) {
-        var ret;
-        eval('ret = ' + templateSpec);
-        return ret;
+        try {
+          var ret;
+          eval('ret = ' + templateSpec);
+          return ret;
+        } catch (err) {
+          console.error(templateSpec);
+          throw err;
+        }
       }
     </script>
     <script>
diff --git a/spec/env/browser.js b/spec/env/browser.js
index 9f69e74d7..bcf4259d3 100644
--- a/spec/env/browser.js
+++ b/spec/env/browser.js
@@ -19,5 +19,10 @@ global.CompilerContext = {
 };
 
 function safeEval(templateSpec) {
-  return eval('(' + templateSpec + ')');
+  try {
+    return eval('(' + templateSpec + ')');
+  } catch (err) {
+    console.error(templateSpec);
+    throw err;
+  }
 }
diff --git a/spec/env/node.js b/spec/env/node.js
index 808c07ef4..d7db85346 100644
--- a/spec/env/node.js
+++ b/spec/env/node.js
@@ -14,5 +14,10 @@ global.CompilerContext = {
 };
 
 function safeEval(templateSpec) {
-  return eval('(' + templateSpec + ')');
+  try {
+    return eval('(' + templateSpec + ')');
+  } catch (err) {
+    console.error(templateSpec);
+    throw err;
+  }
 }
diff --git a/spec/env/runtime.js b/spec/env/runtime.js
index e73d11115..5a2dcd9b3 100644
--- a/spec/env/runtime.js
+++ b/spec/env/runtime.js
@@ -39,5 +39,10 @@ global.CompilerContext = {
 };
 
 function safeEval(templateSpec) {
-  return eval('(' + templateSpec + ')');
+  try {
+    return eval('(' + templateSpec + ')');
+  } catch (err) {
+    console.error(templateSpec);
+    throw err;
+  }
 }
diff --git a/spec/index.html b/spec/index.html
index f0a647134..fe306e7f0 100644
--- a/spec/index.html
+++ b/spec/index.html
@@ -37,9 +37,14 @@
       };
 
       function safeEval(templateSpec) {
-        var ret;
-        eval('ret = ' + templateSpec);
-        return ret;
+        try {
+          var ret;
+          eval('ret = ' + templateSpec);
+          return ret;
+        } catch (err) {
+          console.error(templateSpec);
+          throw err;
+        }
       }
     </script>
     <script src="/tmp/tests.js"></script>

From daeecf825cda40d5903095cb86e6b2872870c472 Mon Sep 17 00:00:00 2001
From: kpdecker <kpdecker@gmail.com>
Date: Sat, 4 Jan 2014 17:09:00 -0600
Subject: [PATCH 08/53] Track stringParams mode in local state vars

---
 lib/handlebars/compiler/compiler.js            |  5 +++--
 lib/handlebars/compiler/javascript-compiler.js | 13 +++++++------
 2 files changed, 10 insertions(+), 8 deletions(-)

diff --git a/lib/handlebars/compiler/compiler.js b/lib/handlebars/compiler/compiler.js
index 8389f8482..32d20487a 100644
--- a/lib/handlebars/compiler/compiler.js
+++ b/lib/handlebars/compiler/compiler.js
@@ -73,6 +73,7 @@ Compiler.prototype = {
     this.children = [];
     this.depths = {list: []};
     this.options = options;
+    this.stringParams = options.stringParams;
 
     // These changes will propagate to the other compiler components
     var knownHelpers = this.options.knownHelpers;
@@ -193,7 +194,7 @@ Compiler.prototype = {
       pair = pairs[i];
       val  = pair[1];
 
-      if (this.options.stringParams) {
+      if (this.stringParams) {
         if(val.depth) {
           this.addDepth(val.depth);
         }
@@ -386,7 +387,7 @@ Compiler.prototype = {
     while(i--) {
       param = params[i];
 
-      if(this.options.stringParams) {
+      if(this.stringParams) {
         if(param.depth) {
           this.addDepth(param.depth);
         }
diff --git a/lib/handlebars/compiler/javascript-compiler.js b/lib/handlebars/compiler/javascript-compiler.js
index 8cbb9e4f2..213672497 100644
--- a/lib/handlebars/compiler/javascript-compiler.js
+++ b/lib/handlebars/compiler/javascript-compiler.js
@@ -61,6 +61,7 @@ JavaScriptCompiler.prototype = {
   compile: function(environment, options, context, asObject) {
     this.environment = environment;
     this.options = options || {};
+    this.stringParams = this.options.stringParams;
 
     log('debug', this.environment.disassemble() + "\n\n");
 
@@ -418,7 +419,7 @@ JavaScriptCompiler.prototype = {
   emptyHash: function() {
     this.pushStackLiteral('{}');
 
-    if (this.options.stringParams) {
+    if (this.stringParams) {
       this.push('{}'); // hashContexts
       this.push('{}'); // hashTypes
     }
@@ -433,7 +434,7 @@ JavaScriptCompiler.prototype = {
     var hash = this.hash;
     this.hash = this.hashes.pop();
 
-    if (this.options.stringParams) {
+    if (this.stringParams) {
       this.push('{' + hash.contexts.join(',') + '}');
       this.push('{' + hash.types.join(',') + '}');
     }
@@ -596,7 +597,7 @@ JavaScriptCompiler.prototype = {
         context,
         type;
 
-    if (this.options.stringParams) {
+    if (this.stringParams) {
       type = this.popStack();
       context = this.popStack();
     }
@@ -842,7 +843,7 @@ JavaScriptCompiler.prototype = {
 
     options.push("hash:" + this.popStack());
 
-    if (this.options.stringParams) {
+    if (this.stringParams) {
       options.push("hashTypes:" + this.popStack());
       options.push("hashContexts:" + this.popStack());
     }
@@ -871,13 +872,13 @@ JavaScriptCompiler.prototype = {
       param = this.popStack();
       params.push(param);
 
-      if(this.options.stringParams) {
+      if(this.stringParams) {
         types.push(this.popStack());
         contexts.push(this.popStack());
       }
     }
 
-    if (this.options.stringParams) {
+    if (this.stringParams) {
       options.push("contexts:[" + contexts.join(",") + "]");
       options.push("types:[" + types.join(",") + "]");
     }

From c73aceb3d3415e9847fa0680f42bf80d5a652bac Mon Sep 17 00:00:00 2001
From: kpdecker <kpdecker@gmail.com>
Date: Mon, 6 Jan 2014 14:41:23 -0600
Subject: [PATCH 09/53] Refactor out pushParams method

Simplifies hash and param push logic
---
 lib/handlebars/compiler/compiler.js | 53 +++++++++++------------------
 1 file changed, 19 insertions(+), 34 deletions(-)

diff --git a/lib/handlebars/compiler/compiler.js b/lib/handlebars/compiler/compiler.js
index 32d20487a..b428c5fc5 100644
--- a/lib/handlebars/compiler/compiler.js
+++ b/lib/handlebars/compiler/compiler.js
@@ -186,30 +186,14 @@ Compiler.prototype = {
   },
 
   hash: function(hash) {
-    var pairs = hash.pairs, pair, val;
+    var pairs = hash.pairs, pair;
 
     this.opcode('pushHash');
 
     for(var i=0, l=pairs.length; i<l; i++) {
       pair = pairs[i];
-      val  = pair[1];
-
-      if (this.stringParams) {
-        if(val.depth) {
-          this.addDepth(val.depth);
-        }
-        this.opcode('getContext', val.depth || 0);
-        this.opcode('pushStringParam', val.stringModeValue, val.type);
-
-        if (val.type === 'sexpr') {
-          // Subexpressions get evaluated and passed in
-          // in string params mode.
-          this.sexpr(val);
-        }
-      } else {
-        this.accept(val);
-      }
 
+      this.pushParam(pair[1]);
       this.opcode('assignToHash', pair[0]);
     }
     this.opcode('popHash');
@@ -382,27 +366,28 @@ Compiler.prototype = {
   },
 
   pushParams: function(params) {
-    var i = params.length, param;
+    var i = params.length;
 
     while(i--) {
-      param = params[i];
-
-      if(this.stringParams) {
-        if(param.depth) {
-          this.addDepth(param.depth);
-        }
+      this.pushParam(params[i]);
+    }
+  },
 
-        this.opcode('getContext', param.depth || 0);
-        this.opcode('pushStringParam', param.stringModeValue, param.type);
+  pushParam: function(val) {
+    if (this.stringParams) {
+      if(val.depth) {
+        this.addDepth(val.depth);
+      }
+      this.opcode('getContext', val.depth || 0);
+      this.opcode('pushStringParam', val.stringModeValue, val.type);
 
-        if (param.type === 'sexpr') {
-          // Subexpressions get evaluated and passed in
-          // in string params mode.
-          this.sexpr(param);
-        }
-      } else {
-        this[param.type](param);
+      if (val.type === 'sexpr') {
+        // Subexpressions get evaluated and passed in
+        // in string params mode.
+        this.sexpr(val);
       }
+    } else {
+      this.accept(val);
     }
   },
 

From 0f328f375e9682642676d6c58d1cf307a63155f0 Mon Sep 17 00:00:00 2001
From: kpdecker <kpdecker@gmail.com>
Date: Sat, 4 Jan 2014 11:05:57 -0600
Subject: [PATCH 10/53] Include name option for all helper calls

All helper calls will have access to `options.name` which is the first id value of the mustache operation.

As part of this the helperMissing call has been simplified to remove the indexed name in order to optimize the call. This is a breaking change.

Fixes #634
---
 lib/handlebars/base.js                        |  8 ++--
 lib/handlebars/compiler/compiler.js           |  2 +-
 .../compiler/javascript-compiler.js           | 26 +++++------
 spec/helpers.js                               | 43 +++++++++++++++++--
 4 files changed, 57 insertions(+), 22 deletions(-)

diff --git a/lib/handlebars/base.js b/lib/handlebars/base.js
index 00c8bf1ff..059b1c3fb 100644
--- a/lib/handlebars/base.js
+++ b/lib/handlebars/base.js
@@ -49,11 +49,13 @@ HandlebarsEnvironment.prototype = {
 };
 
 function registerDefaultHelpers(instance) {
-  instance.registerHelper('helperMissing', function(arg) {
-    if(arguments.length === 2) {
+  instance.registerHelper('helperMissing', function(/* [args, ]options */) {
+    if(arguments.length === 1) {
+      // A missing field in a {{foo}} constuct.
       return undefined;
     } else {
-      throw new Exception("Missing helper: '" + arg + "'");
+      // Someone is actually trying to call something, blow up.
+      throw new Exception("Missing helper: '" + arguments[arguments.length-1].name + "'");
     }
   });
 
diff --git a/lib/handlebars/compiler/compiler.js b/lib/handlebars/compiler/compiler.js
index b428c5fc5..3c4a9b790 100644
--- a/lib/handlebars/compiler/compiler.js
+++ b/lib/handlebars/compiler/compiler.js
@@ -170,7 +170,7 @@ Compiler.prototype = {
       this.opcode('pushProgram', program);
       this.opcode('pushProgram', inverse);
       this.opcode('emptyHash');
-      this.opcode('blockValue');
+      this.opcode('blockValue', sexpr.id.original);
     } else {
       this.ambiguousSexpr(sexpr, program, inverse);
 
diff --git a/lib/handlebars/compiler/javascript-compiler.js b/lib/handlebars/compiler/javascript-compiler.js
index 213672497..809322fcf 100644
--- a/lib/handlebars/compiler/javascript-compiler.js
+++ b/lib/handlebars/compiler/javascript-compiler.js
@@ -224,11 +224,11 @@ JavaScriptCompiler.prototype = {
   // `{{#this.foo}}...{{/this.foo}}`, resolve the value of `foo`, and
   // replace it on the stack with the result of properly
   // invoking blockHelperMissing.
-  blockValue: function() {
+  blockValue: function(name) {
     this.context.aliases.blockHelperMissing = 'helpers.blockHelperMissing';
 
     var params = ["depth0"];
-    this.setupParams(0, params);
+    this.setupParams(name, 0, params);
 
     this.replaceStack(function(current) {
       params.splice(1, 0, current);
@@ -247,7 +247,7 @@ JavaScriptCompiler.prototype = {
 
     // We're being a bit cheeky and reusing the options value from the prior exec
     var params = ["depth0"];
-    this.setupParams(0, params, true);
+    this.setupParams('', 0, params, true);
 
     this.flushInline();
 
@@ -504,20 +504,15 @@ JavaScriptCompiler.prototype = {
     this.context.aliases.helperMissing = 'helpers.helperMissing';
     this.useRegister('helper');
 
-    var helper = this.lastHelper = this.setupHelper(paramSize, name, true);
+    var helper = this.lastHelper = this.setupHelper(paramSize, name);
     var nonHelper = this.nameLookup('depth' + this.lastContext, name, 'context');
 
-    var lookup = 'helper = ' + helper.name + ' || ' + nonHelper;
+    var lookup = 'helper = ' + helper.name + ' || ' + nonHelper + ' || helperMissing';
     if (helper.paramsInit) {
       lookup += ',' + helper.paramsInit;
     }
 
-    this.push(
-      '('
-        + lookup
-        + ',helper '
-          + '? helper.call(' + helper.callParams + ') '
-          + ': helperMissing.call(' + helper.helperMissingParams + '))');
+    this.push('(' + lookup + ',helper.call(' + helper.callParams + '))');
 
     // Always flush subexpressions. This is both to prevent the compounding size issue that
     // occurs when the code has to be duplicated for inlining and also to prevent errors
@@ -826,7 +821,7 @@ JavaScriptCompiler.prototype = {
 
   setupHelper: function(paramSize, name, missingParams) {
     var params = [],
-        paramsInit = this.setupParams(paramSize, params, missingParams);
+        paramsInit = this.setupParams(name, paramSize, params, missingParams);
     var foundHelper = this.nameLookup('helpers', name, 'helper');
 
     return {
@@ -838,9 +833,10 @@ JavaScriptCompiler.prototype = {
     };
   },
 
-  setupOptions: function(paramSize, params) {
+  setupOptions: function(helper, paramSize, params) {
     var options = [], contexts = [], types = [], param, inverse, program;
 
+    options.push("name:" + this.quotedString(helper));
     options.push("hash:" + this.popStack());
 
     if (this.stringParams) {
@@ -892,8 +888,8 @@ JavaScriptCompiler.prototype = {
 
   // the params and contexts arguments are passed in arrays
   // to fill in
-  setupParams: function(paramSize, params, useRegister) {
-    var options = '{' + this.setupOptions(paramSize, params).join(',') + '}';
+  setupParams: function(helperName, paramSize, params, useRegister) {
+    var options = '{' + this.setupOptions(helperName, paramSize, params).join(',') + '}';
 
     if (useRegister) {
       this.useRegister('options');
diff --git a/spec/helpers.js b/spec/helpers.js
index b0eb91e65..180fc9809 100644
--- a/spec/helpers.js
+++ b/spec/helpers.js
@@ -362,9 +362,9 @@ describe('helpers', function() {
       var context = { hello: "Hello", world: "world" };
 
       var helpers = {
-        helperMissing: function(helper, context) {
-          if(helper === "link_to") {
-            return new Handlebars.SafeString("<a>" + context + "</a>");
+        helperMissing: function(mesg, options) {
+          if(options.name === "link_to") {
+            return new Handlebars.SafeString("<a>" + mesg + "</a>");
           }
         }
       };
@@ -436,6 +436,43 @@ describe('helpers', function() {
     });
   });
 
+  describe('name field', function() {
+    var context = {};
+    var helpers = {
+      blockHelperMissing: function() {
+        return 'missing: ' + arguments[arguments.length-1].name;
+      },
+      helper: function() {
+        return 'ran: ' + arguments[arguments.length-1].name;
+      }
+    };
+
+    it('should include in ambiguous mustache calls', function() {
+      shouldCompileTo('{{helper}}', [context, helpers], 'ran: helper');
+    });
+    it('should include in helper mustache calls', function() {
+      shouldCompileTo('{{helper 1}}', [context, helpers], 'ran: helper');
+    });
+    it('should include in ambiguous block calls', function() {
+      shouldCompileTo('{{#helper}}{{/helper}}', [context, helpers], 'ran: helper');
+    });
+    it('should include in simple block calls', function() {
+      shouldCompileTo('{{#./helper}}{{/./helper}}', [context, helpers], 'missing: ./helper');
+    });
+    it('should include in helper block calls', function() {
+      shouldCompileTo('{{#helper 1}}{{/helper}}', [context, helpers], 'ran: helper');
+    });
+    it('should include in known helper calls', function() {
+      var template = CompilerContext.compile("{{helper}}", {knownHelpers: {'helper': true}, knownHelpersOnly: true});
+
+      equal(template({}, {helpers: helpers}), 'ran: helper');
+    });
+
+    it('should include full id', function() {
+      shouldCompileTo('{{#foo.helper}}{{/foo.helper}}', [{foo: {}}, helpers], 'missing: foo.helper');
+    });
+  });
+
   describe('name conflicts', function() {
     it("helpers take precedence over same-named context properties", function() {
       var template = CompilerContext.compile("{{goodbye}} {{cruel world}}");

From d385e2cab64d169f912348f4f4281d64ae6b7b2f Mon Sep 17 00:00:00 2001
From: kpdecker <kpdecker@gmail.com>
Date: Sat, 4 Jan 2014 17:02:37 -0600
Subject: [PATCH 11/53] Cleanup now unused code in jscompiler

---
 lib/handlebars/compiler/javascript-compiler.js | 9 ++++-----
 1 file changed, 4 insertions(+), 5 deletions(-)

diff --git a/lib/handlebars/compiler/javascript-compiler.js b/lib/handlebars/compiler/javascript-compiler.js
index 809322fcf..d11275d1d 100644
--- a/lib/handlebars/compiler/javascript-compiler.js
+++ b/lib/handlebars/compiler/javascript-compiler.js
@@ -504,7 +504,7 @@ JavaScriptCompiler.prototype = {
     this.context.aliases.helperMissing = 'helpers.helperMissing';
     this.useRegister('helper');
 
-    var helper = this.lastHelper = this.setupHelper(paramSize, name);
+    var helper = this.setupHelper(paramSize, name);
     var nonHelper = this.nameLookup('depth' + this.lastContext, name, 'context');
 
     var lookup = 'helper = ' + helper.name + ' || ' + nonHelper + ' || helperMissing';
@@ -819,17 +819,16 @@ JavaScriptCompiler.prototype = {
       .replace(/\u2029/g, '\\u2029') + '"';
   },
 
-  setupHelper: function(paramSize, name, missingParams) {
+  setupHelper: function(paramSize, name, blockHelper) {
     var params = [],
-        paramsInit = this.setupParams(name, paramSize, params, missingParams);
+        paramsInit = this.setupParams(name, paramSize, params, blockHelper);
     var foundHelper = this.nameLookup('helpers', name, 'helper');
 
     return {
       params: params,
       paramsInit: paramsInit,
       name: foundHelper,
-      callParams: ["depth0"].concat(params).join(", "),
-      helperMissingParams: missingParams && ["depth0", this.quotedString(name)].concat(params).join(", ")
+      callParams: ["depth0"].concat(params).join(", ")
     };
   },
 

From 76b47e339311516e84afdc80f170dec02aa91fda Mon Sep 17 00:00:00 2001
From: Nathaniel Bibler <git@nathanielbibler.com>
Date: Wed, 8 Jan 2014 22:05:16 -0500
Subject: [PATCH 12/53] Add MIT license identification to the Ruby gemspec

---
 components/handlebars-source.gemspec | 1 +
 1 file changed, 1 insertion(+)

diff --git a/components/handlebars-source.gemspec b/components/handlebars-source.gemspec
index 4313e9581..9123fddcd 100644
--- a/components/handlebars-source.gemspec
+++ b/components/handlebars-source.gemspec
@@ -12,6 +12,7 @@ Gem::Specification.new do |gem|
   gem.summary       = %q{Handlebars.js source code wrapper}
   gem.homepage      = "https://github.com/wycats/handlebars.js/"
   gem.version       = package["version"]
+  gem.license       = "MIT"
 
   gem.files = [
     'handlebars.js',

From 91aa96da7ddae172ee5d4629e952408989e3d348 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Scott=20Gonz=C3=A1lez?= <scott.gonzalez@gmail.com>
Date: Tue, 14 Jan 2014 15:35:27 -0500
Subject: [PATCH 13/53] README: Use with helper instead of relying on
 blockHelperMissing

---
 README.markdown | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/README.markdown b/README.markdown
index 63edee879..22dc474e1 100644
--- a/README.markdown
+++ b/README.markdown
@@ -132,7 +132,7 @@ into the person object you could still display the company's name with
 an expression like `{{../company.name}}`, so:
 
 ```
-{{#person}}{{name}} - {{../company.name}}{{/person}}
+{{#with person}}{{name}} - {{../company.name}}{{/person}}
 ```
 
 would render:

From fe9e93fc4dab4ba41e2aa89cf2771ebffe551f5e Mon Sep 17 00:00:00 2001
From: kpdecker <kpdecker@gmail.com>
Date: Wed, 15 Jan 2014 04:47:11 -0600
Subject: [PATCH 14/53] Attempt manual install of git in travis

---
 .travis.yml | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/.travis.yml b/.travis.yml
index 902b7412c..daccd0b5e 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -20,6 +20,8 @@ env:
 matrix:
   include:
     - node_js: "0.10"
+      before_install:
+        - sudo apt-get install git
       env:
         - PUBLISH=true
         - secure: pLTzghtVll9yGKJI0AaB0uI8GypfWxLTaIB0ZL8//yN3nAEIKMhf/RRilYTsn/rKj2NUa7vt2edYILi3lttOUlCBOwTc9amiRms1W8Lwr/3IdWPeBLvLuH1zNJRm2lBAwU4LBSqaOwhGaxOQr6KHTnWudhNhgOucxpZfvfI/dFw=

From f650b0d69ec70f82fc58022a530b95b0e4371f1d Mon Sep 17 00:00:00 2001
From: kpdecker <kpdecker@gmail.com>
Date: Wed, 15 Jan 2014 09:41:53 -0600
Subject: [PATCH 15/53] Expose the initial context via @root

---
 lib/handlebars/compiler/javascript-compiler.js |  2 +-
 lib/handlebars/runtime.js                      |  9 ++++++++-
 spec/data.js                                   | 15 +++++++++++++++
 spec/expected/empty.amd.js                     |  2 +-
 4 files changed, 25 insertions(+), 3 deletions(-)

diff --git a/lib/handlebars/compiler/javascript-compiler.js b/lib/handlebars/compiler/javascript-compiler.js
index 213672497..d6baf13c5 100644
--- a/lib/handlebars/compiler/javascript-compiler.js
+++ b/lib/handlebars/compiler/javascript-compiler.js
@@ -121,7 +121,7 @@ JavaScriptCompiler.prototype = {
 
       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 || {};"; }
+      if (this.options.data) { copies = copies + " data = this.initData(depth0, data);"; }
       out.push(copies);
     } else {
       out.push('');
diff --git a/lib/handlebars/runtime.js b/lib/handlebars/runtime.js
index 5ae8d8a78..d31e5f4fe 100644
--- a/lib/handlebars/runtime.js
+++ b/lib/handlebars/runtime.js
@@ -1,6 +1,6 @@
 module Utils from "./utils";
 import Exception from "./exception";
-import { COMPILER_REVISION, REVISION_CHANGES } from "./base";
+import { COMPILER_REVISION, REVISION_CHANGES, createFrame } from "./base";
 
 export function checkRevision(compilerInfo) {
   var compilerRevision = compilerInfo && compilerInfo[0] || 1,
@@ -56,6 +56,13 @@ export function template(templateSpec, env) {
       }
       return programWrapper;
     },
+    initData: function(context, data) {
+      data = data ? createFrame(data) : {};
+      if (!('root' in data)) {
+        data.root = context;
+      }
+      return data;
+    },
     merge: function(param, common) {
       var ret = param || common;
 
diff --git a/spec/data.js b/spec/data.js
index d84eae729..eab80ef36 100644
--- a/spec/data.js
+++ b/spec/data.js
@@ -236,4 +236,19 @@ describe('data', function() {
     equals("sad world?", result, "Overriden data output by helper");
   });
 
+  describe('@root', function() {
+    it('the root context can be looked up via @root', function() {
+      var template = CompilerContext.compile('{{@root.foo}}');
+      var result = template({foo: 'hello'}, { data: {} });
+      equals('hello', result);
+
+      result = template({foo: 'hello'}, {});
+      equals('hello', result);
+    });
+    it('passed root values take priority', function() {
+      var template = CompilerContext.compile('{{@root.foo}}');
+      var result = template({}, { data: {root: {foo: 'hello'} } });
+      equals('hello', result);
+    });
+  });
 });
diff --git a/spec/expected/empty.amd.js b/spec/expected/empty.amd.js
index 657970cd0..ca80b8850 100644
--- a/spec/expected/empty.amd.js
+++ b/spec/expected/empty.amd.js
@@ -2,7 +2,7 @@ define(['handlebars.runtime'], function(Handlebars) {
   Handlebars = Handlebars["default"];  var template = Handlebars.template, templates = Handlebars.templates = Handlebars.templates || {};
 return templates['empty'] = template(function (Handlebars,depth0,helpers,partials,data) {
   this.compilerInfo = [4,'>= 1.0.0'];
-helpers = this.merge(helpers, Handlebars.helpers); data = data || {};
+helpers = this.merge(helpers, Handlebars.helpers); data = this.initData(depth0, data);
   var buffer = "";
 
 

From 13633e7896b9c104667102b3b5715fc1a6b0d613 Mon Sep 17 00:00:00 2001
From: Blake Embrey <hello@blakeembrey.com>
Date: Thu, 9 Jan 2014 12:45:26 +1000
Subject: [PATCH 16/53] Improve usefulness of extend util, properly use
 namespace property, update setup options to use a hash helper.

---
 lib/handlebars/base.js                        |  8 ++--
 .../compiler/javascript-compiler.js           | 45 ++++++++++++-------
 lib/handlebars/runtime.js                     |  5 +--
 lib/handlebars/utils.js                       | 12 +++--
 4 files changed, 41 insertions(+), 29 deletions(-)

diff --git a/lib/handlebars/base.js b/lib/handlebars/base.js
index 059b1c3fb..ff9736ca6 100644
--- a/lib/handlebars/base.js
+++ b/lib/handlebars/base.js
@@ -102,8 +102,8 @@ function registerDefaultHelpers(instance) {
       } else {
         for(var key in context) {
           if(context.hasOwnProperty(key)) {
-            if(data) { 
-              data.key = key; 
+            if(data) {
+              data.key = key;
               data.index = i;
               data.first = (i === 0);
             }
@@ -174,7 +174,5 @@ export var logger = {
 export function log(level, obj) { logger.log(level, obj); }
 
 export var createFrame = function(object) {
-  var obj = {};
-  Utils.extend(obj, object);
-  return obj;
+  return Utils.extend({}, object);
 };
diff --git a/lib/handlebars/compiler/javascript-compiler.js b/lib/handlebars/compiler/javascript-compiler.js
index abdc8053b..7898bd1f3 100644
--- a/lib/handlebars/compiler/javascript-compiler.js
+++ b/lib/handlebars/compiler/javascript-compiler.js
@@ -21,8 +21,7 @@ JavaScriptCompiler.prototype = {
       ret = parent + "[" + name + "]";
     } else if (JavaScriptCompiler.isValidJavaScriptVariableName(name)) {
       ret = parent + "." + name;
-    }
-    else {
+    } else {
       ret = parent + "['" + name + "']";
     }
 
@@ -168,7 +167,7 @@ JavaScriptCompiler.prototype = {
       this.pushSource("return buffer;");
     }
 
-    var params = this.isChild ? ["depth0", "data"] : ["Handlebars", "depth0", "helpers", "partials", "data"];
+    var params = this.isChild ? ["depth0", "data"] : [this.namespace, "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]);
@@ -819,6 +818,18 @@ JavaScriptCompiler.prototype = {
       .replace(/\u2029/g, '\\u2029') + '"';
   },
 
+  setupHash: function(obj) {
+    var pairs = [];
+
+    for (var key in obj) {
+      if (obj.hasOwnProperty(key)) {
+        pairs.push(this.quotedString(key) + ':' + obj[key]);
+      }
+    }
+
+    return '{' + pairs.join(',') + '}';
+  },
+
   setupHelper: function(paramSize, name, blockHelper) {
     var params = [],
         paramsInit = this.setupParams(name, paramSize, params, blockHelper);
@@ -833,14 +844,14 @@ JavaScriptCompiler.prototype = {
   },
 
   setupOptions: function(helper, paramSize, params) {
-    var options = [], contexts = [], types = [], param, inverse, program;
+    var options = {}, contexts = [], types = [], param, inverse, program;
 
-    options.push("name:" + this.quotedString(helper));
-    options.push("hash:" + this.popStack());
+    options.name = this.quotedString(helper);
+    options.hash = this.popStack();
 
     if (this.stringParams) {
-      options.push("hashTypes:" + this.popStack());
-      options.push("hashContexts:" + this.popStack());
+      options.hashTypes = this.popStack();
+      options.hashContexts = this.popStack();
     }
 
     inverse = this.popStack();
@@ -859,27 +870,27 @@ JavaScriptCompiler.prototype = {
         inverse = "self.noop";
       }
 
-      options.push("inverse:" + inverse);
-      options.push("fn:" + program);
+      options.fn = program;
+      options.inverse = inverse;
     }
 
-    for(var i=0; i<paramSize; i++) {
+    for (var i = 0; i < paramSize; i++) {
       param = this.popStack();
       params.push(param);
 
-      if(this.stringParams) {
+      if (this.stringParams) {
         types.push(this.popStack());
         contexts.push(this.popStack());
       }
     }
 
     if (this.stringParams) {
-      options.push("contexts:[" + contexts.join(",") + "]");
-      options.push("types:[" + types.join(",") + "]");
+      options.types = "[" + types.join(",") + "]";
+      options.contexts = "[" + contexts.join(",") + "]";
     }
 
-    if(this.options.data) {
-      options.push("data:data");
+    if (this.options.data) {
+      options.data = "data";
     }
 
     return options;
@@ -888,7 +899,7 @@ JavaScriptCompiler.prototype = {
   // the params and contexts arguments are passed in arrays
   // to fill in
   setupParams: function(helperName, paramSize, params, useRegister) {
-    var options = '{' + this.setupOptions(helperName, paramSize, params).join(',') + '}';
+    var options = this.setupHash(this.setupOptions(helperName, paramSize, params));
 
     if (useRegister) {
       this.useRegister('options');
diff --git a/lib/handlebars/runtime.js b/lib/handlebars/runtime.js
index d31e5f4fe..699499c54 100644
--- a/lib/handlebars/runtime.js
+++ b/lib/handlebars/runtime.js
@@ -67,10 +67,9 @@ export function template(templateSpec, env) {
       var ret = param || common;
 
       if (param && common && (param !== common)) {
-        ret = {};
-        Utils.extend(ret, common);
-        Utils.extend(ret, param);
+        ret = Utils.extend({}, common, param);
       }
+
       return ret;
     },
     programWithDepth: env.VM.programWithDepth,
diff --git a/lib/handlebars/utils.js b/lib/handlebars/utils.js
index ed2e1d8ff..63148e460 100644
--- a/lib/handlebars/utils.js
+++ b/lib/handlebars/utils.js
@@ -17,12 +17,16 @@ function escapeChar(chr) {
   return escape[chr] || "&amp;";
 }
 
-export function extend(obj, value) {
-  for(var key in value) {
-    if(Object.prototype.hasOwnProperty.call(value, key)) {
-      obj[key] = value[key];
+export function extend(obj /* , ...source */) {
+  for (var i = 1; i < arguments.length; i++) {
+    for (var key in arguments[i]) {
+      if (Object.prototype.hasOwnProperty.call(arguments[i], key)) {
+        obj[key] = arguments[i][key];
+      }
     }
   }
+
+  return obj;
 }
 
 export var toString = Object.prototype.toString;

From 1b0b44a5b78bb400313f72271a5a0e0380823a4b Mon Sep 17 00:00:00 2001
From: kpdecker <kpdecker@gmail.com>
Date: Fri, 17 Jan 2014 17:18:11 -0600
Subject: [PATCH 17/53] Remove apt git install

---
 .travis.yml | 2 --
 1 file changed, 2 deletions(-)

diff --git a/.travis.yml b/.travis.yml
index daccd0b5e..902b7412c 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -20,8 +20,6 @@ env:
 matrix:
   include:
     - node_js: "0.10"
-      before_install:
-        - sudo apt-get install git
       env:
         - PUBLISH=true
         - secure: pLTzghtVll9yGKJI0AaB0uI8GypfWxLTaIB0ZL8//yN3nAEIKMhf/RRilYTsn/rKj2NUa7vt2edYILi3lttOUlCBOwTc9amiRms1W8Lwr/3IdWPeBLvLuH1zNJRm2lBAwU4LBSqaOwhGaxOQr6KHTnWudhNhgOucxpZfvfI/dFw=

From 1b8c0e3c76d8595b49ad30c2a50a1bab47b5ec70 Mon Sep 17 00:00:00 2001
From: kpdecker <kpdecker@gmail.com>
Date: Fri, 17 Jan 2014 17:18:26 -0600
Subject: [PATCH 18/53] Use git describe to lookup tag

---
 tasks/util/git.js | 15 +++++++++++----
 1 file changed, 11 insertions(+), 4 deletions(-)

diff --git a/tasks/util/git.js b/tasks/util/git.js
index dc57c91b6..a6c9ec1dc 100644
--- a/tasks/util/git.js
+++ b/tasks/util/git.js
@@ -80,20 +80,27 @@ module.exports = {
     childProcess.exec('git tag -a --message=' + name + ' ' + name, {}, function(err, stdout, stderr) {
       if (err) {
         throw new Error('git.tag: ' + err.message);
-        throw err;
       }
 
       callback();
     });
   },
   tagName: function(callback) {
-    childProcess.exec('git tag -l --points-at HEAD', {}, function(err, stdout) {
+    childProcess.exec('git describe --tags', {}, function(err, stdout) {
       if (err) {
         throw new Error('git.tagName: ' + err.message);
       }
 
-      var tags = stdout.trim().split(/\n/),
-          versionTags = tags.filter(function(tag) { return /^v/.test(tag); });
+      var tags = stdout.trim().split(/\n/);
+      tags = tags.filter(function(info) {
+        info = info.split('-');
+        return info.length == 1;
+      });
+
+      var versionTags = tags.filter(function(info) {
+        return /^v/.test(info[0]);
+      });
+
       callback(undefined, versionTags[0] || tags[0]);
     });
   }

From f90981adf608f98551d527c3109771d8babbba9f Mon Sep 17 00:00:00 2001
From: kpdecker <kpdecker@gmail.com>
Date: Fri, 17 Jan 2014 16:31:59 -0600
Subject: [PATCH 19/53] Add partial hash parser support

---
 lib/handlebars/compiler/ast.js     | 3 ++-
 lib/handlebars/compiler/printer.js | 7 ++++++-
 spec/ast.js                        | 2 +-
 spec/parser.js                     | 8 ++++++++
 src/handlebars.yy                  | 3 ++-
 5 files changed, 19 insertions(+), 4 deletions(-)

diff --git a/lib/handlebars/compiler/ast.js b/lib/handlebars/compiler/ast.js
index dda682e7e..5538f4057 100644
--- a/lib/handlebars/compiler/ast.js
+++ b/lib/handlebars/compiler/ast.js
@@ -97,11 +97,12 @@ var AST = {
     // pass or at runtime.
   },
 
-  PartialNode: function(partialName, context, strip, locInfo) {
+  PartialNode: function(partialName, context, hash, strip, locInfo) {
     LocationInfo.call(this, locInfo);
     this.type         = "partial";
     this.partialName  = partialName;
     this.context      = context;
+    this.hash = hash;
     this.strip = strip;
   },
 
diff --git a/lib/handlebars/compiler/printer.js b/lib/handlebars/compiler/printer.js
index ad55c7d4a..b7b760fac 100644
--- a/lib/handlebars/compiler/printer.js
+++ b/lib/handlebars/compiler/printer.js
@@ -82,7 +82,12 @@ PrintVisitor.prototype.mustache = function(mustache) {
 
 PrintVisitor.prototype.partial = function(partial) {
   var content = this.accept(partial.partialName);
-  if(partial.context) { content = content + " " + this.accept(partial.context); }
+  if(partial.context) {
+    content += " " + this.accept(partial.context);
+  }
+  if (partial.hash) {
+    content += " " + this.accept(partial.hash);
+  }
   return this.pad("{{> " + content + " }}");
 };
 
diff --git a/spec/ast.js b/spec/ast.js
index 46f0131f7..430574e22 100644
--- a/spec/ast.js
+++ b/spec/ast.js
@@ -193,7 +193,7 @@ describe('ast', function() {
   describe("PartialNode", function(){
 
     it('stores location info', function(){
-      var pn = new handlebarsEnv.AST.PartialNode("so_partial", {}, {}, LOCATION_INFO);
+      var pn = new handlebarsEnv.AST.PartialNode("so_partial", {}, {}, {}, LOCATION_INFO);
       testLocationInfoStorage(pn);
     });
   });
diff --git a/spec/parser.js b/spec/parser.js
index 70c16359e..0f6ed312f 100644
--- a/spec/parser.js
+++ b/spec/parser.js
@@ -84,6 +84,14 @@ describe('parser', function() {
     equals(ast_for("{{> foo bar}}"), "{{> PARTIAL:foo ID:bar }}\n");
   });
 
+  it('parses a partial with hash', function() {
+    equals(ast_for("{{> foo bar=bat}}"), "{{> PARTIAL:foo HASH{bar=ID:bat} }}\n");
+  });
+
+  it('parses a partial with context and hash', function() {
+    equals(ast_for("{{> foo bar bat=baz}}"), "{{> PARTIAL:foo ID:bar HASH{bat=ID:baz} }}\n");
+  });
+
   it('parses a partial with a complex name', function() {
     equals(ast_for("{{> shared/partial?.bar}}"), "{{> PARTIAL:shared/partial?.bar }}\n");
   });
diff --git a/src/handlebars.yy b/src/handlebars.yy
index 7bff5125a..bac1cc9cb 100644
--- a/src/handlebars.yy
+++ b/src/handlebars.yy
@@ -63,7 +63,8 @@ mustache
   ;
 
 partial
-  : OPEN_PARTIAL partialName path? CLOSE -> new yy.PartialNode($2, $3, stripFlags($1, $4), @$)
+  : OPEN_PARTIAL partialName param hash? CLOSE -> new yy.PartialNode($2, $3, $4, stripFlags($1, $5), @$)
+  | OPEN_PARTIAL partialName hash? CLOSE -> new yy.PartialNode($2, undefined, $3, stripFlags($1, $4), @$)
   ;
 
 simpleInverse

From 45ae86a248f3da7f24ee68da12454a2444ec398f Mon Sep 17 00:00:00 2001
From: kpdecker <kpdecker@gmail.com>
Date: Fri, 17 Jan 2014 16:49:44 -0600
Subject: [PATCH 20/53] Implement partial hash evaluation

---
 lib/handlebars/compiler/compiler.js            | 10 ++++++++--
 lib/handlebars/compiler/javascript-compiler.js |  2 +-
 lib/handlebars/runtime.js                      |  8 ++++++--
 spec/partials.js                               |  8 ++++++++
 4 files changed, 23 insertions(+), 5 deletions(-)

diff --git a/lib/handlebars/compiler/compiler.js b/lib/handlebars/compiler/compiler.js
index 3c4a9b790..b92289a7f 100644
--- a/lib/handlebars/compiler/compiler.js
+++ b/lib/handlebars/compiler/compiler.js
@@ -203,8 +203,14 @@ Compiler.prototype = {
     var partialName = partial.partialName;
     this.usePartial = true;
 
-    if(partial.context) {
-      this.ID(partial.context);
+    if (partial.hash) {
+      this.accept(partial.hash);
+    } else {
+      this.opcode('push', 'undefined');
+    }
+
+    if (partial.context) {
+      this.accept(partial.context);
     } else {
       this.opcode('push', 'depth0');
     }
diff --git a/lib/handlebars/compiler/javascript-compiler.js b/lib/handlebars/compiler/javascript-compiler.js
index 7898bd1f3..8ca0116b4 100644
--- a/lib/handlebars/compiler/javascript-compiler.js
+++ b/lib/handlebars/compiler/javascript-compiler.js
@@ -569,7 +569,7 @@ JavaScriptCompiler.prototype = {
   // 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"];
+    var params = [this.nameLookup('partials', name, 'partial'), "'" + name + "'", this.popStack(), this.popStack(), "helpers", "partials"];
 
     if (this.options.data) {
       params.push("data");
diff --git a/lib/handlebars/runtime.js b/lib/handlebars/runtime.js
index 699499c54..5df681f82 100644
--- a/lib/handlebars/runtime.js
+++ b/lib/handlebars/runtime.js
@@ -29,8 +29,12 @@ export function template(templateSpec, env) {
 
   // Note: Using env.VM references rather than local var references throughout this section to allow
   // for external users to override these as psuedo-supported APIs.
-  var invokePartialWrapper = function(partial, name, context, helpers, partials, data) {
-    var result = env.VM.invokePartial.apply(this, arguments);
+  var invokePartialWrapper = function(partial, name, context, hash, helpers, partials, data) {
+    if (hash) {
+      context = Utils.extend({}, context, hash);
+    }
+
+    var result = env.VM.invokePartial.call(this, partial, name, context, helpers, partials, data);
     if (result != null) { return result; }
 
     if (env.compile) {
diff --git a/spec/partials.js b/spec/partials.js
index bea72f5b7..68c0441f2 100644
--- a/spec/partials.js
+++ b/spec/partials.js
@@ -23,6 +23,14 @@ describe('partials', function() {
     shouldCompileToWithPartials(string, [hash, {}, {dude: partial}], true, "Dudes:  Empty");
   });
 
+  it("partials with parameters", function() {
+    var string = "Dudes: {{#dudes}}{{> dude otherDude=name}}{{/dudes}}";
+    var partial = "{{otherDude}} ({{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("partial in a partial", function() {
     var string = "Dudes: {{#dudes}}{{>dude}}{{/dudes}}";
     var dude = "{{name}} {{> url}} ";

From e290ec24f131f89ddf2c6aeb707a4884d41c3c6d Mon Sep 17 00:00:00 2001
From: kpdecker <kpdecker@gmail.com>
Date: Fri, 17 Jan 2014 17:11:28 -0600
Subject: [PATCH 21/53] Test more concrete behavior in partial hash test

---
 spec/partials.js | 8 ++++----
 1 file changed, 4 insertions(+), 4 deletions(-)

diff --git a/spec/partials.js b/spec/partials.js
index 68c0441f2..732436af0 100644
--- a/spec/partials.js
+++ b/spec/partials.js
@@ -24,10 +24,10 @@ describe('partials', function() {
   });
 
   it("partials with parameters", function() {
-    var string = "Dudes: {{#dudes}}{{> dude otherDude=name}}{{/dudes}}";
-    var partial = "{{otherDude}} ({{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) ",
+    var string = "Dudes: {{#dudes}}{{> dude others=..}}{{/dudes}}";
+    var partial = "{{others.foo}}{{name}} ({{url}}) ";
+    var hash = {foo: 'bar', dudes: [{name: "Yehuda", url: "http://yehuda"}, {name: "Alan", url: "http://alan"}]};
+    shouldCompileToWithPartials(string, [hash, {}, {dude: partial}], true, "Dudes: barYehuda (http://yehuda) barAlan (http://alan) ",
                     "Basic partials output based on current context.");
   });
 

From fe4880feaabb6107e39e9af6d4d0f68ff3736653 Mon Sep 17 00:00:00 2001
From: kpdecker <kpdecker@gmail.com>
Date: Fri, 17 Jan 2014 20:13:00 -0600
Subject: [PATCH 22/53] Allow implicit context iteration with each

Fixes #671
---
 lib/handlebars/base.js | 6 ++++++
 spec/builtins.js       | 5 +++++
 2 files changed, 11 insertions(+)

diff --git a/lib/handlebars/base.js b/lib/handlebars/base.js
index ff9736ca6..790228c1c 100644
--- a/lib/handlebars/base.js
+++ b/lib/handlebars/base.js
@@ -80,6 +80,12 @@ function registerDefaultHelpers(instance) {
   });
 
   instance.registerHelper('each', function(context, options) {
+    // Allow for {{#each}}
+    if (!options) {
+      options = context;
+      context = this;
+    }
+
     var fn = options.fn, inverse = options.inverse;
     var i = 0, ret = "", data;
 
diff --git a/spec/builtins.js b/spec/builtins.js
index 766541a77..6d9fa0ba4 100644
--- a/spec/builtins.js
+++ b/spec/builtins.js
@@ -181,6 +181,11 @@ describe('builtin helpers', function() {
       equal(result, 'a!b!c!', 'should output data');
     });
 
+    it("each on implicit context", function() {
+      var string   = "{{#each}}{{text}}! {{/each}}cruel world!";
+      var hash     = [{text: "goodbye"}, {text: "Goodbye"}, {text: "GOODBYE"}];
+      shouldCompileTo(string, [hash], "goodbye! Goodbye! GOODBYE! cruel world!");
+    });
   });
 
   it("#log", function() {

From 051618c024333404746795304f5858f3ef56a215 Mon Sep 17 00:00:00 2001
From: kpdecker <kpdecker@gmail.com>
Date: Fri, 17 Jan 2014 20:16:10 -0600
Subject: [PATCH 23/53] Always process explicitly passed files in CLI

Fixes #689
---
 bin/handlebars | 6 +++---
 1 file changed, 3 insertions(+), 3 deletions(-)

diff --git a/bin/handlebars b/bin/handlebars
index 247ddd540..d7c489b0d 100755
--- a/bin/handlebars
+++ b/bin/handlebars
@@ -139,7 +139,7 @@ if (!argv.simple) {
   output.push(argv.namespace);
   output.push(' || {};\n');
 }
-function processTemplate(template, root) {
+function processTemplate(template, root, explicit) {
   var path = template,
       stat = fs.statSync(path);
   if (stat.isDirectory()) {
@@ -150,7 +150,7 @@ function processTemplate(template, root) {
         processTemplate(path, root || template);
       }
     });
-  } else if (extension.test(path)) {
+  } else if (explicit || extension.test(path)) {
     var data = fs.readFileSync(path, 'utf8');
 
     if (argv.bom && data.indexOf('\uFEFF') === 0) {
@@ -191,7 +191,7 @@ function processTemplate(template, root) {
 }
 
 argv._.forEach(function(template) {
-  processTemplate(template, argv.root);
+  processTemplate(template, argv.root, true);
 });
 
 // Output the content

From d4cfe90959c5a585e5d87e31038eb2f0432f87a5 Mon Sep 17 00:00:00 2001
From: kpdecker <kpdecker@gmail.com>
Date: Fri, 17 Jan 2014 20:42:02 -0600
Subject: [PATCH 24/53] Allow decimal number values

Fixes #472
---
 lib/handlebars/compiler/ast.js      |  8 ++++----
 lib/handlebars/compiler/compiler.js |  4 ++--
 lib/handlebars/compiler/printer.js  |  4 ++--
 spec/ast.js                         |  4 ++--
 spec/helpers.js                     | 11 +++++++++++
 spec/parser.js                      |  8 ++++----
 spec/tokenizer.js                   | 20 ++++++++++++++------
 src/handlebars.l                    |  2 +-
 src/handlebars.yy                   |  4 ++--
 9 files changed, 42 insertions(+), 23 deletions(-)

diff --git a/lib/handlebars/compiler/ast.js b/lib/handlebars/compiler/ast.js
index 5538f4057..8fa649568 100644
--- a/lib/handlebars/compiler/ast.js
+++ b/lib/handlebars/compiler/ast.js
@@ -201,12 +201,12 @@ var AST = {
       this.stringModeValue = string;
   },
 
-  IntegerNode: function(integer, locInfo) {
+  NumberNode: function(number, locInfo) {
     LocationInfo.call(this, locInfo);
-    this.type = "INTEGER";
+    this.type = "NUMBER";
     this.original =
-      this.integer = integer;
-    this.stringModeValue = Number(integer);
+      this.number = number;
+    this.stringModeValue = Number(number);
   },
 
   BooleanNode: function(bool, locInfo) {
diff --git a/lib/handlebars/compiler/compiler.js b/lib/handlebars/compiler/compiler.js
index b92289a7f..83eca2408 100644
--- a/lib/handlebars/compiler/compiler.js
+++ b/lib/handlebars/compiler/compiler.js
@@ -321,8 +321,8 @@ Compiler.prototype = {
     this.opcode('pushString', string.string);
   },
 
-  INTEGER: function(integer) {
-    this.opcode('pushLiteral', integer.integer);
+  NUMBER: function(number) {
+    this.opcode('pushLiteral', number.number);
   },
 
   BOOLEAN: function(bool) {
diff --git a/lib/handlebars/compiler/printer.js b/lib/handlebars/compiler/printer.js
index b7b760fac..be66d73d3 100644
--- a/lib/handlebars/compiler/printer.js
+++ b/lib/handlebars/compiler/printer.js
@@ -108,8 +108,8 @@ PrintVisitor.prototype.STRING = function(string) {
   return '"' + string.string + '"';
 };
 
-PrintVisitor.prototype.INTEGER = function(integer) {
-  return "INTEGER{" + integer.integer + "}";
+PrintVisitor.prototype.NUMBER = function(number) {
+  return "NUMBER{" + number.number + "}";
 };
 
 PrintVisitor.prototype.BOOLEAN = function(bool) {
diff --git a/spec/ast.js b/spec/ast.js
index 430574e22..17a982c41 100644
--- a/spec/ast.js
+++ b/spec/ast.js
@@ -150,10 +150,10 @@ describe('ast', function() {
     });
   });
 
-  describe("IntegerNode", function(){
+  describe("NumberNode", function(){
 
     it('stores location info', function(){
-      var integer = new handlebarsEnv.AST.IntegerNode("6", LOCATION_INFO);
+      var integer = new handlebarsEnv.AST.NumberNode("6", LOCATION_INFO);
       testLocationInfoStorage(integer);
     });
   });
diff --git a/spec/helpers.js b/spec/helpers.js
index 180fc9809..904f56a7c 100644
--- a/spec/helpers.js
+++ b/spec/helpers.js
@@ -188,6 +188,17 @@ describe('helpers', function() {
       "found it! Goodbye cruel world!!");
   });
 
+  it("decimal number literals work", function() {
+    var string   = 'Message: {{hello -1.2 1.2}}';
+    var hash     = {};
+    var helpers  = {hello: function(times, times2) {
+      if(typeof times !== 'number') { times = "NaN"; }
+      if(typeof times2 !== 'number') { times2 = "NaN"; }
+      return "Hello " + times + " " + times2 + " times";
+    }};
+    shouldCompileTo(string, [hash, helpers], "Message: Hello -1.2 1.2 times", "template with a negative integer literal");
+  });
+
   it("negative number literals work", function() {
     var string   = 'Message: {{hello -12}}';
     var hash     = {};
diff --git a/spec/parser.js b/spec/parser.js
index 0f6ed312f..5b6dc8a4e 100644
--- a/spec/parser.js
+++ b/spec/parser.js
@@ -41,8 +41,8 @@ describe('parser', function() {
     equals(ast_for("{{foo bar \"baz\" }}"), '{{ ID:foo [ID:bar, "baz"] }}\n');
   });
 
-  it('parses mustaches with INTEGER parameters', function() {
-    equals(ast_for("{{foo 1}}"), "{{ ID:foo [INTEGER{1}] }}\n");
+  it('parses mustaches with NUMBER parameters', function() {
+    equals(ast_for("{{foo 1}}"), "{{ ID:foo [NUMBER{1}] }}\n");
   });
 
   it('parses mustaches with BOOLEAN parameters', function() {
@@ -56,7 +56,7 @@ describe('parser', function() {
 
   it('parses mustaches with hash arguments', function() {
     equals(ast_for("{{foo bar=baz}}"), "{{ ID:foo [] HASH{bar=ID:baz} }}\n");
-    equals(ast_for("{{foo bar=1}}"), "{{ ID:foo [] HASH{bar=INTEGER{1}} }}\n");
+    equals(ast_for("{{foo bar=1}}"), "{{ ID:foo [] HASH{bar=NUMBER{1}} }}\n");
     equals(ast_for("{{foo bar=true}}"), "{{ ID:foo [] HASH{bar=BOOLEAN{true}} }}\n");
     equals(ast_for("{{foo bar=false}}"), "{{ ID:foo [] HASH{bar=BOOLEAN{false}} }}\n");
     equals(ast_for("{{foo bar=@baz}}"), "{{ ID:foo [] HASH{bar=@ID:baz} }}\n");
@@ -67,7 +67,7 @@ describe('parser', function() {
     equals(ast_for("{{foo bat='bam'}}"), '{{ ID:foo [] HASH{bat="bam"} }}\n');
 
     equals(ast_for("{{foo omg bar=baz bat=\"bam\"}}"), '{{ ID:foo [ID:omg] HASH{bar=ID:baz, bat="bam"} }}\n');
-    equals(ast_for("{{foo omg bar=baz bat=\"bam\" baz=1}}"), '{{ ID:foo [ID:omg] HASH{bar=ID:baz, bat="bam", baz=INTEGER{1}} }}\n');
+    equals(ast_for("{{foo omg bar=baz bat=\"bam\" baz=1}}"), '{{ ID:foo [ID:omg] HASH{bar=ID:baz, bat="bam", baz=NUMBER{1}} }}\n');
     equals(ast_for("{{foo omg bar=baz bat=\"bam\" baz=true}}"), '{{ ID:foo [ID:omg] HASH{bar=ID:baz, bat="bam", baz=BOOLEAN{true}} }}\n');
     equals(ast_for("{{foo omg bar=baz bat=\"bam\" baz=false}}"), '{{ ID:foo [ID:omg] HASH{bar=ID:baz, bat="bam", baz=BOOLEAN{false}} }}\n');
   });
diff --git a/spec/tokenizer.js b/spec/tokenizer.js
index 841a5abc1..36a632b8a 100644
--- a/spec/tokenizer.js
+++ b/spec/tokenizer.js
@@ -295,12 +295,20 @@ describe('Tokenizer', function() {
 
   it('tokenizes numbers', function() {
     var result = tokenize('{{ foo 1 }}');
-    shouldMatchTokens(result, ['OPEN', 'ID', 'INTEGER', 'CLOSE']);
-    shouldBeToken(result[2], "INTEGER", "1");
+    shouldMatchTokens(result, ['OPEN', 'ID', 'NUMBER', 'CLOSE']);
+    shouldBeToken(result[2], "NUMBER", "1");
+
+    result = tokenize('{{ foo 1.1 }}');
+    shouldMatchTokens(result, ['OPEN', 'ID', 'NUMBER', 'CLOSE']);
+    shouldBeToken(result[2], "NUMBER", "1.1");
 
     result = tokenize('{{ foo -1 }}');
-    shouldMatchTokens(result, ['OPEN', 'ID', 'INTEGER', 'CLOSE']);
-    shouldBeToken(result[2], "INTEGER", "-1");
+    shouldMatchTokens(result, ['OPEN', 'ID', 'NUMBER', 'CLOSE']);
+    shouldBeToken(result[2], "NUMBER", "-1");
+
+    result = tokenize('{{ foo -1.1 }}');
+    shouldMatchTokens(result, ['OPEN', 'ID', 'NUMBER', 'CLOSE']);
+    shouldBeToken(result[2], "NUMBER", "-1.1");
   });
 
   it('tokenizes booleans', function() {
@@ -321,7 +329,7 @@ describe('Tokenizer', function() {
     shouldMatchTokens(result, ['OPEN', 'ID', 'ID', 'ID', 'EQUALS', 'ID', 'CLOSE']);
 
     result = tokenize("{{ foo bar baz=1 }}");
-    shouldMatchTokens(result, ['OPEN', 'ID', 'ID', 'ID', 'EQUALS', 'INTEGER', 'CLOSE']);
+    shouldMatchTokens(result, ['OPEN', 'ID', 'ID', 'ID', 'EQUALS', 'NUMBER', 'CLOSE']);
 
     result = tokenize("{{ foo bar baz=true }}");
     shouldMatchTokens(result, ['OPEN', 'ID', 'ID', 'ID', 'EQUALS', 'BOOLEAN', 'CLOSE']);
@@ -389,6 +397,6 @@ describe('Tokenizer', function() {
 
   it('tokenizes nested subexpressions: literals', function() {
     var result = tokenize("{{foo (bar (lol true) false) (baz 1) (blah 'b') (blorg \"c\")}}");
-    shouldMatchTokens(result, ['OPEN', 'ID', 'OPEN_SEXPR', 'ID', 'OPEN_SEXPR', 'ID', 'BOOLEAN', 'CLOSE_SEXPR', 'BOOLEAN', 'CLOSE_SEXPR', 'OPEN_SEXPR', 'ID', 'INTEGER', 'CLOSE_SEXPR', 'OPEN_SEXPR', 'ID', 'STRING', 'CLOSE_SEXPR', 'OPEN_SEXPR', 'ID', 'STRING', 'CLOSE_SEXPR', 'CLOSE']);
+    shouldMatchTokens(result, ['OPEN', 'ID', 'OPEN_SEXPR', 'ID', 'OPEN_SEXPR', 'ID', 'BOOLEAN', 'CLOSE_SEXPR', 'BOOLEAN', 'CLOSE_SEXPR', 'OPEN_SEXPR', 'ID', 'NUMBER', 'CLOSE_SEXPR', 'OPEN_SEXPR', 'ID', 'STRING', 'CLOSE_SEXPR', 'OPEN_SEXPR', 'ID', 'STRING', 'CLOSE_SEXPR', 'CLOSE']);
   });
 });
diff --git a/src/handlebars.l b/src/handlebars.l
index 996badb22..630840eb5 100644
--- a/src/handlebars.l
+++ b/src/handlebars.l
@@ -77,7 +77,7 @@ ID    [^\s!"#%-,\.\/;->@\[-\^`\{-~]+/{LOOKAHEAD}
 <mu>"@"                          return 'DATA';
 <mu>"true"/{LITERAL_LOOKAHEAD}   return 'BOOLEAN';
 <mu>"false"/{LITERAL_LOOKAHEAD}  return 'BOOLEAN';
-<mu>\-?[0-9]+/{LITERAL_LOOKAHEAD} return 'INTEGER';
+<mu>\-?[0-9]+(?:\.[0-9]+)?/{LITERAL_LOOKAHEAD} return 'NUMBER';
 
 <mu>{ID}                         return 'ID';
 
diff --git a/src/handlebars.yy b/src/handlebars.yy
index bac1cc9cb..40f68ce59 100644
--- a/src/handlebars.yy
+++ b/src/handlebars.yy
@@ -79,7 +79,7 @@ sexpr
 param
   : path -> $1
   | STRING -> new yy.StringNode($1, @$)
-  | INTEGER -> new yy.IntegerNode($1, @$)
+  | NUMBER -> new yy.NumberNode($1, @$)
   | BOOLEAN -> new yy.BooleanNode($1, @$)
   | dataName -> $1
   | OPEN_SEXPR sexpr CLOSE_SEXPR {$2.isHelper = true; $$ = $2;}
@@ -96,7 +96,7 @@ hashSegment
 partialName
   : path -> new yy.PartialNameNode($1, @$)
   | STRING -> new yy.PartialNameNode(new yy.StringNode($1, @$), @$)
-  | INTEGER -> new yy.PartialNameNode(new yy.IntegerNode($1, @$))
+  | NUMBER -> new yy.PartialNameNode(new yy.NumberNode($1, @$))
   ;
 
 dataName

From 33921beaa08649f251e9e8b77124cb8fd2c42b1c Mon Sep 17 00:00:00 2001
From: kpdecker <kpdecker@gmail.com>
Date: Fri, 17 Jan 2014 22:56:40 -0600
Subject: [PATCH 25/53] Fix missing parameters for pathed mustaches

Fixes #658
---
 lib/handlebars/compiler/ast.js                | 10 +++----
 lib/handlebars/compiler/compiler.js           |  4 ++-
 .../compiler/javascript-compiler.js           |  2 +-
 spec/basic.js                                 | 28 +++++++++++++++++++
 4 files changed, 37 insertions(+), 7 deletions(-)

diff --git a/lib/handlebars/compiler/ast.js b/lib/handlebars/compiler/ast.js
index 8fa649568..678988394 100644
--- a/lib/handlebars/compiler/ast.js
+++ b/lib/handlebars/compiler/ast.js
@@ -83,14 +83,14 @@ var AST = {
     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);
+    this.isHelper = params.length || hash;
+
+    // a mustache is an eligible helper if:
+    // * its id is simple (a single part, not `this` or `..`)
+    this.eligibleHelper = this.isHelper || id.isSimple;
 
     // if a mustache is an eligible helper but not a definite
     // helper, it is ambiguous, and will be resolved in a later
diff --git a/lib/handlebars/compiler/compiler.js b/lib/handlebars/compiler/compiler.js
index 83eca2408..3f7aa3bff 100644
--- a/lib/handlebars/compiler/compiler.js
+++ b/lib/handlebars/compiler/compiler.js
@@ -265,13 +265,15 @@ Compiler.prototype = {
 
   helperSexpr: function(sexpr, program, inverse) {
     var params = this.setupFullMustacheParams(sexpr, program, inverse),
-        name = sexpr.id.parts[0];
+        id = sexpr.id,
+        name = id.parts[0];
 
     if (this.options.knownHelpers[name]) {
       this.opcode('invokeKnownHelper', params.length, name);
     } else if (this.options.knownHelpersOnly) {
       throw new Exception("You specified knownHelpersOnly, but used the unknown helper " + name, sexpr);
     } else {
+      this.ID(id);
       this.opcode('invokeHelper', params.length, name, sexpr.isRoot);
     }
   },
diff --git a/lib/handlebars/compiler/javascript-compiler.js b/lib/handlebars/compiler/javascript-compiler.js
index 8ca0116b4..96a8a0eae 100644
--- a/lib/handlebars/compiler/javascript-compiler.js
+++ b/lib/handlebars/compiler/javascript-compiler.js
@@ -503,8 +503,8 @@ JavaScriptCompiler.prototype = {
     this.context.aliases.helperMissing = 'helpers.helperMissing';
     this.useRegister('helper');
 
+    var nonHelper = this.popStack();
     var helper = this.setupHelper(paramSize, name);
-    var nonHelper = this.nameLookup('depth' + this.lastContext, name, 'context');
 
     var lookup = 'helper = ' + helper.name + ' || ' + nonHelper + ' || helperMissing';
     if (helper.paramsInit) {
diff --git a/spec/basic.js b/spec/basic.js
index 245022c61..8aa54b98c 100644
--- a/spec/basic.js
+++ b/spec/basic.js
@@ -97,6 +97,18 @@ describe("basic context", function() {
           frank: "Frank"},
         "Frank", "functions are called with context arguments");
   });
+  it("pathed functions with context argument", function() {
+    shouldCompileTo("{{bar.awesome frank}}",
+        {bar: {awesome: function(context) { return context; }},
+          frank: "Frank"},
+        "Frank", "functions are called with context arguments");
+  });
+  it("depthed functions with context argument", function() {
+    shouldCompileTo("{{#with frank}}{{../awesome .}}{{/with}}",
+        {awesome: function(context) { return context; },
+          frank: "Frank"},
+        "Frank", "functions are called with context arguments");
+  });
 
   it("block functions with context argument", function() {
     shouldCompileTo("{{#awesome 1}}inner {{.}}{{/awesome}}",
@@ -104,11 +116,27 @@ describe("basic context", function() {
         "inner 1", "block functions are called with context and options");
   });
 
+  it("depthed block functions with context argument", function() {
+    shouldCompileTo("{{#with value}}{{#../awesome 1}}inner {{.}}{{/../awesome}}{{/with}}",
+        {value: true, 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("pathed block functions without context argument", function() {
+    shouldCompileTo("{{#foo.awesome}}inner{{/foo.awesome}}",
+        {foo: {awesome: function(options) { return this; }}},
+        "inner", "block functions are called with options");
+  });
+  it("depthed block functions without context argument", function() {
+    shouldCompileTo("{{#with value}}{{#../awesome}}inner{{/../awesome}}{{/with}}",
+        {value: true, awesome: function(options) { return this; }},
+        "inner", "block functions are called with options");
+  });
 
 
   it("paths with hyphens", function() {

From a14daca1c6bff532146cd4bae66fe12c94c44117 Mon Sep 17 00:00:00 2001
From: kpdecker <kpdecker@gmail.com>
Date: Fri, 17 Jan 2014 23:11:13 -0600
Subject: [PATCH 26/53] Simplify sauce test environments

---
 Gruntfile.js | 3 ---
 1 file changed, 3 deletions(-)

diff --git a/Gruntfile.js b/Gruntfile.js
index 3468e4d38..ae4ff7b1f 100644
--- a/Gruntfile.js
+++ b/Gruntfile.js
@@ -141,14 +141,11 @@ module.exports = function(grunt) {
             {browserName: 'firefox', version: '3.6'},
             {browserName: 'safari', version: 7, platform: 'OS X 10.9'},
             {browserName: 'safari', version: 6, platform: 'OS X 10.8'},
-            {browserName: 'safari', version: 5},
             {browserName: 'opera', version: 12},
             {browserName: 'opera', version: 11},
             {browserName: 'internet explorer', version: 11, platform: 'Windows 8.1'},
             {browserName: 'internet explorer', version: 10, platform: 'Windows 8'},
             {browserName: 'internet explorer', version: 9, platform: 'Windows 7'},
-            {browserName: 'internet explorer', version: 8, platform: 'XP'},
-            {browserName: 'internet explorer', version: 7, platform: 'XP'},
             {browserName: 'internet explorer', version: 6, platform: 'XP'}
           ]
         }

From 103e5f8409ad624227ff99b8e5fbbc276cadee7b Mon Sep 17 00:00:00 2001
From: kpdecker <kpdecker@gmail.com>
Date: Fri, 17 Jan 2014 23:11:35 -0600
Subject: [PATCH 27/53] Only lint 1 set of release content

---
 Gruntfile.js | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/Gruntfile.js b/Gruntfile.js
index ae4ff7b1f..29611c7f5 100644
--- a/Gruntfile.js
+++ b/Gruntfile.js
@@ -10,7 +10,7 @@ module.exports = function(grunt) {
         jshintrc: '.jshintrc'
       },
       files: [
-        'dist/**/!(*.min|parser).js'
+        'dist/cjs/**/!(*.min|parser).js'
       ]
     },
 

From 9df919083d4e25512fb6dc6fe1a050e63ad12c80 Mon Sep 17 00:00:00 2001
From: kpdecker <kpdecker@gmail.com>
Date: Thu, 16 Jan 2014 13:59:02 -0600
Subject: [PATCH 28/53] Create track ids test stub

---
 spec/track-ids.js | 6 ++++++
 1 file changed, 6 insertions(+)
 create mode 100644 spec/track-ids.js

diff --git a/spec/track-ids.js b/spec/track-ids.js
new file mode 100644
index 000000000..9d9847ced
--- /dev/null
+++ b/spec/track-ids.js
@@ -0,0 +1,6 @@
+describe('track ids', function() {
+  it('should include argument ids');
+  it('should include hash ids');
+  it('should note ../ references');
+  it('should update the path when using built helpers');
+});

From ace2896ec876104f5bd220744124e7370fe6b9a2 Mon Sep 17 00:00:00 2001
From: kpdecker <kpdecker@gmail.com>
Date: Fri, 17 Jan 2014 19:14:36 -0600
Subject: [PATCH 29/53] Add trackIds compiler flag

Allows helpers that care about where a particular field came from derive this data while maintaining backward compatibility with existing helpers.
---
 lib/handlebars/compiler/ast.js                |   6 +-
 lib/handlebars/compiler/compiler.js           |   4 +
 .../compiler/javascript-compiler.js           |  39 ++++++-
 spec/track-ids.js                             | 110 +++++++++++++++++-
 4 files changed, 151 insertions(+), 8 deletions(-)

diff --git a/lib/handlebars/compiler/ast.js b/lib/handlebars/compiler/ast.js
index 678988394..5a5f514db 100644
--- a/lib/handlebars/compiler/ast.js
+++ b/lib/handlebars/compiler/ast.js
@@ -149,7 +149,8 @@ var AST = {
 
     var original = "",
         dig = [],
-        depth = 0;
+        depth = 0,
+        depthString = '';
 
     for(var i=0,l=parts.length; i<l; i++) {
       var part = parts[i].part;
@@ -160,6 +161,7 @@ var AST = {
           throw new Exception("Invalid path: " + original, this);
         } else if (part === "..") {
           depth++;
+          depthString += '../';
         } else {
           this.isScoped = true;
         }
@@ -172,6 +174,7 @@ var AST = {
     this.parts    = dig;
     this.string   = dig.join('.');
     this.depth    = depth;
+    this.idName   = depthString + this.string;
 
     // an ID is simple if it only has one part, and that part is not
     // `..` or `this`.
@@ -191,6 +194,7 @@ var AST = {
     this.type = "DATA";
     this.id = id;
     this.stringModeValue = id.stringModeValue;
+    this.idName = '@' + id.stringModeValue;
   },
 
   StringNode: function(string, locInfo) {
diff --git a/lib/handlebars/compiler/compiler.js b/lib/handlebars/compiler/compiler.js
index 3f7aa3bff..cfcc70ac5 100644
--- a/lib/handlebars/compiler/compiler.js
+++ b/lib/handlebars/compiler/compiler.js
@@ -74,6 +74,7 @@ Compiler.prototype = {
     this.depths = {list: []};
     this.options = options;
     this.stringParams = options.stringParams;
+    this.trackIds = options.trackIds;
 
     // These changes will propagate to the other compiler components
     var knownHelpers = this.options.knownHelpers;
@@ -395,6 +396,9 @@ Compiler.prototype = {
         this.sexpr(val);
       }
     } else {
+      if (this.trackIds) {
+        this.opcode('pushId', val.type, val.idName || val.stringModeValue);
+      }
       this.accept(val);
     }
   },
diff --git a/lib/handlebars/compiler/javascript-compiler.js b/lib/handlebars/compiler/javascript-compiler.js
index 96a8a0eae..ff6bc476c 100644
--- a/lib/handlebars/compiler/javascript-compiler.js
+++ b/lib/handlebars/compiler/javascript-compiler.js
@@ -61,6 +61,7 @@ JavaScriptCompiler.prototype = {
     this.environment = environment;
     this.options = options || {};
     this.stringParams = this.options.stringParams;
+    this.trackIds = this.options.trackIds;
 
     log('debug', this.environment.disassemble() + "\n\n");
 
@@ -418,6 +419,9 @@ JavaScriptCompiler.prototype = {
   emptyHash: function() {
     this.pushStackLiteral('{}');
 
+    if (this.trackIds) {
+      this.push('{}'); // hashIds
+    }
     if (this.stringParams) {
       this.push('{}'); // hashContexts
       this.push('{}'); // hashTypes
@@ -427,12 +431,15 @@ JavaScriptCompiler.prototype = {
     if (this.hash) {
       this.hashes.push(this.hash);
     }
-    this.hash = {values: [], types: [], contexts: []};
+    this.hash = {values: [], types: [], contexts: [], ids: []};
   },
   popHash: function() {
     var hash = this.hash;
     this.hash = this.hashes.pop();
 
+    if (this.trackIds) {
+      this.push('{' + hash.ids.join(',') + '}');
+    }
     if (this.stringParams) {
       this.push('{' + hash.contexts.join(',') + '}');
       this.push('{' + hash.types.join(',') + '}');
@@ -589,8 +596,12 @@ JavaScriptCompiler.prototype = {
   assignToHash: function(key) {
     var value = this.popStack(),
         context,
-        type;
+        type,
+        id;
 
+    if (this.trackIds) {
+      id = this.popStack();
+    }
     if (this.stringParams) {
       type = this.popStack();
       context = this.popStack();
@@ -603,9 +614,22 @@ JavaScriptCompiler.prototype = {
     if (type) {
       hash.types.push("'" + key + "': " + type);
     }
+    if (id) {
+      hash.ids.push("'" + key + "': " + id);
+    }
     hash.values.push("'" + key + "': (" + value + ")");
   },
 
+  pushId: function(type, name) {
+    if (type === 'ID' || type === 'DATA') {
+      this.pushString(name);
+    } else if (type === 'sexpr') {
+      this.pushStackLiteral('true');
+    } else {
+      this.pushStackLiteral('null');
+    }
+  },
+
   // HELPERS
 
   compiler: JavaScriptCompiler,
@@ -844,11 +868,14 @@ JavaScriptCompiler.prototype = {
   },
 
   setupOptions: function(helper, paramSize, params) {
-    var options = {}, contexts = [], types = [], param, inverse, program;
+    var options = {}, contexts = [], types = [], ids = [], param, inverse, program;
 
     options.name = this.quotedString(helper);
     options.hash = this.popStack();
 
+    if (this.trackIds) {
+      options.hashIds = this.popStack();
+    }
     if (this.stringParams) {
       options.hashTypes = this.popStack();
       options.hashContexts = this.popStack();
@@ -878,12 +905,18 @@ JavaScriptCompiler.prototype = {
       param = this.popStack();
       params.push(param);
 
+      if (this.trackIds) {
+        ids.push(this.popStack());
+      }
       if (this.stringParams) {
         types.push(this.popStack());
         contexts.push(this.popStack());
       }
     }
 
+    if (this.trackIds) {
+      options.ids = "[" + ids.join(",") + "]";
+    }
     if (this.stringParams) {
       options.types = "[" + types.join(",") + "]";
       options.contexts = "[" + contexts.join(",") + "]";
diff --git a/spec/track-ids.js b/spec/track-ids.js
index 9d9847ced..6bd97d5b3 100644
--- a/spec/track-ids.js
+++ b/spec/track-ids.js
@@ -1,6 +1,108 @@
+/*global CompilerContext */
 describe('track ids', function() {
-  it('should include argument ids');
-  it('should include hash ids');
-  it('should note ../ references');
-  it('should update the path when using built helpers');
+  var context;
+  beforeEach(function() {
+    context = {is: {a: 'foo'}, slave: {driver: 'bar'}};
+  });
+
+  it('should not include anything without the flag', function() {
+    var template = CompilerContext.compile('{{wycats is.a slave.driver}}');
+
+    var helpers = {
+      wycats: function(passiveVoice, noun, options) {
+        equal(options.ids, undefined);
+        equal(options.hashIds, undefined);
+
+        return 'success';
+      }
+    };
+
+    equals(template({}, {helpers: helpers}), 'success');
+  });
+  it('should include argument ids', function() {
+    var template = CompilerContext.compile('{{wycats is.a slave.driver}}', {trackIds: true});
+
+    var helpers = {
+      wycats: function(passiveVoice, noun, options) {
+        equal(options.ids[0], 'is.a');
+        equal(options.ids[1], 'slave.driver');
+
+        return "HELP ME MY BOSS " + options.ids[0] + ':' + passiveVoice + ' ' + options.ids[1] + ':' + noun;
+      }
+    };
+
+    equals(template(context, {helpers: helpers}), 'HELP ME MY BOSS is.a:foo slave.driver:bar');
+  });
+  it('should include hash ids', function() {
+    var template = CompilerContext.compile('{{wycats bat=is.a baz=slave.driver}}', {trackIds: true});
+
+    var helpers = {
+      wycats: function(options) {
+        equal(options.hashIds.bat, 'is.a');
+        equal(options.hashIds.baz, 'slave.driver');
+
+        return "HELP ME MY BOSS " + options.hashIds.bat + ':' + options.hash.bat + ' ' + options.hashIds.baz + ':' + options.hash.baz;
+      }
+    };
+
+    equals(template(context, {helpers: helpers}), 'HELP ME MY BOSS is.a:foo slave.driver:bar');
+  });
+  it('should note ../ and ./ references', function() {
+    var template = CompilerContext.compile('{{wycats ./is.a ../slave.driver}}', {trackIds: true});
+
+    var helpers = {
+      wycats: function(passiveVoice, noun, options) {
+        equal(options.ids[0], 'is.a');
+        equal(options.ids[1], '../slave.driver');
+
+        return "HELP ME MY BOSS " + options.ids[0] + ':' + passiveVoice + ' ' + options.ids[1] + ':' + noun;
+      }
+    };
+
+    equals(template(context, {helpers: helpers}), 'HELP ME MY BOSS is.a:foo ../slave.driver:undefined');
+  });
+  it('should note @data references', function() {
+    var template = CompilerContext.compile('{{wycats @is.a @slave.driver}}', {trackIds: true});
+
+    var helpers = {
+      wycats: function(passiveVoice, noun, options) {
+        equal(options.ids[0], '@is.a');
+        equal(options.ids[1], '@slave.driver');
+
+        return "HELP ME MY BOSS " + options.ids[0] + ':' + passiveVoice + ' ' + options.ids[1] + ':' + noun;
+      }
+    };
+
+    equals(template({}, {helpers: helpers, data:context}), 'HELP ME MY BOSS @is.a:foo @slave.driver:bar');
+  });
+
+  it('should return null for constants', function() {
+    var template = CompilerContext.compile('{{wycats 1 "foo" key=false}}', {trackIds: true});
+
+    var helpers = {
+      wycats: function(passiveVoice, noun, options) {
+        equal(options.ids[0], null);
+        equal(options.ids[1], null);
+        equal(options.hashIds.key, null);
+
+        return "HELP ME MY BOSS " + passiveVoice + ' ' + noun + ' ' + options.hash.key;
+      }
+    };
+
+    equals(template(context, {helpers: helpers}), 'HELP ME MY BOSS 1 foo false');
+  });
+  it('should return true for subexpressions', function() {
+    var template = CompilerContext.compile('{{wycats (sub)}}', {trackIds: true});
+
+    var helpers = {
+      sub: function() { return 1; },
+      wycats: function(passiveVoice, options) {
+        equal(options.ids[0], true);
+
+        return "HELP ME MY BOSS " + passiveVoice;
+      }
+    };
+
+    equals(template(context, {helpers: helpers}), 'HELP ME MY BOSS 1');
+  });
 });

From 49fcf10de21b89c504d641e72ce6c31c2feaed8f Mon Sep 17 00:00:00 2001
From: kpdecker <kpdecker@gmail.com>
Date: Fri, 17 Jan 2014 20:01:43 -0600
Subject: [PATCH 30/53] Add contextPath tracking in builtin helpers

---
 lib/handlebars/base.js  | 34 +++++++++++++++++++++++--
 lib/handlebars/utils.js |  4 +++
 spec/track-ids.js       | 55 +++++++++++++++++++++++++++++++++++++++++
 3 files changed, 91 insertions(+), 2 deletions(-)

diff --git a/lib/handlebars/base.js b/lib/handlebars/base.js
index 790228c1c..9dc49fc8e 100644
--- a/lib/handlebars/base.js
+++ b/lib/handlebars/base.js
@@ -70,12 +70,19 @@ function registerDefaultHelpers(instance) {
       return inverse(this);
     } else if (isArray(context)) {
       if(context.length > 0) {
+        options.ids = [options.name];
         return instance.helpers.each(context, options);
       } else {
         return inverse(this);
       }
     } else {
-      return fn(context);
+      if (options.ids) {
+        var data = createFrame(options.data);
+        data.contextPath = Utils.appendContextPath(options.data.contextPath, options.name);
+        options = {data: data};
+      }
+
+      return fn(context, options);
     }
   });
 
@@ -89,6 +96,11 @@ function registerDefaultHelpers(instance) {
     var fn = options.fn, inverse = options.inverse;
     var i = 0, ret = "", data;
 
+    var contextPath;
+    if (options.ids) {
+      contextPath = Utils.appendContextPath(options.data.contextPath, options.ids[0]) + '.';
+    }
+
     if (isFunction(context)) { context = context.call(this); }
 
     if (options.data) {
@@ -102,6 +114,10 @@ function registerDefaultHelpers(instance) {
             data.index = i;
             data.first = (i === 0);
             data.last  = (i === (context.length-1));
+
+            if (contextPath) {
+              data.contextPath = contextPath + i;
+            }
           }
           ret = ret + fn(context[i], { data: data });
         }
@@ -112,6 +128,10 @@ function registerDefaultHelpers(instance) {
               data.key = key;
               data.index = i;
               data.first = (i === 0);
+
+              if (contextPath) {
+                data.contextPath = contextPath + key;
+              }
             }
             ret = ret + fn(context[key], {data: data});
             i++;
@@ -147,7 +167,17 @@ function registerDefaultHelpers(instance) {
   instance.registerHelper('with', function(context, options) {
     if (isFunction(context)) { context = context.call(this); }
 
-    if (!Utils.isEmpty(context)) return options.fn(context);
+    var fn = options.fn;
+
+    if (!Utils.isEmpty(context)) {
+      if (options.ids) {
+        var data = createFrame(options.data);
+        data.contextPath = Utils.appendContextPath(options.data.contextPath, options.ids[0]);
+        options = {data:data};
+      }
+
+      return fn(context, options);
+    }
   });
 
   instance.registerHelper('log', function(context, options) {
diff --git a/lib/handlebars/utils.js b/lib/handlebars/utils.js
index 63148e460..f2f1a5470 100644
--- a/lib/handlebars/utils.js
+++ b/lib/handlebars/utils.js
@@ -75,3 +75,7 @@ export function isEmpty(value) {
     return false;
   }
 }
+
+export function appendContextPath(contextPath, id) {
+  return (contextPath ? contextPath + '.' : '') + id;
+}
diff --git a/spec/track-ids.js b/spec/track-ids.js
index 6bd97d5b3..938f98bb5 100644
--- a/spec/track-ids.js
+++ b/spec/track-ids.js
@@ -105,4 +105,59 @@ describe('track ids', function() {
 
     equals(template(context, {helpers: helpers}), 'HELP ME MY BOSS 1');
   });
+
+  describe('builtin helpers', function() {
+    var helpers = {
+      wycats: function(name, options) {
+        return name + ':' + options.data.contextPath + '\n';
+      }
+    };
+
+    describe('#each', function() {
+      it('should track contextPath for arrays', function() {
+        var template = CompilerContext.compile('{{#each array}}{{wycats name}}{{/each}}', {trackIds: true});
+
+        equals(template({array: [{name: 'foo'}, {name: 'bar'}]}, {helpers: helpers}), 'foo:array.0\nbar:array.1\n');
+      });
+      it('should track contextPath for keys', function() {
+        var template = CompilerContext.compile('{{#each object}}{{wycats name}}{{/each}}', {trackIds: true});
+
+        equals(template({object: {foo: {name: 'foo'}, bar: {name: 'bar'}}}, {helpers: helpers}), 'foo:object.foo\nbar:object.bar\n');
+      });
+      it('should handle nesting', function() {
+        var template = CompilerContext.compile('{{#each .}}{{#each .}}{{wycats name}}{{/each}}{{/each}}', {trackIds: true});
+
+        equals(template({array: [{name: 'foo'}, {name: 'bar'}]}, {helpers: helpers}), 'foo:.array..0\nbar:.array..1\n');
+      });
+    });
+    describe('#with', function() {
+      it('should track contextPath', function() {
+        var template = CompilerContext.compile('{{#with field}}{{wycats name}}{{/with}}', {trackIds: true});
+
+        equals(template({field: {name: 'foo'}}, {helpers: helpers}), 'foo:field\n');
+      });
+      it('should handle nesting', function() {
+        var template = CompilerContext.compile('{{#with bat}}{{#with field}}{{wycats name}}{{/with}}{{/with}}', {trackIds: true});
+
+        equals(template({bat: {field: {name: 'foo'}}}, {helpers: helpers}), 'foo:bat.field\n');
+      });
+    });
+    describe('#blockHelperMissing', function() {
+      it('should track contextPath for arrays', function() {
+        var template = CompilerContext.compile('{{#field}}{{wycats name}}{{/field}}', {trackIds: true});
+
+        equals(template({field: [{name: 'foo'}]}, {helpers: helpers}), 'foo:field.0\n');
+      });
+      it('should track contextPath for keys', function() {
+        var template = CompilerContext.compile('{{#field}}{{wycats name}}{{/field}}', {trackIds: true});
+
+        equals(template({field: {name: 'foo'}}, {helpers: helpers}), 'foo:field\n');
+      });
+      it('should handle nesting', function() {
+        var template = CompilerContext.compile('{{#bat}}{{#field}}{{wycats name}}{{/field}}{{/bat}}', {trackIds: true});
+
+        equals(template({bat: {field: {name: 'foo'}}}, {helpers: helpers}), 'foo:bat.field\n');
+      });
+    });
+  });
 });

From 5a0bcb932de840b32910ebe65cb851b764425e66 Mon Sep 17 00:00:00 2001
From: kpdecker <kpdecker@gmail.com>
Date: Fri, 17 Jan 2014 23:24:35 -0600
Subject: [PATCH 31/53] Fix handler execution in nondata/nonid mode

---
 lib/handlebars/base.js | 11 +++++++----
 1 file changed, 7 insertions(+), 4 deletions(-)

diff --git a/lib/handlebars/base.js b/lib/handlebars/base.js
index 9dc49fc8e..2dc2fea6d 100644
--- a/lib/handlebars/base.js
+++ b/lib/handlebars/base.js
@@ -70,13 +70,16 @@ function registerDefaultHelpers(instance) {
       return inverse(this);
     } else if (isArray(context)) {
       if(context.length > 0) {
-        options.ids = [options.name];
+        if (options.ids) {
+          options.ids = [options.name];
+        }
+
         return instance.helpers.each(context, options);
       } else {
         return inverse(this);
       }
     } else {
-      if (options.ids) {
+      if (options.data && options.ids) {
         var data = createFrame(options.data);
         data.contextPath = Utils.appendContextPath(options.data.contextPath, options.name);
         options = {data: data};
@@ -97,7 +100,7 @@ function registerDefaultHelpers(instance) {
     var i = 0, ret = "", data;
 
     var contextPath;
-    if (options.ids) {
+    if (options.data && options.ids) {
       contextPath = Utils.appendContextPath(options.data.contextPath, options.ids[0]) + '.';
     }
 
@@ -170,7 +173,7 @@ function registerDefaultHelpers(instance) {
     var fn = options.fn;
 
     if (!Utils.isEmpty(context)) {
-      if (options.ids) {
+      if (options.data && options.ids) {
         var data = createFrame(options.data);
         data.contextPath = Utils.appendContextPath(options.data.contextPath, options.ids[0]);
         options = {data:data};

From 311be67bdedd6c27472937b046dd131848259d79 Mon Sep 17 00:00:00 2001
From: kpdecker <kpdecker@gmail.com>
Date: Sat, 18 Jan 2014 09:22:29 -0600
Subject: [PATCH 32/53] Add partial and helper unregister APIs

Fixes #669
---
 lib/handlebars/base.js | 6 ++++++
 1 file changed, 6 insertions(+)

diff --git a/lib/handlebars/base.js b/lib/handlebars/base.js
index 2dc2fea6d..7ebfc5567 100644
--- a/lib/handlebars/base.js
+++ b/lib/handlebars/base.js
@@ -38,6 +38,9 @@ HandlebarsEnvironment.prototype = {
       this.helpers[name] = fn;
     }
   },
+  unregisterHelper: function(name) {
+    delete this.helpers[name];
+  },
 
   registerPartial: function(name, str) {
     if (toString.call(name) === objectType) {
@@ -45,6 +48,9 @@ HandlebarsEnvironment.prototype = {
     } else {
       this.partials[name] = str;
     }
+  },
+  unregisterPartial: function(name) {
+    delete this.partials[name];
   }
 };
 

From cea57c05e28e0c2f5af2fab1c67792613f3728c9 Mon Sep 17 00:00:00 2001
From: kpdecker <kpdecker@gmail.com>
Date: Sat, 18 Jan 2014 09:23:11 -0600
Subject: [PATCH 33/53] Optimize initData for root defined case

---
 lib/handlebars/runtime.js | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/lib/handlebars/runtime.js b/lib/handlebars/runtime.js
index 5df681f82..6014691ac 100644
--- a/lib/handlebars/runtime.js
+++ b/lib/handlebars/runtime.js
@@ -61,8 +61,8 @@ export function template(templateSpec, env) {
       return programWrapper;
     },
     initData: function(context, data) {
-      data = data ? createFrame(data) : {};
-      if (!('root' in data)) {
+      if (!data || !('root' in data)) {
+        data = data ? createFrame(data) : {};
         data.root = context;
       }
       return data;

From 4679b1d9314a022ad305bab9e6cfed66088428b9 Mon Sep 17 00:00:00 2001
From: kpdecker <kpdecker@gmail.com>
Date: Sat, 18 Jan 2014 09:29:47 -0600
Subject: [PATCH 34/53] Include content on binary output test failure

---
 tasks/test.js | 5 +++--
 1 file changed, 3 insertions(+), 2 deletions(-)

diff --git a/tasks/test.js b/tasks/test.js
index cd5f00e80..b3f2056fd 100644
--- a/tasks/test.js
+++ b/tasks/test.js
@@ -10,8 +10,9 @@ module.exports = function(grunt) {
         throw err;
       }
 
-      if (stdout.toString() !== fs.readFileSync('./spec/expected/empty.amd.js').toString()) {
-        throw new Error('Expected binary output differed');
+      var expected = fs.readFileSync('./spec/expected/empty.amd.js');
+      if (stdout.toString() !== expected.toString()) {
+        throw new Error('Expected binary output differed:\n\n' + stdout + '\n\n' + expected);
       }
 
       done();

From 8f07bbabeabaed5d8100261b952c9de07fad7a8c Mon Sep 17 00:00:00 2001
From: kpdecker <kpdecker@gmail.com>
Date: Sun, 19 Jan 2014 19:20:55 -0600
Subject: [PATCH 35/53] Add quotes to exception

---
 tasks/test.js | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/tasks/test.js b/tasks/test.js
index b3f2056fd..664af60d8 100644
--- a/tasks/test.js
+++ b/tasks/test.js
@@ -12,7 +12,7 @@ module.exports = function(grunt) {
 
       var expected = fs.readFileSync('./spec/expected/empty.amd.js');
       if (stdout.toString() !== expected.toString()) {
-        throw new Error('Expected binary output differed:\n\n' + stdout + '\n\n' + expected);
+        throw new Error('Expected binary output differed:\n\n"' + stdout + '"\n\n"' + expected + '"');
       }
 
       done();

From 6ca06d41156a43d29790d101d6aa97ed922ca8ad Mon Sep 17 00:00:00 2001
From: kpdecker <kpdecker@gmail.com>
Date: Thu, 6 Feb 2014 22:50:12 -0800
Subject: [PATCH 36/53] Add depth benchmark cases

---
 bench/templates/depth-1.js | 6 ++++++
 bench/templates/depth-2.js | 6 ++++++
 2 files changed, 12 insertions(+)
 create mode 100644 bench/templates/depth-1.js
 create mode 100644 bench/templates/depth-2.js

diff --git a/bench/templates/depth-1.js b/bench/templates/depth-1.js
new file mode 100644
index 000000000..74809bca8
--- /dev/null
+++ b/bench/templates/depth-1.js
@@ -0,0 +1,6 @@
+module.exports = {
+  context: { names: [{name: "Moe"}, {name: "Larry"}, {name: "Curly"}, {name: "Shemp"}], foo: 'bar' },
+  handlebars: "{{#each names}}{{../foo}}{{/each}}",
+  mustache: "{{#names}}{{foo}}{{/names}}",
+  eco: "<% for item in @names: %><%= @foo %><% end %>"
+};
diff --git a/bench/templates/depth-2.js b/bench/templates/depth-2.js
new file mode 100644
index 000000000..1d38baa4e
--- /dev/null
+++ b/bench/templates/depth-2.js
@@ -0,0 +1,6 @@
+module.exports = {
+  context: { names: [{bat: 'foo', name: ["Moe"]}, {bat: 'foo', name: ["Larry"]}, {bat: 'foo', name: ["Curly"]}, {bat: 'foo', name: ["Shemp"]}], foo: 'bar' },
+  handlebars: "{{#each names}}{{#each name}}{{../bat}}{{../../foo}}{{/each}}{{/each}}",
+  mustache: "{{#names}}{{#name}}{{bat}}{{foo}}{{/name}}{{/names}}",
+  eco: "<% for item in @names: %><% for child in item.name: %><%= item.bat %><%= @foo %><% end %><% end %>"
+};

From b0e72985239504e7d70643bfd767cf14fd14920b Mon Sep 17 00:00:00 2001
From: kpdecker <kpdecker@gmail.com>
Date: Thu, 6 Feb 2014 23:01:43 -0800
Subject: [PATCH 37/53] Remove unnecessary conditional

---
 lib/handlebars/compiler/javascript-compiler.js | 4 +---
 1 file changed, 1 insertion(+), 3 deletions(-)

diff --git a/lib/handlebars/compiler/javascript-compiler.js b/lib/handlebars/compiler/javascript-compiler.js
index ff6bc476c..55edb9af7 100644
--- a/lib/handlebars/compiler/javascript-compiler.js
+++ b/lib/handlebars/compiler/javascript-compiler.js
@@ -17,9 +17,7 @@ JavaScriptCompiler.prototype = {
       wrap = true;
     }
 
-    if (/^[0-9]+$/.test(name)) {
-      ret = parent + "[" + name + "]";
-    } else if (JavaScriptCompiler.isValidJavaScriptVariableName(name)) {
+    if (JavaScriptCompiler.isValidJavaScriptVariableName(name)) {
       ret = parent + "." + name;
     } else {
       ret = parent + "['" + name + "']";

From 2812fe27758e38dbefad8b02c61ffb19aabf2410 Mon Sep 17 00:00:00 2001
From: kpdecker <kpdecker@gmail.com>
Date: Sun, 19 Jan 2014 17:32:25 -0600
Subject: [PATCH 38/53] Convert template spec to object literal

This allows for metadata to be associated with the template and a simplification of the template init logic.
---
 .../compiler/javascript-compiler.js           | 112 +++++++++---------
 lib/handlebars/runtime.js                     |  62 ++++++----
 spec/expected/empty.amd.js                    |   8 +-
 spec/javascript-compiler.js                   |   2 +-
 4 files changed, 95 insertions(+), 89 deletions(-)

diff --git a/lib/handlebars/compiler/javascript-compiler.js b/lib/handlebars/compiler/javascript-compiler.js
index ff6bc476c..f0ae5ac85 100644
--- a/lib/handlebars/compiler/javascript-compiler.js
+++ b/lib/handlebars/compiler/javascript-compiler.js
@@ -35,7 +35,7 @@ JavaScriptCompiler.prototype = {
   compilerInfo: function() {
     var revision = COMPILER_REVISION,
         versions = REVISION_CHANGES[revision];
-    return "this.compilerInfo = ["+revision+",'"+versions+"'];\n";
+    return [revision, versions];
   },
 
   appendToBuffer: function(string) {
@@ -62,6 +62,7 @@ JavaScriptCompiler.prototype = {
     this.options = options || {};
     this.stringParams = this.options.stringParams;
     this.trackIds = this.options.trackIds;
+    this.precompile = !asObject;
 
     log('debug', this.environment.disassemble() + "\n\n");
 
@@ -69,14 +70,14 @@ JavaScriptCompiler.prototype = {
     this.isChild = !!context;
     this.context = context || {
       programs: [],
-      environments: [],
-      aliases: { }
+      environments: []
     };
 
     this.preamble();
 
     this.stackSlot = 0;
     this.stackVars = [];
+    this.aliases = {};
     this.registers = { list: [] };
     this.hashes = [];
     this.compileStack = [];
@@ -110,22 +111,38 @@ JavaScriptCompiler.prototype = {
       throw new Exception('Compile completed with content left on stack');
     }
 
-    return this.createFunctionContext(asObject);
-  },
+    var fn = this.createFunctionContext(asObject);
+    if (!this.isChild) {
+      var ret = {
+        compiler: this.compilerInfo(),
+        main: fn
+      };
+      this.context.programs.map(function(program, index) {
+        if (program) {
+          ret[index] = program;
+        }
+      });
 
-  preamble: function() {
-    var out = [];
+      if (this.environment.usePartial) {
+        ret.usePartial = true;
+      }
+      if (this.options.data) {
+        ret.useData = true;
+      }
 
-    if (!this.isChild) {
-      var namespace = this.namespace;
+      if (!asObject) {
+        ret.compiler = JSON.stringify(ret.compiler);
+        ret = this.objectLiteral(ret);
+      }
 
-      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 = this.initData(depth0, data);"; }
-      out.push(copies);
+      return ret;
     } else {
-      out.push('');
+      return fn;
     }
+  },
+
+  preamble: function() {
+    var out = [];
 
     if (!this.environment.isSimple) {
       out.push(", buffer = " + this.initializeBuffer());
@@ -143,32 +160,25 @@ JavaScriptCompiler.prototype = {
     var locals = this.stackVars.concat(this.registers.list);
 
     if(locals.length > 0) {
-      this.source[1] = this.source[1] + ", " + locals.join(", ");
+      this.source[0] += ", " + 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];
-        }
+    for (var alias in this.aliases) {
+      if (this.aliases.hasOwnProperty(alias)) {
+        this.source[0] += ', ' + alias + '=' + this.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.source[0]) {
+      this.source[0] = "var " + this.source[0].substring(2) + ";";
     }
 
     if (!this.environment.isSimple) {
       this.pushSource("return buffer;");
     }
 
-    var params = this.isChild ? ["depth0", "data"] : [this.namespace, "depth0", "helpers", "partials", "data"];
+    var params = ["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]);
@@ -177,18 +187,12 @@ JavaScriptCompiler.prototype = {
     // Perform a second pass over the output to merge content when possible
     var source = this.mergeSource();
 
-    if (!this.isChild) {
-      source = this.compilerInfo()+source;
-    }
-
     if (asObject) {
       params.push(source);
 
       return Function.apply(this, params);
     } else {
-      var functionSource = 'function ' + (this.name || '') + '(' + params.join(',') + ') {\n  ' + source + '}';
-      log('debug', functionSource + "\n\n");
-      return functionSource;
+      return 'function(' + params.join(',') + ') {\n  ' + source + '}';
     }
   },
   mergeSource: function() {
@@ -225,7 +229,7 @@ JavaScriptCompiler.prototype = {
   // replace it on the stack with the result of properly
   // invoking blockHelperMissing.
   blockValue: function(name) {
-    this.context.aliases.blockHelperMissing = 'helpers.blockHelperMissing';
+    this.aliases.blockHelperMissing = 'helpers.blockHelperMissing';
 
     var params = ["depth0"];
     this.setupParams(name, 0, params);
@@ -243,7 +247,7 @@ JavaScriptCompiler.prototype = {
   // On stack, after, if no lastHelper: same as [blockValue]
   // On stack, after, if lastHelper: value
   ambiguousBlockValue: function() {
-    this.context.aliases.blockHelperMissing = 'helpers.blockHelperMissing';
+    this.aliases.blockHelperMissing = 'helpers.blockHelperMissing';
 
     // We're being a bit cheeky and reusing the options value from the prior exec
     var params = ["depth0"];
@@ -315,7 +319,7 @@ JavaScriptCompiler.prototype = {
   //
   // Escape `value` and append it to the buffer
   appendEscaped: function() {
-    this.context.aliases.escapeExpression = 'this.escapeExpression';
+    this.aliases.escapeExpression = 'this.escapeExpression';
 
     this.pushSource(this.appendToBuffer("escapeExpression(" + this.popStack() + ")"));
   },
@@ -362,7 +366,7 @@ JavaScriptCompiler.prototype = {
   // 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.aliases.functionType = '"function"';
 
     this.replaceStack(function(current) {
       return "typeof " + current + " === functionType ? " + current + ".apply(depth0) : " + current;
@@ -507,7 +511,7 @@ JavaScriptCompiler.prototype = {
   //
   // If the helper is not found, `helperMissing` is called.
   invokeHelper: function(paramSize, name, isRoot) {
-    this.context.aliases.helperMissing = 'helpers.helperMissing';
+    this.aliases.helperMissing = 'helpers.helperMissing';
     this.useRegister('helper');
 
     var nonHelper = this.popStack();
@@ -553,7 +557,7 @@ JavaScriptCompiler.prototype = {
   // and can be avoided by passing the `knownHelpers` and
   // `knownHelpersOnly` flags at compile-time.
   invokeAmbiguous: function(name, helperCall) {
-    this.context.aliases.functionType = '"function"';
+    this.aliases.functionType = '"function"';
     this.useRegister('helper');
 
     this.emptyHash();
@@ -582,8 +586,7 @@ JavaScriptCompiler.prototype = {
       params.push("data");
     }
 
-    this.context.aliases.self = "this";
-    this.push("self.invokePartial(" + params.join(", ") + ")");
+    this.push("this.invokePartial(" + params.join(", ") + ")");
   },
 
   // [assignToHash]
@@ -648,7 +651,7 @@ JavaScriptCompiler.prototype = {
         index = this.context.programs.length;
         child.index = index;
         child.name = 'program' + index;
-        this.context.programs[index] = compiler.compile(child, options, this.context);
+        this.context.programs[index] = compiler.compile(child, options, this.context, !this.precompile);
         this.context.environments[index] = child;
       } else {
         child.index = index;
@@ -666,25 +669,22 @@ JavaScriptCompiler.prototype = {
   },
 
   programExpression: function(guid) {
-    this.context.aliases.self = "this";
-
     if(guid == null) {
-      return "self.noop";
+      return 'this.noop';
     }
 
     var child = this.environment.children[guid],
         depths = child.depths.list, depth;
 
-    var programParams = [child.index, child.name, "data"];
+    var programParams = [child.index, '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)); }
+      programParams.push('depth' + (depth - 1));
     }
 
-    return (depths.length === 0 ? "self.program(" : "self.programWithDepth(") + programParams.join(", ") + ")";
+    return (depths.length === 0 ? 'this.program(' : 'this.programWithDepth(') + programParams.join(', ') + ')';
   },
 
   register: function(name, val) {
@@ -842,7 +842,7 @@ JavaScriptCompiler.prototype = {
       .replace(/\u2029/g, '\\u2029') + '"';
   },
 
-  setupHash: function(obj) {
+  objectLiteral: function(obj) {
     var pairs = [];
 
     for (var key in obj) {
@@ -888,13 +888,11 @@ JavaScriptCompiler.prototype = {
     // helpers to do a check for `if (options.fn)`
     if (program || inverse) {
       if (!program) {
-        this.context.aliases.self = "this";
-        program = "self.noop";
+        program = 'this.noop';
       }
 
       if (!inverse) {
-        this.context.aliases.self = "this";
-        inverse = "self.noop";
+        inverse = 'this.noop';
       }
 
       options.fn = program;
@@ -932,7 +930,7 @@ JavaScriptCompiler.prototype = {
   // the params and contexts arguments are passed in arrays
   // to fill in
   setupParams: function(helperName, paramSize, params, useRegister) {
-    var options = this.setupHash(this.setupOptions(helperName, paramSize, params));
+    var options = this.objectLiteral(this.setupOptions(helperName, paramSize, params));
 
     if (useRegister) {
       this.useRegister('options');
diff --git a/lib/handlebars/runtime.js b/lib/handlebars/runtime.js
index 6014691ac..e65db6e61 100644
--- a/lib/handlebars/runtime.js
+++ b/lib/handlebars/runtime.js
@@ -29,6 +29,8 @@ export function template(templateSpec, env) {
 
   // Note: Using env.VM references rather than local var references throughout this section to allow
   // for external users to override these as psuedo-supported APIs.
+  env.VM.checkRevision(templateSpec.compiler);
+
   var invokePartialWrapper = function(partial, name, context, hash, helpers, partials, data) {
     if (hash) {
       context = Utils.extend({}, context, hash);
@@ -50,16 +52,24 @@ export function template(templateSpec, env) {
   var container = {
     escapeExpression: Utils.escapeExpression,
     invokePartial: invokePartialWrapper,
+
+    fn: function(i) {
+      return templateSpec[i];
+    },
+
     programs: [],
-    program: function(i, fn, data) {
-      var programWrapper = this.programs[i];
+    program: function(i, data) {
+      var programWrapper = this.programs[i],
+          fn = this.fn(i);
       if(data) {
-        programWrapper = program(i, fn, data);
+        programWrapper = program(this, i, fn, data);
       } else if (!programWrapper) {
-        programWrapper = this.programs[i] = program(i, fn);
+        programWrapper = this.programs[i] = program(this, i, fn);
       }
       return programWrapper;
     },
+    programWithDepth: env.VM.programWithDepth,
+
     initData: function(context, data) {
       if (!data || !('root' in data)) {
         data = data ? createFrame(data) : {};
@@ -76,54 +86,56 @@ export function template(templateSpec, env) {
 
       return ret;
     },
-    programWithDepth: env.VM.programWithDepth,
+
     noop: env.VM.noop,
-    compilerInfo: null
+    compilerInfo: templateSpec.compiler
   };
 
   return function(context, options) {
     options = options || {};
     var namespace = options.partial ? options : env,
         helpers,
-        partials;
+        partials,
+        data = options.data;
 
     if (!options.partial) {
-      helpers = options.helpers;
-      partials = options.partials;
-    }
-    var result = templateSpec.call(
-          container,
-          namespace, context,
-          helpers,
-          partials,
-          options.data);
+      helpers = container.helpers = container.merge(options.helpers, namespace.helpers);
 
-    if (!options.partial) {
-      env.VM.checkRevision(container.compilerInfo);
+      if (templateSpec.usePartial) {
+        partials = container.partials = container.merge(options.partials, namespace.partials);
+      }
+      if (templateSpec.useData) {
+        data = container.initData(context, data);
+      }
+    } else {
+      helpers = container.helpers = options.helpers;
+      partials = container.partials = options.partials;
     }
-
-    return result;
+    return templateSpec.main.call(container, context, helpers, partials, data);
   };
 }
 
-export function programWithDepth(i, fn, data /*, $depth */) {
-  var args = Array.prototype.slice.call(arguments, 3);
+export function programWithDepth(i, data /*, $depth */) {
+  /*jshint -W040 */
+  var args = Array.prototype.slice.call(arguments, 2),
+      container = this,
+      fn = container.fn(i);
 
   var prog = function(context, options) {
     options = options || {};
 
-    return fn.apply(this, [context, options.data || data].concat(args));
+    return fn.apply(container, [context, container.helpers, container.partials, options.data || data].concat(args));
   };
   prog.program = i;
   prog.depth = args.length;
   return prog;
 }
 
-export function program(i, fn, data) {
+export function program(container, i, fn, data) {
   var prog = function(context, options) {
     options = options || {};
 
-    return fn(context, options.data || data);
+    return fn.call(container, context, container.helpers, container.partials, options.data || data);
   };
   prog.program = i;
   prog.depth = 0;
diff --git a/spec/expected/empty.amd.js b/spec/expected/empty.amd.js
index ca80b8850..ae6fdf95b 100644
--- a/spec/expected/empty.amd.js
+++ b/spec/expected/empty.amd.js
@@ -1,11 +1,7 @@
 define(['handlebars.runtime'], function(Handlebars) {
   Handlebars = Handlebars["default"];  var template = Handlebars.template, templates = Handlebars.templates = Handlebars.templates || {};
-return templates['empty'] = template(function (Handlebars,depth0,helpers,partials,data) {
-  this.compilerInfo = [4,'>= 1.0.0'];
-helpers = this.merge(helpers, Handlebars.helpers); data = this.initData(depth0, data);
+return templates['empty'] = template({"compiler":[4,">= 1.0.0"],"main":function(depth0,helpers,partials,data) {
   var buffer = "";
-
-
   return buffer;
-  });
+  },"useData":true});
 });
diff --git a/spec/javascript-compiler.js b/spec/javascript-compiler.js
index 884abf1c9..a9ae0f8a9 100644
--- a/spec/javascript-compiler.js
+++ b/spec/javascript-compiler.js
@@ -32,7 +32,7 @@ describe('javascript-compiler api', function() {
     });
     it('should allow compilerInfo override', function() {
       handlebarsEnv.JavaScriptCompiler.prototype.compilerInfo = function() {
-        return 'this.compilerInfo = "crazy";';
+        return 'crazy';
       };
       handlebarsEnv.VM.checkRevision = function(compilerInfo) {
         if (compilerInfo !== 'crazy') {

From dd365eaede63b2a1ae2e58778647ffe42e0539c3 Mon Sep 17 00:00:00 2001
From: PatrickJS <github@gdi2290.com>
Date: Mon, 3 Feb 2014 22:31:02 -0800
Subject: [PATCH 39/53] update copyright year

---
 LICENSE | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/LICENSE b/LICENSE
index f466a93fd..e9563e18b 100644
--- a/LICENSE
+++ b/LICENSE
@@ -1,4 +1,4 @@
-Copyright (C) 2011 by Yehuda Katz
+Copyright (C) 2014 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

From 7a6f706dc41a39b360e33c83fe31684316d5d3f9 Mon Sep 17 00:00:00 2001
From: kpdecker <kpdecker@gmail.com>
Date: Sun, 9 Feb 2014 12:19:55 -0600
Subject: [PATCH 40/53] Add initial copyright year

---
 LICENSE | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/LICENSE b/LICENSE
index e9563e18b..a2d22cbb4 100644
--- a/LICENSE
+++ b/LICENSE
@@ -1,4 +1,4 @@
-Copyright (C) 2014 by Yehuda Katz
+Copyright (C) 2011-2014 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

From d6e86af41ab987b5ad8be0d4a98f0cc408cb5e1d Mon Sep 17 00:00:00 2001
From: kpdecker <kpdecker@gmail.com>
Date: Sun, 9 Feb 2014 13:09:38 -0600
Subject: [PATCH 41/53] Update to latest packager version

Fixes #705
---
 package.json | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/package.json b/package.json
index 6403c5d46..0ce1d5bdc 100644
--- a/package.json
+++ b/package.json
@@ -43,7 +43,7 @@
     "grunt-contrib-uglify": "~0.2.2",
     "grunt-contrib-watch": "~0.5.3",
     "grunt-saucelabs": "~4.1.2",
-    "es6-module-packager": "0.x",
+    "es6-module-packager": "1.x",
     "jison": "~0.3.0",
     "keen.io": "0.0.3",
     "mocha": "*",

From 7a7ad74ff1c4fe8685d33cceebe03f1474eb865d Mon Sep 17 00:00:00 2001
From: kpdecker <kpdecker@gmail.com>
Date: Sun, 19 Jan 2014 18:54:13 -0600
Subject: [PATCH 42/53] Optimize buffer generate first and all edge cases

---
 .../compiler/javascript-compiler.js           | 65 +++++++++++--------
 spec/expected/empty.amd.js                    |  5 +-
 spec/javascript-compiler.js                   |  2 +
 3 files changed, 43 insertions(+), 29 deletions(-)

diff --git a/lib/handlebars/compiler/javascript-compiler.js b/lib/handlebars/compiler/javascript-compiler.js
index 8222506d2..878181901 100644
--- a/lib/handlebars/compiler/javascript-compiler.js
+++ b/lib/handlebars/compiler/javascript-compiler.js
@@ -140,42 +140,27 @@ JavaScriptCompiler.prototype = {
   },
 
   preamble: function() {
-    var out = [];
-
-    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;
+    this.source = [];
   },
 
   createFunctionContext: function(asObject) {
-    var locals = this.stackVars.concat(this.registers.list);
+    var varDeclarations = '';
 
+    var locals = this.stackVars.concat(this.registers.list);
     if(locals.length > 0) {
-      this.source[0] += ", " + locals.join(", ");
+      varDeclarations += ", " + locals.join(", ");
     }
 
     // Generate minimizer alias mappings
     for (var alias in this.aliases) {
       if (this.aliases.hasOwnProperty(alias)) {
-        this.source[0] += ', ' + alias + '=' + this.aliases[alias];
+        varDeclarations += ', ' + alias + '=' + this.aliases[alias];
       }
     }
 
-    if (this.source[0]) {
-      this.source[0] = "var " + this.source[0].substring(2) + ";";
-    }
-
-    if (!this.environment.isSimple) {
-      this.pushSource("return buffer;");
-    }
-
     var params = ["depth0", "helpers", "partials", "data"];
 
     for(var i=0, l=this.environment.depths.list.length; i<l; i++) {
@@ -183,7 +168,7 @@ JavaScriptCompiler.prototype = {
     }
 
     // Perform a second pass over the output to merge content when possible
-    var source = this.mergeSource();
+    var source = this.mergeSource(varDeclarations);
 
     if (asObject) {
       params.push(source);
@@ -193,11 +178,12 @@ JavaScriptCompiler.prototype = {
       return 'function(' + params.join(',') + ') {\n  ' + source + '}';
     }
   },
-  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.
+  mergeSource: function(varDeclarations) {
     var source = '',
-        buffer;
+        buffer,
+        appendOnly = !this.forceBuffer,
+        appendFirst;
+
     for (var i = 0, len = this.source.length; i < len; i++) {
       var line = this.source[i];
       if (line.appendToBuffer) {
@@ -208,12 +194,39 @@ JavaScriptCompiler.prototype = {
         }
       } else {
         if (buffer) {
-          source += 'buffer += ' + buffer + ';\n  ';
+          if (!source) {
+            appendFirst = true;
+            source = buffer + ';\n  ';
+          } else {
+            source += 'buffer += ' + buffer + ';\n  ';
+          }
           buffer = undefined;
         }
         source += line + '\n  ';
+
+        if (!this.environment.isSimple) {
+          appendOnly = false;
+        }
       }
     }
+
+    if (appendOnly) {
+      if (buffer || !source) {
+        source += 'return ' + (buffer || '""') + ';\n';
+      }
+    } else {
+      varDeclarations += ", buffer = " + (appendFirst ? '' : this.initializeBuffer());
+      if (buffer) {
+        source += 'return buffer + ' + buffer + ';\n';
+      } else {
+        source += 'return buffer;\n';
+      }
+    }
+
+    if (varDeclarations) {
+      source = 'var ' + varDeclarations.substring(2) + (appendFirst ? '' : ';\n  ') + source;
+    }
+
     return source;
   },
 
diff --git a/spec/expected/empty.amd.js b/spec/expected/empty.amd.js
index ae6fdf95b..d80446a1a 100644
--- a/spec/expected/empty.amd.js
+++ b/spec/expected/empty.amd.js
@@ -1,7 +1,6 @@
 define(['handlebars.runtime'], function(Handlebars) {
   Handlebars = Handlebars["default"];  var template = Handlebars.template, templates = Handlebars.templates = Handlebars.templates || {};
 return templates['empty'] = template({"compiler":[4,">= 1.0.0"],"main":function(depth0,helpers,partials,data) {
-  var buffer = "";
-  return buffer;
-  },"useData":true});
+  return "";
+},"useData":true});
 });
diff --git a/spec/javascript-compiler.js b/spec/javascript-compiler.js
index a9ae0f8a9..16058680b 100644
--- a/spec/javascript-compiler.js
+++ b/spec/javascript-compiler.js
@@ -45,10 +45,12 @@ describe('javascript-compiler api', function() {
   describe('buffer', function() {
     var $superAppend, $superCreate;
     beforeEach(function() {
+      handlebarsEnv.JavaScriptCompiler.prototype.forceBuffer = true;
       $superAppend = handlebarsEnv.JavaScriptCompiler.prototype.appendToBuffer;
       $superCreate = handlebarsEnv.JavaScriptCompiler.prototype.initializeBuffer;
     });
     afterEach(function() {
+      handlebarsEnv.JavaScriptCompiler.prototype.forceBuffer = false;
       handlebarsEnv.JavaScriptCompiler.prototype.appendToBuffer = $superAppend;
       handlebarsEnv.JavaScriptCompiler.prototype.initializeBuffer = $superCreate;
     });

From 954253305a0deadf25236e087dd4d2908aee715a Mon Sep 17 00:00:00 2001
From: Jesse Ezell <jesse.ezell@neudesic.com>
Date: Thu, 11 Jul 2013 12:28:37 -0700
Subject: [PATCH 43/53] Add {{{{ }}}} for raw blocks

---
 spec/blocks.js    | 8 ++++++++
 src/handlebars.l  | 5 +++++
 src/handlebars.yy | 3 ++-
 3 files changed, 15 insertions(+), 1 deletion(-)

diff --git a/spec/blocks.js b/spec/blocks.js
index d72a44ec4..b9672af87 100644
--- a/spec/blocks.js
+++ b/spec/blocks.js
@@ -21,6 +21,14 @@ describe('blocks', function() {
     equal(result, "0. goodbye! 1. Goodbye! 2. GOODBYE! cruel world!", "The @index variable is used");
   });
 
+
+  it("raw block", function() {
+    var string   = "{{{{ {{test}} }}}}";
+    var hash = { test: "hello" };
+    shouldCompileTo(string, hash, " {{test}} ",
+                    "raw block ignores blocks");
+  });
+  
   it("empty block", function() {
     var string   = "{{#goodbyes}}{{/goodbyes}}cruel {{world}}!";
     var hash     = {goodbyes: [{text: "goodbye"}, {text: "Goodbye"}, {text: "GOODBYE"}], world: "world"};
diff --git a/src/handlebars.l b/src/handlebars.l
index 630840eb5..51e921aec 100644
--- a/src/handlebars.l
+++ b/src/handlebars.l
@@ -54,6 +54,11 @@ ID    [^\s!"#%-,\.\/;->@\[-\^`\{-~]+/{LOOKAHEAD}
 <mu>"("                          return 'OPEN_SEXPR';
 <mu>")"                          return 'CLOSE_SEXPR';
 
+<mu>"{{{{"[^\x00]*"}}}}"         {
+                                  yytext = yytext.substr(4, yyleng-8);
+                                  this.popState();
+                                  return 'RAW_BLOCK';
+                                 }
 <mu>"{{"{LEFT_STRIP}?">"         return 'OPEN_PARTIAL';
 <mu>"{{"{LEFT_STRIP}?"#"         return 'OPEN_BLOCK';
 <mu>"{{"{LEFT_STRIP}?"/"         return 'OPEN_ENDBLOCK';
diff --git a/src/handlebars.yy b/src/handlebars.yy
index 40f68ce59..8b5b77455 100644
--- a/src/handlebars.yy
+++ b/src/handlebars.yy
@@ -35,7 +35,8 @@ statements
   ;
 
 statement
-  : openInverse program closeBlock -> new yy.BlockNode($1, $2.inverse, $2, $3, @$)
+  : RAW_BLOCK { $$ = new yy.ContentNode($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

From 9b14dc40a5ff5c3db0b62278dc0ea794c9a16593 Mon Sep 17 00:00:00 2001
From: Jesse Ezell <jesse.ezell@neudesic.com>
Date: Thu, 11 Jul 2013 14:50:14 -0700
Subject: [PATCH 44/53] raw block helpers

---
 lib/handlebars/compiler/ast.js |  9 +++++++++
 spec/blocks.js                 |  8 --------
 spec/helpers.js                | 20 ++++++++++++++++++++
 src/handlebars.l               |  7 ++++++-
 src/handlebars.yy              |  6 +++++-
 5 files changed, 40 insertions(+), 10 deletions(-)

diff --git a/lib/handlebars/compiler/ast.js b/lib/handlebars/compiler/ast.js
index 5a5f514db..b9e67d7fe 100644
--- a/lib/handlebars/compiler/ast.js
+++ b/lib/handlebars/compiler/ast.js
@@ -131,6 +131,15 @@ var AST = {
     }
   },
 
+  RawBlockNode: function(mustache, content, locInfo) {
+    LocationInfo.call(this, locInfo);
+
+    this.type = 'block';
+    this.mustache = mustache;
+    mustache.sexpr.isHelper = mustache.isHelper = true;
+    mustache.params.splice(0, 0, new AST.StringNode(content, locInfo));
+  },
+
   ContentNode: function(string, locInfo) {
     LocationInfo.call(this, locInfo);
     this.type = "content";
diff --git a/spec/blocks.js b/spec/blocks.js
index b9672af87..8f7c242fa 100644
--- a/spec/blocks.js
+++ b/spec/blocks.js
@@ -20,14 +20,6 @@ describe('blocks', function() {
 
     equal(result, "0. goodbye! 1. Goodbye! 2. GOODBYE! cruel world!", "The @index variable is used");
   });
-
-
-  it("raw block", function() {
-    var string   = "{{{{ {{test}} }}}}";
-    var hash = { test: "hello" };
-    shouldCompileTo(string, hash, " {{test}} ",
-                    "raw block ignores blocks");
-  });
   
   it("empty block", function() {
     var string   = "{{#goodbyes}}{{/goodbyes}}cruel {{world}}!";
diff --git a/spec/helpers.js b/spec/helpers.js
index 904f56a7c..b1d972291 100644
--- a/spec/helpers.js
+++ b/spec/helpers.js
@@ -9,6 +9,26 @@ describe('helpers', function() {
     shouldCompileTo(string, [hash, helpers], "<a href='/root/goodbye'>Goodbye</a>");
   });
 
+  it("helper for raw block gets raw content", function() {
+    var string   = "{{{{raw}}}} {{test}} {{{{/raw}}}}";
+    var hash = { test: "hello" };
+    var helpers = { raw: function(content) { 
+        return content;
+    } };
+    shouldCompileTo(string, [hash, helpers], " {{test}} ",
+                    "raw block helper gets raw content");
+  });
+  
+  it("helper for raw block gets parameters", function() {
+    var string   = "{{{{raw 1 2 3}}}} {{test}} {{{{/raw}}}}";
+    var hash = { test: "hello" };
+    var helpers = { raw: function(content, a, b, c) { 
+        return content + a + b + c;
+    } };
+    shouldCompileTo(string, [hash, helpers], " {{test}} 123",
+                    "raw block helper gets raw content");
+  });
+  
   it("helper block with complex lookup expression", function() {
     var string = "{{#goodbyes}}{{../name}}{{/goodbyes}}";
     var hash = {name: "Alan"};
diff --git a/src/handlebars.l b/src/handlebars.l
index 51e921aec..9b1266526 100644
--- a/src/handlebars.l
+++ b/src/handlebars.l
@@ -1,5 +1,5 @@
 
-%x mu emu com
+%x mu emu com raw
 
 %{
 
@@ -49,11 +49,16 @@ ID    [^\s!"#%-,\.\/;->@\[-\^`\{-~]+/{LOOKAHEAD}
                                    return 'CONTENT';
                                  }
 
+<raw>"{{{{/"[^\s!"#%-,\.\/;->@\[-\^`\{-~]+/[=}\s\/.]"}}}}" { yytext = yytext.substr(5, yyleng-9); this.popState(); return 'END_RAW_BLOCK'; }
+<raw>[^\x00]*?/("{{{{/")         { return 'CONTENT'; }
+
 <com>[\s\S]*?"--}}"              strip(0,4); this.popState(); return 'COMMENT';
 
 <mu>"("                          return 'OPEN_SEXPR';
 <mu>")"                          return 'CLOSE_SEXPR';
 
+<mu>"{{{{"                       { return 'OPEN_RAW_BLOCK'; }
+<mu>"}}}}"                       { this.popState(); this.begin('raw'); return 'CLOSE_RAW_BLOCK'; }
 <mu>"{{{{"[^\x00]*"}}}}"         {
                                   yytext = yytext.substr(4, yyleng-8);
                                   this.popState();
diff --git a/src/handlebars.yy b/src/handlebars.yy
index 8b5b77455..464d1bab0 100644
--- a/src/handlebars.yy
+++ b/src/handlebars.yy
@@ -35,7 +35,7 @@ statements
   ;
 
 statement
-  : RAW_BLOCK { $$ = new yy.ContentNode($1, @$); }
+  : openRawBlock CONTENT END_RAW_BLOCK -> new yy.RawBlockNode($1, $2, @$)
   | openInverse program closeBlock -> new yy.BlockNode($1, $2.inverse, $2, $3, @$)
   | openBlock program closeBlock -> new yy.BlockNode($1, $2, $2.inverse, $3, @$)
   | mustache -> $1
@@ -44,6 +44,10 @@ statement
   | COMMENT -> new yy.CommentNode($1, @$)
   ;
 
+openRawBlock
+  : OPEN_RAW_BLOCK sexpr CLOSE_RAW_BLOCK -> new yy.MustacheNode($2, null, '', '', @$)
+  ;
+
 openBlock
   : OPEN_BLOCK sexpr CLOSE -> new yy.MustacheNode($2, null, $1, stripFlags($1, $3), @$)
   ;

From 14b7ef9066d107dc83deedc8e6791947811cc764 Mon Sep 17 00:00:00 2001
From: kpdecker <kpdecker@gmail.com>
Date: Sun, 9 Feb 2014 15:11:12 -0600
Subject: [PATCH 45/53] Make raw blocks operate like blocks

---
 lib/handlebars/compiler/ast.js | 11 ++++++++---
 spec/helpers.js                |  8 ++++----
 spec/parser.js                 |  4 ++++
 src/handlebars.l               | 12 ++++++++++--
 src/handlebars.yy              |  2 +-
 5 files changed, 27 insertions(+), 10 deletions(-)

diff --git a/lib/handlebars/compiler/ast.js b/lib/handlebars/compiler/ast.js
index b9e67d7fe..286917dbe 100644
--- a/lib/handlebars/compiler/ast.js
+++ b/lib/handlebars/compiler/ast.js
@@ -131,13 +131,18 @@ var AST = {
     }
   },
 
-  RawBlockNode: function(mustache, content, locInfo) {
+  RawBlockNode: function(mustache, content, close, locInfo) {
     LocationInfo.call(this, locInfo);
 
+    if (mustache.sexpr.id.original !== close) {
+      throw new Exception(mustache.sexpr.id.original + " doesn't match " + close, this);
+    }
+
+    content = new AST.ContentNode(content, locInfo);
+
     this.type = 'block';
     this.mustache = mustache;
-    mustache.sexpr.isHelper = mustache.isHelper = true;
-    mustache.params.splice(0, 0, new AST.StringNode(content, locInfo));
+    this.program = new AST.ProgramNode([content], locInfo);
   },
 
   ContentNode: function(string, locInfo) {
diff --git a/spec/helpers.js b/spec/helpers.js
index b1d972291..ccde982ba 100644
--- a/spec/helpers.js
+++ b/spec/helpers.js
@@ -12,8 +12,8 @@ describe('helpers', function() {
   it("helper for raw block gets raw content", function() {
     var string   = "{{{{raw}}}} {{test}} {{{{/raw}}}}";
     var hash = { test: "hello" };
-    var helpers = { raw: function(content) { 
-        return content;
+    var helpers = { raw: function(options) {
+        return options.fn();
     } };
     shouldCompileTo(string, [hash, helpers], " {{test}} ",
                     "raw block helper gets raw content");
@@ -22,8 +22,8 @@ describe('helpers', function() {
   it("helper for raw block gets parameters", function() {
     var string   = "{{{{raw 1 2 3}}}} {{test}} {{{{/raw}}}}";
     var hash = { test: "hello" };
-    var helpers = { raw: function(content, a, b, c) { 
-        return content + a + b + c;
+    var helpers = { raw: function(a, b, c, options) {
+        return options.fn() + a + b + c;
     } };
     shouldCompileTo(string, [hash, helpers], " {{test}} 123",
                     "raw block helper gets raw content");
diff --git a/spec/parser.js b/spec/parser.js
index 5b6dc8a4e..097bb15b7 100644
--- a/spec/parser.js
+++ b/spec/parser.js
@@ -157,6 +157,10 @@ describe('parser', function() {
     shouldThrow(function() {
       ast_for("{{#goodbyes}}{{/hellos}}");
     }, Error, /goodbyes doesn't match hellos/);
+
+    shouldThrow(function() {
+      ast_for("{{{{goodbyes}}}} {{{{/hellos}}}}");
+    }, Error, /goodbyes doesn't match hellos/);
   });
 
   it('knows how to report the correct line number in errors', function() {
diff --git a/src/handlebars.l b/src/handlebars.l
index 9b1266526..cafdd72c7 100644
--- a/src/handlebars.l
+++ b/src/handlebars.l
@@ -49,7 +49,11 @@ ID    [^\s!"#%-,\.\/;->@\[-\^`\{-~]+/{LOOKAHEAD}
                                    return 'CONTENT';
                                  }
 
-<raw>"{{{{/"[^\s!"#%-,\.\/;->@\[-\^`\{-~]+/[=}\s\/.]"}}}}" { yytext = yytext.substr(5, yyleng-9); this.popState(); return 'END_RAW_BLOCK'; }
+<raw>"{{{{/"[^\s!"#%-,\.\/;->@\[-\^`\{-~]+/[=}\s\/.]"}}}}" {
+                                  yytext = yytext.substr(5, yyleng-9);
+                                  this.popState();
+                                  return 'END_RAW_BLOCK';
+                                 }
 <raw>[^\x00]*?/("{{{{/")         { return 'CONTENT'; }
 
 <com>[\s\S]*?"--}}"              strip(0,4); this.popState(); return 'COMMENT';
@@ -58,7 +62,11 @@ ID    [^\s!"#%-,\.\/;->@\[-\^`\{-~]+/{LOOKAHEAD}
 <mu>")"                          return 'CLOSE_SEXPR';
 
 <mu>"{{{{"                       { return 'OPEN_RAW_BLOCK'; }
-<mu>"}}}}"                       { this.popState(); this.begin('raw'); return 'CLOSE_RAW_BLOCK'; }
+<mu>"}}}}"                       {
+                                  this.popState();
+                                  this.begin('raw');
+                                  return 'CLOSE_RAW_BLOCK';
+                                 }
 <mu>"{{{{"[^\x00]*"}}}}"         {
                                   yytext = yytext.substr(4, yyleng-8);
                                   this.popState();
diff --git a/src/handlebars.yy b/src/handlebars.yy
index 464d1bab0..51796ec6d 100644
--- a/src/handlebars.yy
+++ b/src/handlebars.yy
@@ -35,7 +35,7 @@ statements
   ;
 
 statement
-  : openRawBlock CONTENT END_RAW_BLOCK -> new yy.RawBlockNode($1, $2, @$)
+  : openRawBlock CONTENT END_RAW_BLOCK -> new yy.RawBlockNode($1, $2, $3, @$)
   | openInverse program closeBlock -> new yy.BlockNode($1, $2.inverse, $2, $3, @$)
   | openBlock program closeBlock -> new yy.BlockNode($1, $2, $2.inverse, $3, @$)
   | mustache -> $1

From a9f76e1475e4b74572bbdf6abc07ba7893bf349e Mon Sep 17 00:00:00 2001
From: kpdecker <kpdecker@gmail.com>
Date: Sun, 9 Feb 2014 17:21:49 -0600
Subject: [PATCH 46/53] jshint changes

---
 spec/builtins.js | 8 +++++---
 1 file changed, 5 insertions(+), 3 deletions(-)

diff --git a/spec/builtins.js b/spec/builtins.js
index 6d9fa0ba4..53f46ccd5 100644
--- a/spec/builtins.js
+++ b/spec/builtins.js
@@ -1,4 +1,4 @@
-/*global CompilerContext, shouldCompileTo, compileWithPartials */
+/*global CompilerContext, shouldCompileTo, compileWithPartials, handlebarsEnv */
 describe('builtin helpers', function() {
   describe('#if', function() {
     it("if", function() {
@@ -189,12 +189,14 @@ describe('builtin helpers', function() {
   });
 
   it("#log", function() {
-
     var string = "{{log blah}}";
     var hash   = { blah: "whee" };
 
     var levelArg, logArg;
-    handlebarsEnv.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 306feb497c72d539ce0f6f926fc8844fb35844fe Mon Sep 17 00:00:00 2001
From: kpdecker <kpdecker@gmail.com>
Date: Sun, 9 Feb 2014 17:22:18 -0600
Subject: [PATCH 47/53] Implement lookup helper

---
 lib/handlebars/base.js              |  4 ++++
 lib/handlebars/compiler/compiler.js |  3 ++-
 spec/builtins.js                    | 21 +++++++++++++++++++++
 3 files changed, 27 insertions(+), 1 deletion(-)

diff --git a/lib/handlebars/base.js b/lib/handlebars/base.js
index 7ebfc5567..02d274d3a 100644
--- a/lib/handlebars/base.js
+++ b/lib/handlebars/base.js
@@ -193,6 +193,10 @@ function registerDefaultHelpers(instance) {
     var level = options.data && options.data.level != null ? parseInt(options.data.level, 10) : 1;
     instance.log(level, context);
   });
+
+  instance.registerHelper('lookup', function(obj, field, options) {
+    return obj && obj[field];
+  });
 }
 
 export var logger = {
diff --git a/lib/handlebars/compiler/compiler.js b/lib/handlebars/compiler/compiler.js
index cfcc70ac5..21e1024f2 100644
--- a/lib/handlebars/compiler/compiler.js
+++ b/lib/handlebars/compiler/compiler.js
@@ -85,7 +85,8 @@ Compiler.prototype = {
       'if': true,
       'unless': true,
       'with': true,
-      'log': true
+      'log': true,
+      'lookup': true
     };
     if (knownHelpers) {
       for (var name in knownHelpers) {
diff --git a/spec/builtins.js b/spec/builtins.js
index 53f46ccd5..bbe494e08 100644
--- a/spec/builtins.js
+++ b/spec/builtins.js
@@ -203,4 +203,25 @@ describe('builtin helpers', function() {
     equals("whee", logArg, "should call log with 'whee'");
   });
 
+
+  describe('#lookup', function() {
+    it('should lookup arbitrary content', function() {
+      var string = '{{#each goodbyes}}{{lookup ../data .}}{{/each}}',
+          hash   = {goodbyes: [0, 1], data: ['foo', 'bar']};
+
+      var template = CompilerContext.compile(string);
+      var result = template(hash);
+
+      equal(result, 'foobar');
+    });
+    it('should not fail on undefined value', function() {
+      var string = '{{#each goodbyes}}{{lookup ../bar .}}{{/each}}',
+          hash   = {goodbyes: [0, 1], data: ['foo', 'bar']};
+
+      var template = CompilerContext.compile(string);
+      var result = template(hash);
+
+      equal(result, '');
+    });
+  });
 });

From 16f135835eb55dcbb7fa26dab63a9c20fd3981ac Mon Sep 17 00:00:00 2001
From: kpdecker <kpdecker@gmail.com>
Date: Sun, 9 Feb 2014 18:35:22 -0600
Subject: [PATCH 48/53] Add support for depthed resolution of data fields

---
 lib/handlebars/base.js                        |  4 ++-
 lib/handlebars/compiler/compiler.js           |  6 +---
 .../compiler/javascript-compiler.js           |  8 +++--
 lib/handlebars/runtime.js                     |  6 ++++
 spec/data.js                                  | 35 ++++++++++---------
 spec/parser.js                                |  4 +++
 6 files changed, 39 insertions(+), 24 deletions(-)

diff --git a/lib/handlebars/base.js b/lib/handlebars/base.js
index 02d274d3a..26eb1a884 100644
--- a/lib/handlebars/base.js
+++ b/lib/handlebars/base.js
@@ -223,5 +223,7 @@ export var logger = {
 export function log(level, obj) { logger.log(level, obj); }
 
 export var createFrame = function(object) {
-  return Utils.extend({}, object);
+  var frame = Utils.extend({}, object);
+  frame._parent = object;
+  return frame;
 };
diff --git a/lib/handlebars/compiler/compiler.js b/lib/handlebars/compiler/compiler.js
index 21e1024f2..be17ac338 100644
--- a/lib/handlebars/compiler/compiler.js
+++ b/lib/handlebars/compiler/compiler.js
@@ -310,11 +310,7 @@ Compiler.prototype = {
 
   DATA: function(data) {
     this.options.data = true;
-    if (data.id.isScoped || data.id.depth) {
-      throw new Exception('Scoped data references are not supported: ' + data.original, data);
-    }
-
-    this.opcode('lookupData');
+    this.opcode('lookupData', data.id.depth);
     var parts = data.id.parts;
     for(var i=0, l=parts.length; i<l; i++) {
       this.opcode('lookup', parts[i]);
diff --git a/lib/handlebars/compiler/javascript-compiler.js b/lib/handlebars/compiler/javascript-compiler.js
index 878181901..769179bd9 100644
--- a/lib/handlebars/compiler/javascript-compiler.js
+++ b/lib/handlebars/compiler/javascript-compiler.js
@@ -403,8 +403,12 @@ JavaScriptCompiler.prototype = {
   // On stack, after: data, ...
   //
   // Push the data lookup operator
-  lookupData: function() {
-    this.pushStackLiteral('data');
+  lookupData: function(depth) {
+    if (!depth) {
+      this.pushStackLiteral('data');
+    } else {
+      this.pushStackLiteral('this.data(data, ' + depth + ')');
+    }
   },
 
   // [pushStringParam]
diff --git a/lib/handlebars/runtime.js b/lib/handlebars/runtime.js
index e65db6e61..9974c713a 100644
--- a/lib/handlebars/runtime.js
+++ b/lib/handlebars/runtime.js
@@ -77,6 +77,12 @@ export function template(templateSpec, env) {
       }
       return data;
     },
+    data: function(data, depth) {
+      while (data && depth--) {
+        data = data._parent;
+      }
+      return data;
+    },
     merge: function(param, common) {
       var ret = param || common;
 
diff --git a/spec/data.js b/spec/data.js
index eab80ef36..bb90df5a8 100644
--- a/spec/data.js
+++ b/spec/data.js
@@ -85,22 +85,6 @@ describe('data', function() {
     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}}";
-
-    shouldThrow(function() {
-      CompilerContext.compile(string);
-    }, Error);
-  });
-
-  it("parameter data throws when using parent scope references", function() {
-    var string = "{{#goodbyes}}{{text}} cruel {{@../name}}! {{/goodbyes}}";
-
-    shouldThrow(function() {
-      CompilerContext.compile(string);
-    }, Error);
-  });
-
   it("parameter data throws when using complex scope references", function() {
     var string = "{{#goodbyes}}{{text}} cruel {{@foo/../name}}! {{/goodbyes}}";
 
@@ -251,4 +235,23 @@ describe('data', function() {
       equals('hello', result);
     });
   });
+
+  describe('nesting', function() {
+    it('the root context can be looked up via @root', function() {
+      var template = CompilerContext.compile('{{#helper}}{{#helper}}{{@./depth}} {{@../depth}} {{@../../depth}}{{/helper}}{{/helper}}');
+      var result = template({foo: 'hello'}, {
+        helpers: {
+          helper: function(options) {
+            var frame = Handlebars.createFrame(options.data);
+            frame.depth = options.data.depth + 1;
+            return options.fn(this, {data: frame});
+          }
+        },
+        data: {
+          depth: 0
+        }
+      });
+      equals('2 1 0', result);
+    });
+  });
 });
diff --git a/spec/parser.js b/spec/parser.js
index 097bb15b7..ebde17196 100644
--- a/spec/parser.js
+++ b/spec/parser.js
@@ -21,6 +21,10 @@ describe('parser', function() {
     equals(ast_for("{{@foo}}"), "{{ @ID:foo [] }}\n");
   });
 
+  it('parses simple mustaches with data paths', function() {
+    equals(ast_for("{{@../foo}}"), "{{ @ID:foo [] }}\n");
+  });
+
   it('parses mustaches with paths', function() {
     equals(ast_for("{{foo/bar}}"), "{{ PATH:foo/bar [] }}\n");
   });

From d00d8b677616ebafce70d93b9149c11a1d78842c Mon Sep 17 00:00:00 2001
From: kpdecker <kpdecker@gmail.com>
Date: Sun, 9 Feb 2014 22:47:40 -0600
Subject: [PATCH 49/53] Better docs on how to file issues

---
 README.markdown | 12 +++++++++++-
 1 file changed, 11 insertions(+), 1 deletion(-)

diff --git a/README.markdown b/README.markdown
index 22dc474e1..1a284c1f0 100644
--- a/README.markdown
+++ b/README.markdown
@@ -352,9 +352,17 @@ See [release-notes.md](https://github.com/wycats/handlebars.js/blob/master/relea
 
 Known Issues
 ------------
+* Runtime/precompiler mismatches: Often result in errors like "can not find method match of object" or similar. Please verify the version of the runtime and the version used to precompile templates if odd issues occur after upgrading one component or another.
 * Handlebars.js can be cryptic when there's an error while rendering.
 * Using a variable, helper, or partial named `class` causes errors in IE browsers. (Instead, use `className`)
 
+Reporting Issues
+----------------
+
+Should you run into other issues with the project, please file an [issue][issue]. When filing issues a repo case running against the latest version of the code is appreciated. A [jsfiddle template][jsfiddle] is available for this purpose. As new versions are released the bitly link will be updated to point to a fiddle template with the latest version.
+
+We also accept [pull requests][pull-request]!
+
 Handlebars in the Wild
 ----------------------
 
@@ -447,7 +455,7 @@ gem build handlebars-source.gemspec
 gem push handlebars-source-*.gem
 ```
 
-After this point the handlebars site needs to be updated to point to the new version numbers.
+After this point the handlebars site needs to be updated to point to the new version numbers. The jsfiddle bitly link should be updated to point to the most recent distribution.
 
 License
 -------
@@ -457,3 +465,5 @@ Handlebars.js is released under the MIT license.
 [builds-page]: http://builds.handlebarsjs.com.s3.amazonaws.com/bucket-listing.html?sort=lastmod&sortdir=desc
 [generator-release]: https://github.com/walmartlabs/generator-release
 [pull-request]: https://github.com/wycats/handlebars.js/pull/new/master
+[issue]: https://github.com/wycats/handlebars.js/issues/new
+[jsfiddle]: http://l.kde.cc/hbs-bug

From c72a8d5e844bf94d048630396b27728826e1b1a1 Mon Sep 17 00:00:00 2001
From: kpdecker <kpdecker@gmail.com>
Date: Sun, 19 Jan 2014 21:18:21 -0600
Subject: [PATCH 50/53] Add child accessor API

---
 lib/handlebars/compiler/compiler.js | 9 ++++++++-
 lib/handlebars/runtime.js           | 7 ++++++-
 2 files changed, 14 insertions(+), 2 deletions(-)

diff --git a/lib/handlebars/compiler/compiler.js b/lib/handlebars/compiler/compiler.js
index be17ac338..7acff1fee 100644
--- a/lib/handlebars/compiler/compiler.js
+++ b/lib/handlebars/compiler/compiler.js
@@ -453,10 +453,17 @@ export function compile(input, options, env) {
   }
 
   // Template is only compiled on first use and cached after that point.
-  return function(context, options) {
+  var ret = function(context, options) {
     if (!compiled) {
       compiled = compileInput();
     }
     return compiled.call(this, context, options);
   };
+  ret.child = function(i) {
+    if (!compiled) {
+      compiled = compileInput();
+    }
+    return compiled.child(i);
+  };
+  return ret;
 }
diff --git a/lib/handlebars/runtime.js b/lib/handlebars/runtime.js
index 9974c713a..b179620c9 100644
--- a/lib/handlebars/runtime.js
+++ b/lib/handlebars/runtime.js
@@ -97,7 +97,7 @@ export function template(templateSpec, env) {
     compilerInfo: templateSpec.compiler
   };
 
-  return function(context, options) {
+  var ret = function(context, options) {
     options = options || {};
     var namespace = options.partial ? options : env,
         helpers,
@@ -119,6 +119,11 @@ export function template(templateSpec, env) {
     }
     return templateSpec.main.call(container, context, helpers, partials, data);
   };
+
+  ret.child = function(i) {
+    return container.programWithDepth(i);
+  };
+  return ret;
 }
 
 export function programWithDepth(i, data /*, $depth */) {

From 6da3c314943d66aeec325b1c1d263ff4ace6406d Mon Sep 17 00:00:00 2001
From: kpdecker <kpdecker@gmail.com>
Date: Mon, 10 Feb 2014 01:48:12 -0600
Subject: [PATCH 51/53] Update compiler revision info

---
 lib/handlebars/base.js     | 5 +++--
 spec/expected/empty.amd.js | 2 +-
 2 files changed, 4 insertions(+), 3 deletions(-)

diff --git a/lib/handlebars/base.js b/lib/handlebars/base.js
index 26eb1a884..c26d658d0 100644
--- a/lib/handlebars/base.js
+++ b/lib/handlebars/base.js
@@ -2,13 +2,14 @@ module Utils from "./utils";
 import Exception from "./exception";
 
 export var VERSION = "1.3.0";
-export var COMPILER_REVISION = 4;
+export var COMPILER_REVISION = 5;
 
 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',
-  4: '>= 1.0.0'
+  4: '== 1.x.x',
+  5: '>= 2.0.0'
 };
 
 var isArray = Utils.isArray,
diff --git a/spec/expected/empty.amd.js b/spec/expected/empty.amd.js
index d80446a1a..e9616c344 100644
--- a/spec/expected/empty.amd.js
+++ b/spec/expected/empty.amd.js
@@ -1,6 +1,6 @@
 define(['handlebars.runtime'], function(Handlebars) {
   Handlebars = Handlebars["default"];  var template = Handlebars.template, templates = Handlebars.templates = Handlebars.templates || {};
-return templates['empty'] = template({"compiler":[4,">= 1.0.0"],"main":function(depth0,helpers,partials,data) {
+return templates['empty'] = template({"compiler":[5,">= 2.0.0"],"main":function(depth0,helpers,partials,data) {
   return "";
 },"useData":true});
 });

From b22a150a375170ab17fddbf0ee47fa7db8686e7e Mon Sep 17 00:00:00 2001
From: kpdecker <kpdecker@gmail.com>
Date: Mon, 10 Feb 2014 02:01:20 -0600
Subject: [PATCH 52/53] Update release notes

---
 release-notes.md | 36 +++++++++++++++++++++++++++++++++++-
 1 file changed, 35 insertions(+), 1 deletion(-)

diff --git a/release-notes.md b/release-notes.md
index 3f0fac995..43f97d709 100644
--- a/release-notes.md
+++ b/release-notes.md
@@ -2,7 +2,41 @@
 
 ## Development
 
-[Commits](https://github.com/wycats/handlebars.js/compare/v1.3.0...master)
+[Commits](https://github.com/wycats/handlebars.js/compare/v2.0.0-alpha.1...master)
+
+## v2.0.0-alpha.1 - February 10th, 2014
+- [#182](https://github.com/wycats/handlebars.js/pull/182) - Allow passing hash parameters to partials ([@kpdecker](https://api.github.com/users/kpdecker))
+- [#392](https://github.com/wycats/handlebars.js/pull/392) - Access to root context in partials and helpers ([@kpdecker](https://api.github.com/users/kpdecker))
+- [#472](https://github.com/wycats/handlebars.js/issues/472) - Helpers cannot have decimal parameters ([@kayleg](https://api.github.com/users/kayleg))
+- [#569](https://github.com/wycats/handlebars.js/pull/569) - Unable to lookup array values using @index ([@kpdecker](https://api.github.com/users/kpdecker))
+- [#491](https://github.com/wycats/handlebars.js/pull/491) - For nested helpers: get the @ variables of the outer helper from the inner one ([@kpdecker](https://api.github.com/users/kpdecker))
+- [#669](https://github.com/wycats/handlebars.js/issues/669) - Ability to unregister a helper ([@dbachrach](https://api.github.com/users/dbachrach))
+- [#730](https://github.com/wycats/handlebars.js/pull/730) - Raw block helpers ([@kpdecker](https://api.github.com/users/kpdecker))
+- [#634](https://github.com/wycats/handlebars.js/pull/634) - It would be great to have the helper name passed to `blockHelperMissing` ([@kpdecker](https://api.github.com/users/kpdecker))
+- [#729](https://github.com/wycats/handlebars.js/pull/729) - Convert template spec to object literal ([@kpdecker](https://api.github.com/users/kpdecker))
+
+- [#658](https://github.com/wycats/handlebars.js/issues/658) - Depthed helpers do not work after an upgrade from 1.0.0 ([@xibxor](https://api.github.com/users/xibxor))
+- [#671](https://github.com/wycats/handlebars.js/issues/671) - Crashes on no-parameter {{#each}} ([@stepancheg](https://api.github.com/users/stepancheg))
+- [#689](https://github.com/wycats/handlebars.js/issues/689) - broken template precompilation ([@AAS](https://api.github.com/users/AAS))
+- [#698](https://github.com/wycats/handlebars.js/pull/698) - Fix parser generation under windows ([@osiris43](https://api.github.com/users/osiris43))
+- [#699](https://github.com/wycats/handlebars.js/issues/699) - @DATA not compiles to invalid JS in stringParams mode ([@kpdecker](https://api.github.com/users/kpdecker))
+- [#705](https://github.com/wycats/handlebars.js/issues/705) - 1.3.0 can not be wrapped in an IIFE ([@craigteegarden](https://api.github.com/users/craigteegarden))
+- [#706](https://github.com/wycats/handlebars.js/pull/706) - README: Use with helper instead of relying on blockHelperMissing ([@scottgonzalez](https://api.github.com/users/scottgonzalez))
+
+- [#700](https://github.com/wycats/handlebars.js/pull/700) - Remove redundant conditions ([@blakeembrey](https://api.github.com/users/blakeembrey))
+- [#704](https://github.com/wycats/handlebars.js/pull/704) - JavaScript Compiler Cleanup ([@blakeembrey](https://api.github.com/users/blakeembrey))
+
+Compatibility notes:
+- `helperMissing` helper no longer has the indexed name argument. Helper name is now available via `options.name`.
+- Precompiler output has changed, which breaks compatibility with prior versions of the runtime and precompiled output.
+- `JavaScriptCompiler.compilerInfo` now returns generic objects rather than javascript source.
+- AST changes
+  - INTEGER -> NUMBER
+  - Additional PartialNode hash parameter
+  - New RawBlockNode type
+- Data frames now have a `_parent` field. This is internal but is enumerable for performance/compatability reasons.
+
+[Commits](https://github.com/wycats/handlebars.js/compare/v1.3.0...v2.0.0-alpha.1)
 
 ## v1.3.0 - January 1st, 2014
 - [#690](https://github.com/wycats/handlebars.js/pull/690) - Added support for subexpressions ([@machty](https://api.github.com/users/machty))

From a5ff1f3d22178b3889983be29b31b850a4528719 Mon Sep 17 00:00:00 2001
From: kpdecker <kpdecker@gmail.com>
Date: Mon, 10 Feb 2014 02:11:07 -0600
Subject: [PATCH 53/53] vv2.0.0-alpha.1

---
 components/bower.json           | 2 +-
 components/handlebars.js.nuspec | 2 +-
 lib/handlebars/base.js          | 2 +-
 package.json                    | 2 +-
 4 files changed, 4 insertions(+), 4 deletions(-)

diff --git a/components/bower.json b/components/bower.json
index 25923a475..571a7b87b 100644
--- a/components/bower.json
+++ b/components/bower.json
@@ -1,6 +1,6 @@
 {
   "name": "handlebars",
-  "version": "1.3.0",
+  "version": "v2.0.0-alpha.1",
   "main": "handlebars.js",
   "dependencies": {}
 }
diff --git a/components/handlebars.js.nuspec b/components/handlebars.js.nuspec
index e4a169103..586a211a2 100644
--- a/components/handlebars.js.nuspec
+++ b/components/handlebars.js.nuspec
@@ -2,7 +2,7 @@
 <package>
 	<metadata>
 		<id>handlebars.js</id>
-		<version>1.3.0</version>
+		<version>v2.0.0-alpha.1</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 c26d658d0..827bcb7c2 100644
--- a/lib/handlebars/base.js
+++ b/lib/handlebars/base.js
@@ -1,7 +1,7 @@
 module Utils from "./utils";
 import Exception from "./exception";
 
-export var VERSION = "1.3.0";
+export var VERSION = "v2.0.0-alpha.1";
 export var COMPILER_REVISION = 5;
 
 export var REVISION_CHANGES = {
diff --git a/package.json b/package.json
index 0ce1d5bdc..9bfc9575b 100644
--- a/package.json
+++ b/package.json
@@ -1,7 +1,7 @@
 {
   "name": "handlebars",
   "barename": "handlebars",
-  "version": "1.3.0",
+  "version": "v2.0.0-alpha.1",
   "description": "Handlebars provides the power necessary to let you build semantic templates effectively with no frustration",
   "homepage": "http://www.handlebarsjs.com/",
   "keywords": [
