diff --git a/README.markdown b/README.markdown
index 0b338e829..1cba64689 100644
--- a/README.markdown
+++ b/README.markdown
@@ -3,20 +3,30 @@
Handlebars.js
=============
-Handlebars.js is an extension to the [Mustache templating language](http://mustache.github.com/) created by Chris Wanstrath. Handlebars.js and Mustache are both logicless templating languages that keep the view and the code separated like we all know they should be.
+Handlebars.js is an extension to the [Mustache templating
+language](http://mustache.github.com/) created by Chris Wanstrath.
+Handlebars.js and Mustache are both logicless templating languages that
+keep the view and the code separated like we all know they should be.
-Checkout the official Handlebars docs site at [http://www.handlebarsjs.com](http://www.handlebarsjs.com).
+Checkout the official Handlebars docs site at
+[http://www.handlebarsjs.com](http://www.handlebarsjs.com).
Installing
----------
-Installing Handlebars is easy. Simply [download the package from GitHub](https://github.com/wycats/handlebars.js/archives/master) and add it to your web pages (you should usually use the most recent version).
+Installing Handlebars is easy. Simply download the package [from the
+official site](http://handlebarsjs.com/) and add it to your web pages
+(you should usually use the most recent version).
Usage
-----
-In general, the syntax of Handlebars.js templates is a superset of Mustache templates. For basic syntax, check out the [Mustache manpage](http://mustache.github.com/mustache.5.html).
+In general, the syntax of Handlebars.js templates is a superset
+of Mustache templates. For basic syntax, check out the [Mustache
+manpage](http://mustache.github.com/mustache.5.html).
-Once you have a template, use the Handlebars.compile method to compile the template into a function. The generated function takes a context argument, which will be used to render the template.
+Once you have a template, use the Handlebars.compile method to compile
+the template into a function. The generated function takes a context
+argument, which will be used to render the template.
```js
var source = "
Hello, my name is {{name}}. I am from {{hometown}}. I have " +
@@ -75,25 +85,34 @@ To explicitly *not* escape the contents, use the triple-mustache
Differences Between Handlebars.js and Mustache
----------------------------------------------
-Handlebars.js adds a couple of additional features to make writing templates easier and also changes a tiny detail of how partials work.
+Handlebars.js adds a couple of additional features to make writing
+templates easier and also changes a tiny detail of how partials work.
### Paths
-Handlebars.js supports an extended expression syntax that we call paths. Paths are made up of typical expressions and . characters. Expressions allow you to not only display data from the current context, but to display data from contexts that are descendents and ancestors of the current context.
+Handlebars.js supports an extended expression syntax that we call paths.
+Paths are made up of typical expressions and . characters. Expressions
+allow you to not only display data from the current context, but to
+display data from contexts that are descendents and ancestors of the
+current context.
-To display data from descendent contexts, use the `.` character. So, for example, if your data were structured like:
+To display data from descendent contexts, use the `.` character. So, for
+example, if your data were structured like:
```js
var data = {"person": { "name": "Alan" }, company: {"name": "Rad, Inc." } };
```
-you could display the person's name from the top-level context with the following expression:
+you could display the person's name from the top-level context with the
+following expression:
```
{{person.name}}
```
-You can backtrack using `../`. For example, if you've already traversed into the person object you could still display the company's name with an expression like `{{../company.name}}`, so:
+You can backtrack using `../`. For example, if you've already traversed
+into the person object you could still display the company's name with
+an expression like `{{../company.name}}`, so:
```
{{#person}}{{name}} - {{../company.name}}{{/person}}
@@ -134,7 +153,9 @@ gets passed to the helper function.
### Block Helpers
-Handlebars.js also adds the ability to define block helpers. Block helpers are functions that can be called from anywhere in the template. Here's an example:
+Handlebars.js also adds the ability to define block helpers. Block
+helpers are functions that can be called from anywhere in the template.
+Here's an example:
```js
var source = "
{{#people}}- {{#link}}{{name}}{{/link}}
{{/people}}
";
@@ -156,7 +177,12 @@ template(data);
//
```
-Whenever the block helper is called it is given two parameters, the argument that is passed to the helper, or the current context if no argument is passed and the compiled contents of the block. Inside of the block helper the value of `this` is the current context, wrapped to include a method named `__get__` that helps translate paths into values within the helpers.
+Whenever the block helper is called it is given two parameters, the
+argument that is passed to the helper, or the current context if no
+argument is passed and the compiled contents of the block. Inside of
+the block helper the value of `this` is the current context, wrapped to
+include a method named `__get__` that helps translate paths into values
+within the helpers.
### Partials
@@ -229,13 +255,15 @@ Options:
-r, --root Template root. Base value that will be stripped from template names. [string]
-If using the precompiler's normal mode, the resulting templates will be stored
-to the `Handlebars.templates` object using the relative template name sans the
-extension. These templates may be executed in the same manner as templates.
+If using the precompiler's normal mode, the resulting templates will be
+stored to the `Handlebars.templates` object using the relative template
+name sans the extension. These templates may be executed in the same
+manner as templates.
-If using the simple mode the precompiler will generate a single javascript method.
-To execute this method it must be passed to the using the `Handlebars.template`
-method and the resulting object may be as normal.
+If using the simple mode the precompiler will generate a single
+javascript method. To execute this method it must be passed to the using
+the `Handlebars.template` method and the resulting object may be as
+normal.
### Optimizations
@@ -252,7 +280,15 @@ method and the resulting object may be as normal.
Performance
-----------
-In a rough performance test, precompiled Handlebars.js templates (in the original version of Handlebars.js) rendered in about half the time of Mustache templates. It would be a shame if it were any other way, since they were precompiled, but the difference in architecture does have some big performance advantages. Justin Marney, a.k.a. [gotascii](http://github.com/gotascii), confirmed that with an [independent test](http://sorescode.com/2010/09/12/benchmarks.html). The rewritten Handlebars (current version) is faster than the old version, and we will have some benchmarks in the near future.
+In a rough performance test, precompiled Handlebars.js templates (in
+the original version of Handlebars.js) rendered in about half the
+time of Mustache templates. It would be a shame if it were any other
+way, since they were precompiled, but the difference in architecture
+does have some big performance advantages. Justin Marney, a.k.a.
+[gotascii](http://github.com/gotascii), confirmed that with an
+[independent test](http://sorescode.com/2010/09/12/benchmarks.html). The
+rewritten Handlebars (current version) is faster than the old version,
+and we will have some benchmarks in the near future.
Building
@@ -288,13 +324,19 @@ Known Issues
Handlebars in the Wild
-----------------
-* [jblotus](http://github.com/jblotus) created [http://tryhandlebarsjs.com](http://tryhandlebarsjs.com) for anyone who would
-like to try out Handlebars.js in their browser.
-* Don Park wrote an Express.js view engine adapter for Handlebars.js called [hbs](http://github.com/donpark/hbs).
-* [sammy.js](http://github.com/quirkey/sammy) by Aaron Quint, a.k.a. quirkey, supports Handlebars.js as one of its template plugins.
-* [SproutCore](http://www.sproutcore.com) uses Handlebars.js as its main templating engine, extending it with automatic data binding support.
-* [Ember.js](http://www.emberjs.com) makes Handlebars.js the primary way to structure your views, also with automatic data binding support.
-* Les Hill (@leshill) wrote a Rails Asset Pipeline gem named [handlebars_assets](http://github.com/leshill/handlebars_assets).
+* [jblotus](http://github.com/jblotus) created [http://tryhandlebarsjs.com](http://tryhandlebarsjs.com)
+ for anyone who would like to try out Handlebars.js in their browser.
+* Don Park wrote an Express.js view engine adapter for Handlebars.js called
+ [hbs](http://github.com/donpark/hbs).
+* [sammy.js](http://github.com/quirkey/sammy) by Aaron Quint, a.k.a. quirkey,
+ supports Handlebars.js as one of its template plugins.
+* [SproutCore](http://www.sproutcore.com) uses Handlebars.js as its main
+ templating engine, extending it with automatic data binding support.
+* [Ember.js](http://www.emberjs.com) makes Handlebars.js the primary way to
+ structure your views, also with automatic data binding support.
+* Les Hill (@leshill) wrote a Rails Asset Pipeline gem named
+ handlebars_assets](http://github.com/leshill/handlebars_assets).
+* [Gist about Synchronous and asynchronous loading of external handlebars templates](https://gist.github.com/2287070)
Helping Out
-----------
@@ -306,12 +348,22 @@ To build Handlebars.js you'll need a few things installed.
* therubyracer, for running tests - `gem install therubyracer`
* rspec, for running tests - `gem install rspec`
-There's a Gemfile in the repo, so you can run `bundle` to install rspec and therubyracer if you've got bundler installed.
+There's a Gemfile in the repo, so you can run `bundle` to install rspec
+and therubyracer if you've got bundler installed.
-To build Handlebars.js from scratch, you'll want to run `rake compile` in the root of the project. That will build Handlebars and output the results to the dist/ folder. To run tests, run `rake spec`. You can also run our set of benchmarks with `rake bench`.
+To build Handlebars.js from scratch, you'll want to run `rake compile`
+in the root of the project. That will build Handlebars and output the
+results to the dist/ folder. To run tests, run `rake spec`. You can also
+run our set of benchmarks with `rake bench`.
-If you notice any problems, please report them to the GitHub issue tracker at [http://github.com/wycats/handlebars.js/issues](http://github.com/wycats/handlebars.js/issues). Feel free to contact commondream or wycats through GitHub with any other questions or feature requests. To submit changes fork the project and send a pull request.
+If you notice any problems, please report
+them to the GitHub issue tracker at
+[http://github.com/wycats/handlebars.js/issues](http://github.com/wycats/handlebars.js/issues).
+Feel free to contact commondream or wycats through GitHub with any other
+questions or feature requests. To submit changes fork the project and
+send a pull request.
License
-------
Handlebars.js is released under the MIT license.
+
diff --git a/Rakefile b/Rakefile
index 6fe5d849e..ac935ddb2 100644
--- a/Rakefile
+++ b/Rakefile
@@ -90,8 +90,24 @@ task :runtime => [:compile] do |task|
Rake::Task["dist/handlebars.runtime.js"].execute
end
+# Updates the various version numbers.
+task :version => [] do |task|
+ # TODO : Pull from package.json when the version numbers are synced
+ version = File.read("lib/handlebars/base.js").match(/Handlebars.VERSION = "(.*)";/)[1]
+
+ content = File.read("bower.json")
+ File.open("bower.json", "w") do |file|
+ file.puts content.gsub(/"version":.*/, "\"version\": \"#{version}\",")
+ end
+
+ content = File.read("handlebars.js.nuspec")
+ File.open("handlebars.js.nuspec", "w") do |file|
+ file.puts content.gsub(/.*<\/version>/, "#{version}")
+ end
+end
+
desc "build the build and runtime version of handlebars"
-task :release => [:build, :runtime]
+task :release => [:version, :build, :runtime]
directory "vendor"
diff --git a/bin/handlebars b/bin/handlebars
index 6d83cef89..d605e74db 100755
--- a/bin/handlebars
+++ b/bin/handlebars
@@ -64,6 +64,12 @@ var optimist = require('optimist')
'type': 'boolean',
'description': 'Include data when compiling',
'alias': 'data'
+ },
+ 'e': {
+ 'type': 'string',
+ 'description': 'Template extension.',
+ 'alias': 'extension',
+ 'default': 'handlebars'
}
})
@@ -109,6 +115,10 @@ if (argv.known) {
}
}
+// Build file extension pattern
+var extension = argv.extension.replace(/[\\^$*+?.():=!|{}\-\[\]]/g, function(arg) { return '\\' + arg; });
+extension = new RegExp('\\.' + extension + '$');
+
var output = [];
if (!argv.simple) {
if (argv.amd) {
@@ -131,7 +141,7 @@ function processTemplate(template, root) {
fs.readdirSync(template).map(function(file) {
var path = template + '/' + file;
- if (/\.handlebars$/.test(path) || fs.statSync(path).isDirectory()) {
+ if (extension.test(path) || fs.statSync(path).isDirectory()) {
processTemplate(path, root || template);
}
});
@@ -153,13 +163,15 @@ function processTemplate(template, root) {
} else if (template.indexOf(root) === 0) {
template = template.substring(root.length+1);
}
- template = template.replace(/\.handlebars$/, '');
+ template = template.replace(extension, '');
if (argv.simple) {
output.push(handlebars.precompile(data, options) + '\n');
} else if (argv.partial) {
+ if(argv.amd && argv._.length == 1){ output.push('return '); }
output.push('Handlebars.partials[\'' + template + '\'] = template(' + handlebars.precompile(data, options) + ');\n');
} else {
+ if(argv.amd && argv._.length == 1){ output.push('return '); }
output.push('templates[\'' + template + '\'] = template(' + handlebars.precompile(data, options) + ');\n');
}
}
@@ -172,6 +184,13 @@ argv._.forEach(function(template) {
// Output the content
if (!argv.simple) {
if (argv.amd) {
+ if(argv._.length > 1){
+ if(argv.partial){
+ output.push('return Handlebars.partials;\n');
+ } else {
+ output.push('return templates;\n');
+ }
+ }
output.push('});');
} else if (!argv.commonjs) {
output.push('})();');
diff --git a/bower.json b/bower.json
new file mode 100644
index 000000000..a5eb00b98
--- /dev/null
+++ b/bower.json
@@ -0,0 +1,9 @@
+{
+ "name": "handlebars.js",
+ "version": "1.0.0-rc.4",
+ "main": "dist/handlebars.js",
+ "ignore": [
+ "node_modules",
+ "components"
+ ]
+}
diff --git a/dist/handlebars.js b/dist/handlebars.js
index 39afa6419..96d86ea81 100644
--- a/dist/handlebars.js
+++ b/dist/handlebars.js
@@ -29,24 +29,38 @@ var Handlebars = {};
;
// lib/handlebars/base.js
-Handlebars.VERSION = "1.0.0-rc.3";
-Handlebars.COMPILER_REVISION = 2;
+Handlebars.VERSION = "1.0.0-rc.4";
+Handlebars.COMPILER_REVISION = 3;
Handlebars.REVISION_CHANGES = {
1: '<= 1.0.rc.2', // 1.0.rc.2 is actually rev2 but doesn't report it
- 2: '>= 1.0.0-rc.3'
+ 2: '== 1.0.0-rc.3',
+ 3: '>= 1.0.0-rc.4'
};
Handlebars.helpers = {};
Handlebars.partials = {};
+var toString = Object.prototype.toString,
+ functionType = '[object Function]',
+ objectType = '[object Object]';
+
Handlebars.registerHelper = function(name, fn, inverse) {
- if(inverse) { fn.not = inverse; }
- this.helpers[name] = fn;
+ if (toString.call(name) === objectType) {
+ if (inverse || fn) { throw new Handlebars.Exception('Arg not supported with multiple helpers'); }
+ Handlebars.Utils.extend(this.helpers, name);
+ } else {
+ if (inverse) { fn.not = inverse; }
+ this.helpers[name] = fn;
+ }
};
Handlebars.registerPartial = function(name, str) {
- this.partials[name] = str;
+ if (toString.call(name) === objectType) {
+ Handlebars.Utils.extend(this.partials, name);
+ } else {
+ this.partials[name] = str;
+ }
};
Handlebars.registerHelper('helperMissing', function(arg) {
@@ -57,8 +71,6 @@ Handlebars.registerHelper('helperMissing', function(arg) {
}
});
-var toString = Object.prototype.toString, functionType = "[object Function]";
-
Handlebars.registerHelper('blockHelperMissing', function(context, options) {
var inverse = options.inverse || function() {}, fn = options.fn;
@@ -156,7 +168,7 @@ Handlebars.registerHelper('unless', function(context, options) {
});
Handlebars.registerHelper('with', function(context, options) {
- return options.fn(context);
+ if (!Handlebars.Utils.isEmpty(context)) return options.fn(context);
});
Handlebars.registerHelper('log', function(context, options) {
@@ -554,84 +566,86 @@ lexer.performAction = function anonymous(yy,yy_,$avoiding_name_collisions,YY_STA
var YYSTATE=YY_START
switch($avoiding_name_collisions) {
-case 0:
+case 0: yy_.yytext = "\\"; return 14;
+break;
+case 1:
if(yy_.yytext.slice(-1) !== "\\") this.begin("mu");
if(yy_.yytext.slice(-1) === "\\") yy_.yytext = yy_.yytext.substr(0,yy_.yyleng-1), this.begin("emu");
if(yy_.yytext) return 14;
break;
-case 1: return 14;
+case 2: return 14;
break;
-case 2:
+case 3:
if(yy_.yytext.slice(-1) !== "\\") this.popState();
if(yy_.yytext.slice(-1) === "\\") yy_.yytext = yy_.yytext.substr(0,yy_.yyleng-1);
return 14;
break;
-case 3: yy_.yytext = yy_.yytext.substr(0, yy_.yyleng-4); this.popState(); return 15;
-break;
-case 4: this.begin("par"); return 24;
+case 4: yy_.yytext = yy_.yytext.substr(0, yy_.yyleng-4); this.popState(); return 15;
break;
-case 5: return 16;
+case 5: this.begin("par"); return 24;
break;
-case 6: return 20;
+case 6: return 16;
break;
-case 7: return 19;
+case 7: return 20;
break;
case 8: return 19;
break;
-case 9: return 23;
+case 9: return 19;
break;
case 10: return 23;
break;
-case 11: this.popState(); this.begin('com');
+case 11: return 23;
break;
-case 12: yy_.yytext = yy_.yytext.substr(3,yy_.yyleng-5); this.popState(); return 15;
+case 12: this.popState(); this.begin('com');
break;
-case 13: return 22;
+case 13: yy_.yytext = yy_.yytext.substr(3,yy_.yyleng-5); this.popState(); return 15;
break;
-case 14: return 36;
+case 14: return 22;
break;
-case 15: return 35;
+case 15: return 36;
break;
case 16: return 35;
break;
-case 17: return 39;
+case 17: return 35;
break;
-case 18: /*ignore whitespace*/
+case 18: return 39;
break;
-case 19: this.popState(); return 18;
+case 19: /*ignore whitespace*/
break;
case 20: this.popState(); return 18;
break;
-case 21: yy_.yytext = yy_.yytext.substr(1,yy_.yyleng-2).replace(/\\"/g,'"'); return 30;
+case 21: this.popState(); return 18;
break;
-case 22: yy_.yytext = yy_.yytext.substr(1,yy_.yyleng-2).replace(/\\'/g,"'"); return 30;
+case 22: yy_.yytext = yy_.yytext.substr(1,yy_.yyleng-2).replace(/\\"/g,'"'); return 30;
break;
-case 23: yy_.yytext = yy_.yytext.substr(1); return 28;
+case 23: yy_.yytext = yy_.yytext.substr(1,yy_.yyleng-2).replace(/\\'/g,"'"); return 30;
break;
-case 24: return 32;
+case 24: yy_.yytext = yy_.yytext.substr(1); return 28;
break;
case 25: return 32;
break;
-case 26: return 31;
+case 26: return 32;
+break;
+case 27: return 31;
break;
-case 27: return 35;
+case 28: return 35;
break;
-case 28: yy_.yytext = yy_.yytext.substr(1, yy_.yyleng-2); return 35;
+case 29: yy_.yytext = yy_.yytext.substr(1, yy_.yyleng-2); return 35;
break;
-case 29: return 'INVALID';
+case 30: return 'INVALID';
break;
-case 30: /*ignore whitespace*/
+case 31: /*ignore whitespace*/
break;
-case 31: this.popState(); return 37;
+case 32: this.popState(); return 37;
break;
-case 32: return 5;
+case 33: return 5;
break;
}
};
-lexer.rules = [/^(?:[^\x00]*?(?=(\{\{)))/,/^(?:[^\x00]+)/,/^(?:[^\x00]{2,}?(?=(\{\{|$)))/,/^(?:[\s\S]*?--\}\})/,/^(?:\{\{>)/,/^(?:\{\{#)/,/^(?:\{\{\/)/,/^(?:\{\{\^)/,/^(?:\{\{\s*else\b)/,/^(?:\{\{\{)/,/^(?:\{\{&)/,/^(?:\{\{!--)/,/^(?:\{\{![\s\S]*?\}\})/,/^(?:\{\{)/,/^(?:=)/,/^(?:\.(?=[} ]))/,/^(?:\.\.)/,/^(?:[\/.])/,/^(?:\s+)/,/^(?:\}\}\})/,/^(?:\}\})/,/^(?:"(\\["]|[^"])*")/,/^(?:'(\\[']|[^'])*')/,/^(?:@[a-zA-Z]+)/,/^(?:true(?=[}\s]))/,/^(?:false(?=[}\s]))/,/^(?:-?[0-9]+(?=[}\s]))/,/^(?:[a-zA-Z0-9_$-]+(?=[=}\s\/.]))/,/^(?:\[[^\]]*\])/,/^(?:.)/,/^(?:\s+)/,/^(?:[a-zA-Z0-9_$-/]+)/,/^(?:$)/];
-lexer.conditions = {"mu":{"rules":[4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,32],"inclusive":false},"emu":{"rules":[2],"inclusive":false},"com":{"rules":[3],"inclusive":false},"par":{"rules":[30,31],"inclusive":false},"INITIAL":{"rules":[0,1,32],"inclusive":true}};
+lexer.rules = [/^(?:\\\\(?=(\{\{)))/,/^(?:[^\x00]*?(?=(\{\{)))/,/^(?:[^\x00]+)/,/^(?:[^\x00]{2,}?(?=(\{\{|$)))/,/^(?:[\s\S]*?--\}\})/,/^(?:\{\{>)/,/^(?:\{\{#)/,/^(?:\{\{\/)/,/^(?:\{\{\^)/,/^(?:\{\{\s*else\b)/,/^(?:\{\{\{)/,/^(?:\{\{&)/,/^(?:\{\{!--)/,/^(?:\{\{![\s\S]*?\}\})/,/^(?:\{\{)/,/^(?:=)/,/^(?:\.(?=[}/ ]))/,/^(?:\.\.)/,/^(?:[\/.])/,/^(?:\s+)/,/^(?:\}\}\})/,/^(?:\}\})/,/^(?:"(\\["]|[^"])*")/,/^(?:'(\\[']|[^'])*')/,/^(?:@[a-zA-Z]+)/,/^(?:true(?=[}\s]))/,/^(?:false(?=[}\s]))/,/^(?:-?[0-9]+(?=[}\s]))/,/^(?:[a-zA-Z0-9_$:\-]+(?=[=}\s\/.]))/,/^(?:\[[^\]]*\])/,/^(?:.)/,/^(?:\s+)/,/^(?:[a-zA-Z0-9_$\-\/]+)/,/^(?:$)/];
+lexer.conditions = {"mu":{"rules":[5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,33],"inclusive":false},"emu":{"rules":[3],"inclusive":false},"com":{"rules":[4],"inclusive":false},"par":{"rules":[31,32],"inclusive":false},"INITIAL":{"rules":[0,1,2,33],"inclusive":true}};
return lexer;})()
parser.lexer = lexer;
function Parser () { this.yy = {}; }Parser.prototype = parser;parser.Parser = Parser;
@@ -815,6 +829,14 @@ var escapeChar = function(chr) {
};
Handlebars.Utils = {
+ extend: function(obj, value) {
+ for(var key in value) {
+ if(value.hasOwnProperty(key)) {
+ obj[key] = value[key];
+ }
+ }
+ },
+
escapeExpression: function(string) {
// don't escape SafeStrings, since they're already safe
if (string instanceof Handlebars.SafeString) {
@@ -823,6 +845,11 @@ Handlebars.Utils = {
return "";
}
+ // Force a string conversion as this will be done by the append regardless and
+ // the regex test will do this transparently behind the scenes, causing issues if
+ // an object's to string has escaped characters in it.
+ string = string.toString();
+
if(!possible.test(string)) { return string; }
return string.replace(badChars, escapeChar);
},
@@ -1023,6 +1050,10 @@ Compiler.prototype = {
val = pair[1];
if (this.options.stringParams) {
+ if(val.depth) {
+ this.addDepth(val.depth);
+ }
+ this.opcode('getContext', val.depth || 0);
this.opcode('pushStringParam', val.stringModeValue, val.type);
} else {
this.accept(val);
@@ -1106,7 +1137,7 @@ Compiler.prototype = {
if (this.options.knownHelpers[name]) {
this.opcode('invokeKnownHelper', params.length, name);
- } else if (this.knownHelpersOnly) {
+ } else if (this.options.knownHelpersOnly) {
throw new Error("You specified knownHelpersOnly, but used the unknown helper " + name);
} else {
this.opcode('invokeHelper', params.length, name);
@@ -1607,16 +1638,18 @@ JavaScriptCompiler.prototype = {
if (this.options.stringParams) {
this.register('hashTypes', '{}');
+ this.register('hashContexts', '{}');
}
},
pushHash: function() {
- this.hash = {values: [], types: []};
+ this.hash = {values: [], types: [], contexts: []};
},
popHash: function() {
var hash = this.hash;
this.hash = undefined;
if (this.options.stringParams) {
+ this.register('hashContexts', '{' + hash.contexts.join(',') + '}');
this.register('hashTypes', '{' + hash.types.join(',') + '}');
}
this.push('{\n ' + hash.values.join(',\n ') + '\n }');
@@ -1759,14 +1792,18 @@ JavaScriptCompiler.prototype = {
// and pushes the hash back onto the stack.
assignToHash: function(key) {
var value = this.popStack(),
+ context,
type;
if (this.options.stringParams) {
type = this.popStack();
- this.popStack();
+ context = this.popStack();
}
var hash = this.hash;
+ if (context) {
+ hash.contexts.push("'" + key + "': " + context);
+ }
if (type) {
hash.types.push("'" + key + "': " + type);
}
@@ -1827,12 +1864,7 @@ JavaScriptCompiler.prototype = {
else { programParams.push("depth" + (depth - 1)); }
}
- if(depths.length === 0) {
- return "self.program(" + programParams.join(", ") + ")";
- } else {
- programParams.shift();
- return "self.programWithDepth(" + programParams.join(", ") + ")";
- }
+ return (depths.length === 0 ? "self.program(" : "self.programWithDepth(") + programParams.join(", ") + ")";
},
register: function(name, val) {
@@ -1964,7 +1996,9 @@ JavaScriptCompiler.prototype = {
.replace(/\\/g, '\\\\')
.replace(/"/g, '\\"')
.replace(/\n/g, '\\n')
- .replace(/\r/g, '\\r') + '"';
+ .replace(/\r/g, '\\r')
+ .replace(/\u2028/g, '\\u2028') // Per Ecma-262 7.3 + 7.8.4
+ .replace(/\u2029/g, '\\u2029') + '"';
},
setupHelper: function(paramSize, name, missingParams) {
@@ -2020,6 +2054,7 @@ JavaScriptCompiler.prototype = {
if (this.options.stringParams) {
options.push("contexts:[" + contexts.join(",") + "]");
options.push("types:[" + types.join(",") + "]");
+ options.push("hashContexts:hashContexts");
options.push("hashTypes:hashTypes");
}
@@ -2070,7 +2105,7 @@ JavaScriptCompiler.isValidJavaScriptVariableName = function(name) {
};
Handlebars.precompile = function(input, options) {
- if (!input || (typeof input !== 'string' && input.constructor !== Handlebars.AST.ProgramNode)) {
+ if (input == null || (typeof input !== 'string' && input.constructor !== Handlebars.AST.ProgramNode)) {
throw new Handlebars.Exception("You must pass a string or Handlebars AST to Handlebars.precompile. You passed " + input);
}
@@ -2084,7 +2119,7 @@ Handlebars.precompile = function(input, options) {
};
Handlebars.compile = function(input, options) {
- if (!input || (typeof input !== 'string' && input.constructor !== Handlebars.AST.ProgramNode)) {
+ if (input == null || (typeof input !== 'string' && input.constructor !== Handlebars.AST.ProgramNode)) {
throw new Handlebars.Exception("You must pass a string or Handlebars AST to Handlebars.compile. You passed " + input);
}
@@ -2122,13 +2157,11 @@ Handlebars.VM = {
program: function(i, fn, data) {
var programWrapper = this.programs[i];
if(data) {
- return Handlebars.VM.program(fn, data);
- } else if(programWrapper) {
- return programWrapper;
- } else {
- programWrapper = this.programs[i] = Handlebars.VM.program(fn);
- return programWrapper;
+ programWrapper = Handlebars.VM.program(i, fn, data);
+ } else if (!programWrapper) {
+ programWrapper = this.programs[i] = Handlebars.VM.program(i, fn);
}
+ return programWrapper;
},
programWithDepth: Handlebars.VM.programWithDepth,
noop: Handlebars.VM.noop,
@@ -2160,21 +2193,27 @@ Handlebars.VM = {
};
},
- programWithDepth: function(fn, data, $depth) {
- var args = Array.prototype.slice.call(arguments, 2);
+ programWithDepth: function(i, fn, data /*, $depth */) {
+ var args = Array.prototype.slice.call(arguments, 3);
- return function(context, options) {
+ var program = function(context, options) {
options = options || {};
return fn.apply(this, [context, options.data || data].concat(args));
};
+ program.program = i;
+ program.depth = args.length;
+ return program;
},
- program: function(fn, data) {
- return function(context, options) {
+ program: function(i, fn, data) {
+ var program = function(context, options) {
options = options || {};
return fn(context, options.data || data);
};
+ program.program = i;
+ program.depth = 0;
+ return program;
},
noop: function() { return ""; },
invokePartial: function(partial, name, context, helpers, partials, data) {
diff --git a/dist/handlebars.runtime.js b/dist/handlebars.runtime.js
index a55b30fd4..7dd40f62e 100644
--- a/dist/handlebars.runtime.js
+++ b/dist/handlebars.runtime.js
@@ -29,24 +29,38 @@ var Handlebars = {};
;
// lib/handlebars/base.js
-Handlebars.VERSION = "1.0.0-rc.3";
-Handlebars.COMPILER_REVISION = 2;
+Handlebars.VERSION = "1.0.0-rc.4";
+Handlebars.COMPILER_REVISION = 3;
Handlebars.REVISION_CHANGES = {
1: '<= 1.0.rc.2', // 1.0.rc.2 is actually rev2 but doesn't report it
- 2: '>= 1.0.0-rc.3'
+ 2: '== 1.0.0-rc.3',
+ 3: '>= 1.0.0-rc.4'
};
Handlebars.helpers = {};
Handlebars.partials = {};
+var toString = Object.prototype.toString,
+ functionType = '[object Function]',
+ objectType = '[object Object]';
+
Handlebars.registerHelper = function(name, fn, inverse) {
- if(inverse) { fn.not = inverse; }
- this.helpers[name] = fn;
+ if (toString.call(name) === objectType) {
+ if (inverse || fn) { throw new Handlebars.Exception('Arg not supported with multiple helpers'); }
+ Handlebars.Utils.extend(this.helpers, name);
+ } else {
+ if (inverse) { fn.not = inverse; }
+ this.helpers[name] = fn;
+ }
};
Handlebars.registerPartial = function(name, str) {
- this.partials[name] = str;
+ if (toString.call(name) === objectType) {
+ Handlebars.Utils.extend(this.partials, name);
+ } else {
+ this.partials[name] = str;
+ }
};
Handlebars.registerHelper('helperMissing', function(arg) {
@@ -57,8 +71,6 @@ Handlebars.registerHelper('helperMissing', function(arg) {
}
});
-var toString = Object.prototype.toString, functionType = "[object Function]";
-
Handlebars.registerHelper('blockHelperMissing', function(context, options) {
var inverse = options.inverse || function() {}, fn = options.fn;
@@ -156,7 +168,7 @@ Handlebars.registerHelper('unless', function(context, options) {
});
Handlebars.registerHelper('with', function(context, options) {
- return options.fn(context);
+ if (!Handlebars.Utils.isEmpty(context)) return options.fn(context);
});
Handlebars.registerHelper('log', function(context, options) {
@@ -203,6 +215,14 @@ var escapeChar = function(chr) {
};
Handlebars.Utils = {
+ extend: function(obj, value) {
+ for(var key in value) {
+ if(value.hasOwnProperty(key)) {
+ obj[key] = value[key];
+ }
+ }
+ },
+
escapeExpression: function(string) {
// don't escape SafeStrings, since they're already safe
if (string instanceof Handlebars.SafeString) {
@@ -211,6 +231,11 @@ Handlebars.Utils = {
return "";
}
+ // Force a string conversion as this will be done by the append regardless and
+ // the regex test will do this transparently behind the scenes, causing issues if
+ // an object's to string has escaped characters in it.
+ string = string.toString();
+
if(!possible.test(string)) { return string; }
return string.replace(badChars, escapeChar);
},
@@ -238,13 +263,11 @@ Handlebars.VM = {
program: function(i, fn, data) {
var programWrapper = this.programs[i];
if(data) {
- return Handlebars.VM.program(fn, data);
- } else if(programWrapper) {
- return programWrapper;
- } else {
- programWrapper = this.programs[i] = Handlebars.VM.program(fn);
- return programWrapper;
+ programWrapper = Handlebars.VM.program(i, fn, data);
+ } else if (!programWrapper) {
+ programWrapper = this.programs[i] = Handlebars.VM.program(i, fn);
}
+ return programWrapper;
},
programWithDepth: Handlebars.VM.programWithDepth,
noop: Handlebars.VM.noop,
@@ -276,21 +299,27 @@ Handlebars.VM = {
};
},
- programWithDepth: function(fn, data, $depth) {
- var args = Array.prototype.slice.call(arguments, 2);
+ programWithDepth: function(i, fn, data /*, $depth */) {
+ var args = Array.prototype.slice.call(arguments, 3);
- return function(context, options) {
+ var program = function(context, options) {
options = options || {};
return fn.apply(this, [context, options.data || data].concat(args));
};
+ program.program = i;
+ program.depth = args.length;
+ return program;
},
- program: function(fn, data) {
- return function(context, options) {
+ program: function(i, fn, data) {
+ var program = function(context, options) {
options = options || {};
return fn(context, options.data || data);
};
+ program.program = i;
+ program.depth = 0;
+ return program;
},
noop: function() { return ""; },
invokePartial: function(partial, name, context, helpers, partials, data) {
diff --git a/handlebars.js.nuspec b/handlebars.js.nuspec
new file mode 100644
index 000000000..9a16cc09d
--- /dev/null
+++ b/handlebars.js.nuspec
@@ -0,0 +1,17 @@
+
+
+
+ handlebars.js
+ 1.0.0-rc.4
+ handlebars.js Authors
+ https://github.com/wycats/handlebars.js/blob/master/LICENSE
+ https://github.com/wycats/handlebars.js/
+ false
+ Extension of the Mustache logicless template language
+
+ handlebars mustache template html
+
+
+
+
+
diff --git a/lib/handlebars/base.js b/lib/handlebars/base.js
index dcbd95889..9f4fdb6eb 100644
--- a/lib/handlebars/base.js
+++ b/lib/handlebars/base.js
@@ -6,24 +6,38 @@ var Handlebars = {};
// BEGIN(BROWSER)
-Handlebars.VERSION = "1.0.0-rc.3";
-Handlebars.COMPILER_REVISION = 2;
+Handlebars.VERSION = "1.0.0-rc.4";
+Handlebars.COMPILER_REVISION = 3;
Handlebars.REVISION_CHANGES = {
1: '<= 1.0.rc.2', // 1.0.rc.2 is actually rev2 but doesn't report it
- 2: '>= 1.0.0-rc.3'
+ 2: '== 1.0.0-rc.3',
+ 3: '>= 1.0.0-rc.4'
};
Handlebars.helpers = {};
Handlebars.partials = {};
+var toString = Object.prototype.toString,
+ functionType = '[object Function]',
+ objectType = '[object Object]';
+
Handlebars.registerHelper = function(name, fn, inverse) {
- if(inverse) { fn.not = inverse; }
- this.helpers[name] = fn;
+ if (toString.call(name) === objectType) {
+ if (inverse || fn) { throw new Handlebars.Exception('Arg not supported with multiple helpers'); }
+ Handlebars.Utils.extend(this.helpers, name);
+ } else {
+ if (inverse) { fn.not = inverse; }
+ this.helpers[name] = fn;
+ }
};
Handlebars.registerPartial = function(name, str) {
- this.partials[name] = str;
+ if (toString.call(name) === objectType) {
+ Handlebars.Utils.extend(this.partials, name);
+ } else {
+ this.partials[name] = str;
+ }
};
Handlebars.registerHelper('helperMissing', function(arg) {
@@ -34,8 +48,6 @@ Handlebars.registerHelper('helperMissing', function(arg) {
}
});
-var toString = Object.prototype.toString, functionType = "[object Function]";
-
Handlebars.registerHelper('blockHelperMissing', function(context, options) {
var inverse = options.inverse || function() {}, fn = options.fn;
@@ -133,7 +145,7 @@ Handlebars.registerHelper('unless', function(context, options) {
});
Handlebars.registerHelper('with', function(context, options) {
- return options.fn(context);
+ if (!Handlebars.Utils.isEmpty(context)) return options.fn(context);
});
Handlebars.registerHelper('log', function(context, options) {
diff --git a/lib/handlebars/compiler/compiler.js b/lib/handlebars/compiler/compiler.js
index 196ba18bb..98f5396e4 100644
--- a/lib/handlebars/compiler/compiler.js
+++ b/lib/handlebars/compiler/compiler.js
@@ -189,6 +189,10 @@ Compiler.prototype = {
val = pair[1];
if (this.options.stringParams) {
+ if(val.depth) {
+ this.addDepth(val.depth);
+ }
+ this.opcode('getContext', val.depth || 0);
this.opcode('pushStringParam', val.stringModeValue, val.type);
} else {
this.accept(val);
@@ -272,7 +276,7 @@ Compiler.prototype = {
if (this.options.knownHelpers[name]) {
this.opcode('invokeKnownHelper', params.length, name);
- } else if (this.knownHelpersOnly) {
+ } else if (this.options.knownHelpersOnly) {
throw new Error("You specified knownHelpersOnly, but used the unknown helper " + name);
} else {
this.opcode('invokeHelper', params.length, name);
@@ -773,16 +777,18 @@ JavaScriptCompiler.prototype = {
if (this.options.stringParams) {
this.register('hashTypes', '{}');
+ this.register('hashContexts', '{}');
}
},
pushHash: function() {
- this.hash = {values: [], types: []};
+ this.hash = {values: [], types: [], contexts: []};
},
popHash: function() {
var hash = this.hash;
this.hash = undefined;
if (this.options.stringParams) {
+ this.register('hashContexts', '{' + hash.contexts.join(',') + '}');
this.register('hashTypes', '{' + hash.types.join(',') + '}');
}
this.push('{\n ' + hash.values.join(',\n ') + '\n }');
@@ -925,14 +931,18 @@ JavaScriptCompiler.prototype = {
// and pushes the hash back onto the stack.
assignToHash: function(key) {
var value = this.popStack(),
+ context,
type;
if (this.options.stringParams) {
type = this.popStack();
- this.popStack();
+ context = this.popStack();
}
var hash = this.hash;
+ if (context) {
+ hash.contexts.push("'" + key + "': " + context);
+ }
if (type) {
hash.types.push("'" + key + "': " + type);
}
@@ -993,12 +1003,7 @@ JavaScriptCompiler.prototype = {
else { programParams.push("depth" + (depth - 1)); }
}
- if(depths.length === 0) {
- return "self.program(" + programParams.join(", ") + ")";
- } else {
- programParams.shift();
- return "self.programWithDepth(" + programParams.join(", ") + ")";
- }
+ return (depths.length === 0 ? "self.program(" : "self.programWithDepth(") + programParams.join(", ") + ")";
},
register: function(name, val) {
@@ -1130,7 +1135,9 @@ JavaScriptCompiler.prototype = {
.replace(/\\/g, '\\\\')
.replace(/"/g, '\\"')
.replace(/\n/g, '\\n')
- .replace(/\r/g, '\\r') + '"';
+ .replace(/\r/g, '\\r')
+ .replace(/\u2028/g, '\\u2028') // Per Ecma-262 7.3 + 7.8.4
+ .replace(/\u2029/g, '\\u2029') + '"';
},
setupHelper: function(paramSize, name, missingParams) {
@@ -1186,6 +1193,7 @@ JavaScriptCompiler.prototype = {
if (this.options.stringParams) {
options.push("contexts:[" + contexts.join(",") + "]");
options.push("types:[" + types.join(",") + "]");
+ options.push("hashContexts:hashContexts");
options.push("hashTypes:hashTypes");
}
@@ -1236,7 +1244,7 @@ JavaScriptCompiler.isValidJavaScriptVariableName = function(name) {
};
Handlebars.precompile = function(input, options) {
- if (!input || (typeof input !== 'string' && input.constructor !== Handlebars.AST.ProgramNode)) {
+ if (input == null || (typeof input !== 'string' && input.constructor !== Handlebars.AST.ProgramNode)) {
throw new Handlebars.Exception("You must pass a string or Handlebars AST to Handlebars.precompile. You passed " + input);
}
@@ -1250,7 +1258,7 @@ Handlebars.precompile = function(input, options) {
};
Handlebars.compile = function(input, options) {
- if (!input || (typeof input !== 'string' && input.constructor !== Handlebars.AST.ProgramNode)) {
+ if (input == null || (typeof input !== 'string' && input.constructor !== Handlebars.AST.ProgramNode)) {
throw new Handlebars.Exception("You must pass a string or Handlebars AST to Handlebars.compile. You passed " + input);
}
diff --git a/lib/handlebars/runtime.js b/lib/handlebars/runtime.js
index 805e10fbd..d99019d9e 100644
--- a/lib/handlebars/runtime.js
+++ b/lib/handlebars/runtime.js
@@ -12,13 +12,11 @@ Handlebars.VM = {
program: function(i, fn, data) {
var programWrapper = this.programs[i];
if(data) {
- return Handlebars.VM.program(fn, data);
- } else if(programWrapper) {
- return programWrapper;
- } else {
- programWrapper = this.programs[i] = Handlebars.VM.program(fn);
- return programWrapper;
+ programWrapper = Handlebars.VM.program(i, fn, data);
+ } else if (!programWrapper) {
+ programWrapper = this.programs[i] = Handlebars.VM.program(i, fn);
}
+ return programWrapper;
},
programWithDepth: Handlebars.VM.programWithDepth,
noop: Handlebars.VM.noop,
@@ -50,21 +48,27 @@ Handlebars.VM = {
};
},
- programWithDepth: function(fn, data, $depth) {
- var args = Array.prototype.slice.call(arguments, 2);
+ programWithDepth: function(i, fn, data /*, $depth */) {
+ var args = Array.prototype.slice.call(arguments, 3);
- return function(context, options) {
+ var program = function(context, options) {
options = options || {};
return fn.apply(this, [context, options.data || data].concat(args));
};
+ program.program = i;
+ program.depth = args.length;
+ return program;
},
- program: function(fn, data) {
- return function(context, options) {
+ program: function(i, fn, data) {
+ var program = function(context, options) {
options = options || {};
return fn(context, options.data || data);
};
+ program.program = i;
+ program.depth = 0;
+ return program;
},
noop: function() { return ""; },
invokePartial: function(partial, name, context, helpers, partials, data) {
diff --git a/lib/handlebars/utils.js b/lib/handlebars/utils.js
index 85c1a9408..1e0e4c902 100644
--- a/lib/handlebars/utils.js
+++ b/lib/handlebars/utils.js
@@ -1,5 +1,7 @@
exports.attach = function(Handlebars) {
+var toString = Object.prototype.toString;
+
// BEGIN(BROWSER)
var errorProps = ['description', 'fileName', 'lineNumber', 'message', 'name', 'number', 'stack'];
@@ -39,6 +41,14 @@ var escapeChar = function(chr) {
};
Handlebars.Utils = {
+ extend: function(obj, value) {
+ for(var key in value) {
+ if(value.hasOwnProperty(key)) {
+ obj[key] = value[key];
+ }
+ }
+ },
+
escapeExpression: function(string) {
// don't escape SafeStrings, since they're already safe
if (string instanceof Handlebars.SafeString) {
@@ -47,6 +57,11 @@ Handlebars.Utils = {
return "";
}
+ // Force a string conversion as this will be done by the append regardless and
+ // the regex test will do this transparently behind the scenes, causing issues if
+ // an object's to string has escaped characters in it.
+ string = string.toString();
+
if(!possible.test(string)) { return string; }
return string.replace(badChars, escapeChar);
},
diff --git a/package.json b/package.json
index 7dda35743..ab3f6831e 100644
--- a/package.json
+++ b/package.json
@@ -1,7 +1,7 @@
{
"name": "handlebars",
"description": "Extension of the Mustache logicless template language",
- "version": "1.0.10",
+ "version": "1.0.11",
"homepage": "http://www.handlebarsjs.com/",
"keywords": [
"handlebars mustache template html"
diff --git a/release-notes.md b/release-notes.md
new file mode 100644
index 000000000..fb409c686
--- /dev/null
+++ b/release-notes.md
@@ -0,0 +1,55 @@
+# Release Notes
+
+## Development
+
+[Commits](https://github.com/wycats/handlebars.js/compare/v1.0.11...master)
+
+## v1.0.11 / 1.0.0-rc4 - May 13 2013
+
+- [#458](https://github.com/wycats/handlebars.js/issues/458) - Fix `./foo` syntax ([@jpfiset](https://github.com/jpfiset))
+- [#460](https://github.com/wycats/handlebars.js/issues/460) - Allow `:` in unescaped identifers ([@jpfiset](https://github.com/jpfiset))
+- [#471](https://github.com/wycats/handlebars.js/issues/471) - Create release notes (These!)
+- [#456](https://github.com/wycats/handlebars.js/issues/456) - Allow escaping of `\\`
+- [#211](https://github.com/wycats/handlebars.js/issues/211) - Fix exception in `escapeExpression`
+- [#375](https://github.com/wycats/handlebars.js/issues/375) - Escape unicode newlines
+- [#461](https://github.com/wycats/handlebars.js/issues/461) - Do not fail when compiling `""`
+- [#302](https://github.com/wycats/handlebars.js/issues/302) - Fix sanity check in knownHelpersOnly mode
+- [#369](https://github.com/wycats/handlebars.js/issues/369) - Allow registration of multiple helpers and partial by passing definition object
+- Add bower package declaration ([@DevinClark](https://github.com/DevinClark))
+- Add NuSpec package declaration ([@MikeMayer](https://github.com/MikeMayer))
+- Handle empty context in `with` ([@thejohnfreeman](https://github.com/thejohnfreeman))
+- Support custom template extensions in CLI ([@matteoagosti](https://github.com/matteoagosti))
+- Fix Rhino support ([@broady](https://github.com/broady))
+- Include contexts in string mode ([@leshill](https://github.com/leshill))
+- Return precompiled scripts when compiling to AMD ([@JamesMaroney](https://github.com/JamesMaroney))
+- Docs updates ([@iangreenleaf](https://github.com/iangreenleaf), [@gilesbowkett](https://github.com/gilesbowkett), [@utkarsh2012](https://github.com/utkarsh2012))
+- Fix `toString` handling under IE and browserify ([@tommydudebreaux](https://github.com/tommydudebreaux))
+- Add program metadata
+
+[Commits](https://github.com/wycats/handlebars.js/compare/v1.0.10...master)
+
+## v1.0.10 - Node - Feb 27 2013
+
+- [#428](https://github.com/wycats/handlebars.js/issues/428) - Fix incorrect rendering of nested programs
+- Fix exception message ([@tricknotes](https://github.com/tricknotes))
+- Added negative number literal support
+- Concert library to single IIFE
+- Add handlebars-source gemspec ([@machty](https://github.com/machty))
+
+[Commits](https://github.com/wycats/handlebars.js/compare/v1.0.9...v1.0.10)
+
+## v1.0.9 - Node - Feb 15 2013
+
+- Added `Handlebars.create` API in node module for sandboxed instances ([@tommydudebreaux](https://github.com/tommydudebreaux))
+
+[Commits](https://github.com/wycats/handlebars.js/compare/1.0.0-rc.3...v1.0.9)
+
+## 1.0.0-rc3 - Browser - Feb 14 2013
+
+- Prevent use of `this` or `..` in illogical place ([@leshill](https://github.com/leshill))
+- Allow AST passing for `parse`/`compile`/`precompile` ([@machty](https://github.com/machty))
+- Optimize generated output by inlining statements where possible
+- Check compiler version when evaluating templates
+- Package browser dist in npm package
+
+[Commits](https://github.com/wycats/handlebars.js/compare/v1.0.8...1.0.0-rc.3)
diff --git a/spec/parser_spec.rb b/spec/parser_spec.rb
index 999702752..293fe1f74 100644
--- a/spec/parser_spec.rb
+++ b/spec/parser_spec.rb
@@ -421,7 +421,7 @@ def path(*parts)
end
it "can pass through an already-compiled AST via compile/precompile" do
- @context = Handlebars::Spec::FULL_CONTEXT
+ @context = Handlebars::Spec::CONTEXT
code = 'Handlebars.compile(new Handlebars.AST.ProgramNode([ new Handlebars.AST.ContentNode("Hello")]))();'
@context.eval(code).should == "Hello"
diff --git a/spec/qunit_spec.js b/spec/qunit_spec.js
index f12e44cd4..e2f7d47d0 100644
--- a/spec/qunit_spec.js
+++ b/spec/qunit_spec.js
@@ -92,6 +92,12 @@ test("most basic", function() {
shouldCompileTo("{{foo}}", { foo: "foo" }, "foo");
});
+test("escaping", function() {
+ shouldCompileTo("\\{{foo}}", { foo: "food" }, "{{foo}}");
+ shouldCompileTo("\\\\{{foo}}", { foo: "food" }, "\\food");
+ shouldCompileTo("\\\\ {{foo}}", { foo: "food" }, "\\\\ food");
+});
+
test("compiling with a basic context", function() {
shouldCompileTo("Goodbye\n{{cruel}}\n{{world}}!", {cruel: "cruel", world: "world"}, "Goodbye\ncruel\nworld!",
"It works if all the required keys are provided");
@@ -486,6 +492,27 @@ test("the helpers hash is available is nested contexts", function() {
"helpers hash is available in nested contexts.");
});
+test("Multiple global helper registration", function() {
+ var helpers = Handlebars.helpers;
+ try {
+ Handlebars.helpers = {};
+ Handlebars.registerHelper({
+ 'if': helpers['if'],
+ world: function() { return "world!"; },
+ test_helper: function() { return 'found it!'; }
+ });
+
+ shouldCompileTo(
+ "{{test_helper}} {{#if cruel}}Goodbye {{cruel}} {{world}}!{{/if}}",
+ [{cruel: "cruel"}],
+ "found it! Goodbye cruel world!!");
+ } finally {
+ if (helpers) {
+ Handlebars.helpers = helpers;
+ }
+ }
+});
+
suite("partials");
test("basic partials", function() {
@@ -550,6 +577,17 @@ test("Partials with slash paths", function() {
shouldCompileToWithPartials(string, [hash, {}, {'shared/dude':dude}], true, "Dudes: Jeepers", "Partials can use literal paths");
});
+test("Multiple partial registration", function() {
+ Handlebars.registerPartial({
+ 'shared/dude': '{{name}}',
+ global_test: '{{another_dude}}'
+ });
+
+ var string = "Dudes: {{> shared/dude}} {{> global_test}}";
+ var hash = {name:"Jeepers", another_dude:"Creepers"};
+ shouldCompileToWithPartials(string, [hash], true, "Dudes: Jeepers Creepers", "Partials can use globals or passed");
+});
+
test("Partials with integer path", function() {
var string = "Dudes: {{> 404}}";
var dude = "{{name}}";
@@ -703,6 +741,11 @@ test("Functions are bound to the context in knownHelpers only mode", function()
var result = template({foo: function() { return this.bar; }, bar: 'bar'});
equal(result, "bar", "'bar' should === '" + result);
});
+test("Unknown helper call in knownHelpers only mode should throw", function() {
+ shouldThrow(function() {
+ CompilerContext.compile("{{typeof hello}}", {knownHelpersOnly: true});
+ }, Error, 'specified knownHelpersOnly');
+});
suite("blockHelperMissing");
@@ -1286,6 +1329,32 @@ test("in string mode, hash parameters get type information", function() {
equal(result, "Helper called");
});
+test("in string mode, hash parameters get context information", function() {
+ var template = CompilerContext.compile('{{#with dale}}{{tomdale he.says desire="need" noun=../dad/joke bool=true}}{{/with}}', { stringParams: true });
+
+ var context = {dale: {}};
+
+ var helpers = {
+ tomdale: function(exclamation, options) {
+ equal(exclamation, "he.says");
+ equal(options.types[0], "ID");
+
+ equal(options.contexts.length, 1);
+ equal(options.hashContexts.noun, context);
+ equal(options.hash.desire, "need");
+ equal(options.hash.noun, "dad.joke");
+ equal(options.hash.bool, true);
+ return "Helper called";
+ },
+ "with": function(context, options) {
+ return options.fn(options.contexts[0][context]);
+ }
+ };
+
+ var result = template(context, { helpers: helpers });
+ equal(result, "Helper called");
+});
+
test("when inside a block in String mode, .. passes the appropriate context in the options hash to a block helper", function() {
var template = CompilerContext.compile('{{#with dale}}{{#tomdale ../need dad.joke}}wot{{/tomdale}}{{/with}}', {stringParams: true});
@@ -1397,3 +1466,39 @@ test('GS-428: Nested if else rendering', function() {
shouldCompileTo(succeedingTemplate, [{}, helpers], ' Expected ');
shouldCompileTo(failingTemplate, [{}, helpers], ' Expected ');
});
+
+test('GH-458: Scoped this identifier', function() {
+ shouldCompileTo('{{./foo}}', {foo: 'bar'}, 'bar');
+});
+
+test('GH-375: Unicode line terminators', function() {
+ shouldCompileTo('\u2028', {}, '\u2028');
+});
+
+suite('Utils');
+
+test('escapeExpression', function() {
+ equal(Handlebars.Utils.escapeExpression('foo<&"\'>'), 'foo<&"'>');
+ equal(Handlebars.Utils.escapeExpression(new Handlebars.SafeString('foo<&"\'>')), 'foo<&"\'>');
+ equal(Handlebars.Utils.escapeExpression(''), '');
+ equal(Handlebars.Utils.escapeExpression(undefined), '');
+ equal(Handlebars.Utils.escapeExpression(null), '');
+ equal(Handlebars.Utils.escapeExpression(false), '');
+
+ equal(Handlebars.Utils.escapeExpression(0), '0');
+ equal(Handlebars.Utils.escapeExpression({}), {}.toString());
+ equal(Handlebars.Utils.escapeExpression([]), [].toString());
+});
+
+test('isEmpty', function() {
+ equal(Handlebars.Utils.isEmpty(undefined), true);
+ equal(Handlebars.Utils.isEmpty(null), true);
+ equal(Handlebars.Utils.isEmpty(false), true);
+ equal(Handlebars.Utils.isEmpty(''), true);
+ equal(Handlebars.Utils.isEmpty([]), true);
+
+ equal(Handlebars.Utils.isEmpty(0), false);
+ equal(Handlebars.Utils.isEmpty([1]), false);
+ equal(Handlebars.Utils.isEmpty('foo'), false);
+ equal(Handlebars.Utils.isEmpty({bar: 1}), false);
+});
diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb
index e79415ad2..eb2f26afd 100644
--- a/spec/spec_helper.rb
+++ b/spec/spec_helper.rb
@@ -87,7 +87,7 @@ def self.js_load(context, file)
end
CompilerContext["compileWithPartial"] = proc do |this, *args|
template, options = args[0], args[1] || nil
- FULL_CONTEXT["Handlebars"]["compile"].call(template, options);
+ context["Handlebars"]["compile"].call(template, options);
end
end
@@ -116,23 +116,6 @@ def self.js_load(context, file)
end
end
end
-
- FULL_CONTEXT = V8::Context.new
- FULL_CONTEXT.instance_eval do |context|
- Handlebars::Spec.load_helpers(context);
-
- Handlebars::Spec.js_load(context, 'dist/handlebars.js');
-
- context["Handlebars"]["logger"]["level"] = ENV["DEBUG_JS"] ? context["Handlebars"]["logger"][ENV["DEBUG_JS"]] : 4
-
- context["Handlebars"]["logger"]["log"] = proc do |this, level, str|
- logger_level = context["Handlebars"]["logger"]["level"].to_i
-
- if logger_level <= level
- puts str
- end
- end
- end
end
end
diff --git a/src/handlebars.l b/src/handlebars.l
index b32e39c64..8a17a4e28 100644
--- a/src/handlebars.l
+++ b/src/handlebars.l
@@ -3,6 +3,7 @@
%%
+"\\\\"/("{{") { yytext = "\\"; return 'CONTENT'; }
[^\x00]*?/("{{") {
if(yytext.slice(-1) !== "\\") this.begin("mu");
if(yytext.slice(-1) === "\\") yytext = yytext.substr(0,yyleng-1), this.begin("emu");
@@ -31,7 +32,7 @@
"{{" { return 'OPEN'; }
"=" { return 'EQUALS'; }
-"."/[} ] { return 'ID'; }
+"."/[}/ ] { return 'ID'; }
".." { return 'ID'; }
[\/.] { return 'SEP'; }
\s+ { /*ignore whitespace*/ }
@@ -43,11 +44,11 @@
"true"/[}\s] { return 'BOOLEAN'; }
"false"/[}\s] { return 'BOOLEAN'; }
\-?[0-9]+/[}\s] { return 'INTEGER'; }
-[a-zA-Z0-9_$-]+/[=}\s\/.] { return 'ID'; }
+[a-zA-Z0-9_$:\-]+/[=}\s\/.] { return 'ID'; }
'['[^\]]*']' { yytext = yytext.substr(1, yyleng-2); return 'ID'; }
. { return 'INVALID'; }
\s+ { /*ignore whitespace*/ }
-[a-zA-Z0-9_$-/]+ { this.popState(); return 'PARTIAL_NAME'; }
+[a-zA-Z0-9_$\-\/]+ { this.popState(); return 'PARTIAL_NAME'; }
<> { return 'EOF'; }