Compare commits
139 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| b74711e63b | |||
| e3454a0187 | |||
| f72cbed479 | |||
| bc8214cb7c | |||
| 7f9e3fea81 | |||
| 1ca7462497 | |||
| 514c9391e0 | |||
| 7c633b604d | |||
| 51e9259fac | |||
| 0844d74fbe | |||
| a48e32bbf6 | |||
| 448e43c41e | |||
| 6fe7f17c89 | |||
| edb6f11b42 | |||
| d7758fe1a9 | |||
| a3376e24b1 | |||
| 6a51198a9b | |||
| a68a5ad5ba | |||
| 6ab92eee6d | |||
| b0c9f3d83b | |||
| b661977966 | |||
| 969b418291 | |||
| 8e9688792b | |||
| 9ac47df7dd | |||
| 6210d0b040 | |||
| 69d46e008b | |||
| f8a346b3ce | |||
| 0f6651bad9 | |||
| 231543feaf | |||
| b3a39ccefd | |||
| 956b0a62a6 | |||
| 94e70043f9 | |||
| bf201a66c2 | |||
| 39cc7c6816 | |||
| e9ac494ffd | |||
| cb50caaf53 | |||
| ae95407775 | |||
| 7277284132 | |||
| d486e7fa47 | |||
| 3c20641a2e | |||
| 5e20c809dc | |||
| 2b875cfb33 | |||
| 228c75669f | |||
| d1fdb82291 | |||
| 247babbe1a | |||
| b1ea697b7d | |||
| 822d98a1ef | |||
| 5ac29dcb50 | |||
| 9453c6d9d2 | |||
| 0c4333ae81 | |||
| ce82bef6f1 | |||
| cd9895061b | |||
| ccd6a22ea5 | |||
| 5e5f0dce9c | |||
| f5079765ff | |||
| 24fc3a2288 | |||
| 8961298859 | |||
| 4f54f62abc | |||
| eaa7e97461 | |||
| 59e30ecf37 | |||
| 3396814797 | |||
| fb9a7aa533 | |||
| 72f33c4615 | |||
| 16f2bf8f9b | |||
| 5f56d6582f | |||
| 76882ff75a | |||
| 3cac2672d0 | |||
| 7a3641df5a | |||
| b3f954b0af | |||
| f671c2e3d7 | |||
| 118594f663 | |||
| f4cad086bb | |||
| 51fd64c334 | |||
| 0ffb2a9681 | |||
| ca22ca57e1 | |||
| e9fe73d7ab | |||
| 28f377d169 | |||
| 02d35262f9 | |||
| 8df8488947 | |||
| 17ee7ffaca | |||
| bf4c813db0 | |||
| 070e12f76f | |||
| 9385666e08 | |||
| dfaf698b44 | |||
| 12f160fb31 | |||
| b58c2dd0ad | |||
| 4bb794d814 | |||
| 7ce13a3c2c | |||
| 19bfbe07f8 | |||
| fd0560b951 | |||
| 4cfda479fa | |||
| eabcc8a002 | |||
| cbfe84f1a9 | |||
| db975b42a0 | |||
| 7963218495 | |||
| bd04901454 | |||
| c3f92c402e | |||
| 39832c0633 | |||
| 2676efb222 | |||
| a927a9b0ad | |||
| 8ed7a1e4e6 | |||
| d89e4b3d33 | |||
| 9589ab8949 | |||
| 3212325173 | |||
| 850304f43b | |||
| ed245c6c64 | |||
| 1018d9bfd1 | |||
| a1bfc80577 | |||
| 1b197be7fc | |||
| 9e582fa0eb | |||
| a1c9acb8b1 | |||
| 42120d1177 | |||
| b5074a88ec | |||
| ce74c36118 | |||
| 26b1438f20 | |||
| b83e5e0ea6 | |||
| f6aea819f3 | |||
| 647ee28ca1 | |||
| d117b50fd3 | |||
| ed8c2b95b8 | |||
| aba2269ddb | |||
| 967c69b2da | |||
| 5a6e4f1ddd | |||
| f55ca6c489 | |||
| 4b73b51e12 | |||
| eccc7c35ca | |||
| 7c4813b417 | |||
| acc04c2826 | |||
| 8e9bd6742f | |||
| 89f5ab8aaf | |||
| dc0426d836 | |||
| 6761d4c6d1 | |||
| 3426969221 | |||
| 9ce3032678 | |||
| 4cf0869330 | |||
| 201917a1be | |||
| 2b3e777340 | |||
| 075076975d | |||
| bd9a84a0b7 |
+1
-1
@@ -1,4 +1,3 @@
|
||||
dist
|
||||
vendor
|
||||
.rvmrc
|
||||
.DS_Store
|
||||
@@ -6,3 +5,4 @@ lib/handlebars/compiler/parser.js
|
||||
node_modules
|
||||
*.sublime-project
|
||||
*.sublime-workspace
|
||||
npm-debug.log
|
||||
|
||||
@@ -5,7 +5,6 @@ Gemfile
|
||||
Gemfile.lock
|
||||
Rakefile
|
||||
bench/*
|
||||
dist/*
|
||||
spec/*
|
||||
src/*
|
||||
vendor/*
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
source "http://rubygems.org"
|
||||
|
||||
gem "rake"
|
||||
gem "therubyracer", ">= 0.9.8"
|
||||
gem "therubyracer", ">= 0.9.8", "< 0.11"
|
||||
gem "rspec"
|
||||
|
||||
+10
-10
@@ -3,16 +3,16 @@ GEM
|
||||
specs:
|
||||
diff-lcs (1.1.3)
|
||||
libv8 (3.3.10.4)
|
||||
rake (0.9.2.2)
|
||||
rspec (2.11.0)
|
||||
rspec-core (~> 2.11.0)
|
||||
rspec-expectations (~> 2.11.0)
|
||||
rspec-mocks (~> 2.11.0)
|
||||
rspec-core (2.11.0)
|
||||
rspec-expectations (2.11.1)
|
||||
rake (10.0.3)
|
||||
rspec (2.12.0)
|
||||
rspec-core (~> 2.12.0)
|
||||
rspec-expectations (~> 2.12.0)
|
||||
rspec-mocks (~> 2.12.0)
|
||||
rspec-core (2.12.2)
|
||||
rspec-expectations (2.12.1)
|
||||
diff-lcs (~> 1.1.3)
|
||||
rspec-mocks (2.11.1)
|
||||
therubyracer (0.10.1)
|
||||
rspec-mocks (2.12.1)
|
||||
therubyracer (0.10.2)
|
||||
libv8 (~> 3.3.10)
|
||||
|
||||
PLATFORMS
|
||||
@@ -21,4 +21,4 @@ PLATFORMS
|
||||
DEPENDENCIES
|
||||
rake
|
||||
rspec
|
||||
therubyracer (>= 0.9.8)
|
||||
therubyracer (>= 0.9.8, < 0.11)
|
||||
|
||||
@@ -17,4 +17,3 @@ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
|
||||
|
||||
+7
-6
@@ -19,7 +19,7 @@ In general, the syntax of Handlebars.js templates is a superset of Mustache temp
|
||||
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 = "<p>Hello, my name is {{name}}. I am from {{hometown}}. I have " +
|
||||
var source = "<p>Hello, my name is {{name}}. I am from {{hometown}}. I have " +
|
||||
"{{kids.length}} kids:</p>" +
|
||||
"<ul>{{#kids}}<li>{{name}} is {{age}}</li>{{/kids}}</ul>";
|
||||
var template = Handlebars.compile(source);
|
||||
@@ -112,7 +112,7 @@ instance:
|
||||
|
||||
```js
|
||||
Handlebars.registerHelper('link_to', function(title, context) {
|
||||
return "<a href='/posts" + context.id + "'>" + title + "</a>"
|
||||
return "<a href='/posts" + context.url + "'>" + title + "!</a>"
|
||||
});
|
||||
|
||||
var context = { posts: [{url: "/hello-world", body: "Hello World!"}] };
|
||||
@@ -124,7 +124,7 @@ template(context);
|
||||
// Would render:
|
||||
//
|
||||
// <ul>
|
||||
// <li><a href='/hello-world'>Post!</a></li>
|
||||
// <li><a href='/posts/hello-world'>Post!</a></li>
|
||||
// </ul>
|
||||
```
|
||||
|
||||
@@ -137,9 +137,9 @@ gets passed to the helper function.
|
||||
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 = "<ul>{{#people}}<li>{{{#link}}}{{name}}{{/link}}</li>{{/people}}</ul>";
|
||||
Handlebars.registerHelper('link', function(context, fn) {
|
||||
return '<a href="/people/' + this.__get__("id") + '">' + fn(this) + '</a>';
|
||||
var source = "<ul>{{#people}}<li>{{#link}}{{name}}{{/link}}</li>{{/people}}</ul>";
|
||||
Handlebars.registerHelper('link', function(options) {
|
||||
return '<a href="/people/' + this.id + '">' + options.fn(this) + '</a>';
|
||||
});
|
||||
var template = Handlebars.compile(source);
|
||||
|
||||
@@ -220,6 +220,7 @@ Precompile handlebar templates.
|
||||
Usage: handlebars template...
|
||||
|
||||
Options:
|
||||
-a, --amd Create an AMD format function (allows loading with RequireJS) [boolean]
|
||||
-f, --output Output File [string]
|
||||
-k, --known Known helpers [string]
|
||||
-o, --knownOnly Known helpers only [boolean]
|
||||
|
||||
@@ -2,10 +2,10 @@ require "rubygems"
|
||||
require "bundler/setup"
|
||||
|
||||
def compile_parser
|
||||
system "jison src/handlebars.yy src/handlebars.l"
|
||||
system "./node_modules/.bin/jison -m js src/handlebars.yy src/handlebars.l"
|
||||
if $?.success?
|
||||
File.open("lib/handlebars/compiler/parser.js", "w") do |file|
|
||||
file.puts File.read("handlebars.js") + ";"
|
||||
file.puts File.read("src/parser-prefix.js") + File.read("handlebars.js") + File.read("src/parser-suffix.js")
|
||||
end
|
||||
|
||||
sh "rm handlebars.js"
|
||||
@@ -15,11 +15,11 @@ def compile_parser
|
||||
end
|
||||
|
||||
file "lib/handlebars/compiler/parser.js" => ["src/handlebars.yy","src/handlebars.l"] do
|
||||
if ENV['PATH'].split(':').any? {|folder| File.exists?(folder+'/jison')}
|
||||
if File.exists?('./node_modules/jison')
|
||||
compile_parser
|
||||
else
|
||||
puts "Jison is not installed. Trying `npm install jison`."
|
||||
sh "npm install jison -g"
|
||||
sh "npm install"
|
||||
compile_parser
|
||||
end
|
||||
end
|
||||
@@ -28,10 +28,17 @@ task :compile => "lib/handlebars/compiler/parser.js"
|
||||
|
||||
desc "run the spec suite"
|
||||
task :spec => [:release] do
|
||||
system "rspec -cfs spec"
|
||||
rc = system "rspec -cfs spec"
|
||||
fail "rspec spec failed with exit code #{$?.exitstatus}" if (rc.nil? || ! rc || $?.exitstatus != 0)
|
||||
end
|
||||
|
||||
task :default => [:compile, :spec]
|
||||
desc "run the npm test suite"
|
||||
task :npm_test => [:release] do
|
||||
rc = system "npm test"
|
||||
fail "npm test failed with exit code #{$?.exitstatus}" if (rc.nil? || ! rc || $?.exitstatus != 0)
|
||||
end
|
||||
|
||||
task :default => [:compile, :spec, :npm_test]
|
||||
|
||||
def remove_exports(string)
|
||||
match = string.match(%r{^// BEGIN\(BROWSER\)\n(.*)\n^// END\(BROWSER\)}m)
|
||||
@@ -54,7 +61,7 @@ def build_for_task(task)
|
||||
FileUtils.rm_rf("dist/*") if File.directory?("dist")
|
||||
FileUtils.mkdir_p("dist")
|
||||
|
||||
contents = []
|
||||
contents = ["/*\n\n" + File.read('LICENSE') + "\n*/\n"]
|
||||
task.prerequisites.each do |filename|
|
||||
next if filename == "dist"
|
||||
|
||||
@@ -85,20 +92,6 @@ directory "vendor"
|
||||
desc "benchmark against dust.js and mustache.js"
|
||||
task :bench => "vendor" do
|
||||
require "open-uri"
|
||||
#File.open("vendor/mustache.js", "w") do |file|
|
||||
#file.puts open("https://github.com/janl/mustache.js/raw/master/mustache.js").read
|
||||
#file.puts "module.exports = Mustache;"
|
||||
#end
|
||||
|
||||
File.open("vendor/benchmark.js", "w") do |file|
|
||||
file.puts open("https://raw.github.com/bestiejs/benchmark.js/master/benchmark.js").read
|
||||
end
|
||||
|
||||
#if File.directory?("vendor/dustjs")
|
||||
#system "cd vendor/dustjs && git pull"
|
||||
#else
|
||||
#system "git clone git://github.com/akdubya/dustjs.git vendor/dustjs"
|
||||
#end
|
||||
|
||||
#if File.directory?("vendor/coffee")
|
||||
#system "cd vendor/coffee && git pull"
|
||||
|
||||
@@ -133,7 +133,7 @@ BenchWarmer.prototype = {
|
||||
|
||||
if(!bench.error) {
|
||||
var count = bench.hz,
|
||||
moe = count * bench.stats.RME / 100;
|
||||
moe = count * bench.stats.rme / 100;
|
||||
|
||||
out = Math.round(count / 1000) + " ±" + Math.round(moe / 1000) + " (" + bench.cycles + ")";
|
||||
} else {
|
||||
|
||||
+43
-34
@@ -1,20 +1,22 @@
|
||||
require.paths.push("lib");
|
||||
require.paths.push("vendor");
|
||||
require.paths.push("vendor/dustjs/lib");
|
||||
require.paths.push("vendor/coffee/lib");
|
||||
require.paths.push("vendor/eco/lib");
|
||||
|
||||
|
||||
var BenchWarmer = require("./benchwarmer");
|
||||
Handlebars = require("handlebars");
|
||||
Handlebars = require("../lib/handlebars");
|
||||
|
||||
var dust = require("dust");
|
||||
var Mustache = require("mustache");
|
||||
var ecoExports = require("eco");
|
||||
var dust, Mustache, eco;
|
||||
|
||||
eco = function(str) {
|
||||
return ecoExports(str);
|
||||
}
|
||||
try {
|
||||
dust = require("dust");
|
||||
} catch (err) { /* NOP */ }
|
||||
|
||||
try {
|
||||
Mustache = require("mustache");
|
||||
} catch (err) { /* NOP */ }
|
||||
|
||||
try {
|
||||
var ecoExports = require("eco");
|
||||
eco = function(str) {
|
||||
return ecoExports(str);
|
||||
}
|
||||
} catch (err) { /* NOP */ }
|
||||
|
||||
var benchDetails = {
|
||||
string: {
|
||||
@@ -113,39 +115,46 @@ var makeSuite = function(name) {
|
||||
|
||||
var error = function() { throw new Error("EWOT"); };
|
||||
|
||||
|
||||
//bench("dust", function() {
|
||||
//dust.render(templateName, context, function(err, out) { });
|
||||
//});
|
||||
if (dust) {
|
||||
bench("dust", function() {
|
||||
dust.render(templateName, context, function(err, out) { });
|
||||
});
|
||||
}
|
||||
|
||||
bench("handlebars", function() {
|
||||
handlebarsTemplates[templateName](context);
|
||||
});
|
||||
|
||||
//if(ecoTemplates[templateName]) {
|
||||
//bench("eco", function() {
|
||||
//ecoTemplates[templateName](context);
|
||||
//});
|
||||
//} else {
|
||||
//bench("eco", error);
|
||||
//}
|
||||
if (eco) {
|
||||
if(ecoTemplates[templateName]) {
|
||||
bench("eco", function() {
|
||||
ecoTemplates[templateName](context);
|
||||
});
|
||||
} else {
|
||||
bench("eco", error);
|
||||
}
|
||||
}
|
||||
|
||||
//if(mustacheSource) {
|
||||
//bench("mustache", function() {
|
||||
//Mustache.to_html(mustacheSource, context, mustachePartials);
|
||||
//});
|
||||
//} else {
|
||||
//bench("mustache", error);
|
||||
//}
|
||||
if (Mustache && mustacheSource) {
|
||||
bench("mustache", function() {
|
||||
Mustache.to_html(mustacheSource, context, mustachePartials);
|
||||
});
|
||||
} else {
|
||||
bench("mustache", error);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
for(var name in benchDetails) {
|
||||
if(benchDetails.hasOwnProperty(name)) {
|
||||
dust.loadSource(dust.compile(benchDetails[name].dust, name));
|
||||
if (dust) {
|
||||
dust.loadSource(dust.compile(benchDetails[name].dust, name));
|
||||
}
|
||||
handlebarsTemplates[name] = Handlebars.compile(benchDetails[name].handlebars);
|
||||
|
||||
if(benchDetails[name].eco) { ecoTemplates[name] = eco(benchDetails[name].eco); }
|
||||
if (eco && benchDetails[name].eco) {
|
||||
ecoTemplates[name] = eco(benchDetails[name].eco);
|
||||
}
|
||||
|
||||
var partials = benchDetails[name].partials;
|
||||
if(partials) {
|
||||
|
||||
+56
-2
@@ -7,6 +7,23 @@ var optimist = require('optimist')
|
||||
'description': 'Output File',
|
||||
'alias': 'output'
|
||||
},
|
||||
'a': {
|
||||
'type': 'boolean',
|
||||
'description': 'Exports amd style (require.js)',
|
||||
'alias': 'amd'
|
||||
},
|
||||
'c': {
|
||||
'type': 'string',
|
||||
'description': 'Exports CommonJS style, path to Handlebars module',
|
||||
'alias': 'commonjs',
|
||||
'default': null
|
||||
},
|
||||
'h': {
|
||||
'type': 'string',
|
||||
'description': 'Path to handlebar.js (only valid for amd-style)',
|
||||
'alias': 'handlebarPath',
|
||||
'default': ''
|
||||
},
|
||||
'k': {
|
||||
'type': 'string',
|
||||
'description': 'Known helpers',
|
||||
@@ -22,6 +39,12 @@ var optimist = require('optimist')
|
||||
'description': 'Minimize output',
|
||||
'alias': 'min'
|
||||
},
|
||||
'n': {
|
||||
'type': 'string',
|
||||
'description': 'Template namespace',
|
||||
'alias': 'namespace',
|
||||
'default': 'Handlebars.templates'
|
||||
},
|
||||
's': {
|
||||
'type': 'boolean',
|
||||
'description': 'Output template function only.',
|
||||
@@ -31,6 +54,16 @@ var optimist = require('optimist')
|
||||
'type': 'string',
|
||||
'description': 'Template root. Base value that will be stripped from template names.',
|
||||
'alias': 'root'
|
||||
},
|
||||
'p' : {
|
||||
'type': 'boolean',
|
||||
'description': 'Compiling a partial template',
|
||||
'alias': 'partial'
|
||||
},
|
||||
'd' : {
|
||||
'type': 'boolean',
|
||||
'description': 'Include data when compiling',
|
||||
'alias': 'data'
|
||||
}
|
||||
})
|
||||
|
||||
@@ -78,7 +111,18 @@ if (argv.known) {
|
||||
|
||||
var output = [];
|
||||
if (!argv.simple) {
|
||||
output.push('(function() {\n var template = Handlebars.template, templates = Handlebars.templates = Handlebars.templates || {};\n');
|
||||
if (argv.amd) {
|
||||
output.push('define([\'' + argv.handlebarPath + 'handlebars\'], function(Handlebars) {\n');
|
||||
} else if (argv.commonjs) {
|
||||
output.push('var Handlebars = require("' + argv.commonjs + '");');
|
||||
} else {
|
||||
output.push('(function() {\n');
|
||||
}
|
||||
output.push(' var template = Handlebars.template, templates = ');
|
||||
output.push(argv.namespace);
|
||||
output.push(' = ');
|
||||
output.push(argv.namespace);
|
||||
output.push(' || {};\n');
|
||||
}
|
||||
function processTemplate(template, root) {
|
||||
var path = template,
|
||||
@@ -99,6 +143,10 @@ function processTemplate(template, root) {
|
||||
knownHelpersOnly: argv.o
|
||||
};
|
||||
|
||||
if (argv.data) {
|
||||
options.data = true;
|
||||
}
|
||||
|
||||
// Clean the template name
|
||||
if (!root) {
|
||||
template = basename(template);
|
||||
@@ -109,6 +157,8 @@ function processTemplate(template, root) {
|
||||
|
||||
if (argv.simple) {
|
||||
output.push(handlebars.precompile(data, options) + '\n');
|
||||
} else if (argv.partial) {
|
||||
output.push('Handlebars.partials[\'' + template + '\'] = template(' + handlebars.precompile(data, options) + ');\n');
|
||||
} else {
|
||||
output.push('templates[\'' + template + '\'] = template(' + handlebars.precompile(data, options) + ');\n');
|
||||
}
|
||||
@@ -121,7 +171,11 @@ argv._.forEach(function(template) {
|
||||
|
||||
// Output the content
|
||||
if (!argv.simple) {
|
||||
output.push('})();');
|
||||
if (argv.amd) {
|
||||
output.push('});');
|
||||
} else if (!argv.commonjs) {
|
||||
output.push('})();');
|
||||
}
|
||||
}
|
||||
output = output.join('');
|
||||
|
||||
|
||||
Vendored
+2202
File diff suppressed because it is too large
Load Diff
Vendored
+320
@@ -0,0 +1,320 @@
|
||||
/*
|
||||
|
||||
Copyright (C) 2011 by Yehuda Katz
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
|
||||
*/
|
||||
|
||||
// lib/handlebars/base.js
|
||||
|
||||
var Handlebars = {};
|
||||
|
||||
(function(Handlebars) {
|
||||
|
||||
Handlebars.VERSION = "1.0.0-rc.3";
|
||||
Handlebars.COMPILER_REVISION = 2;
|
||||
|
||||
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'
|
||||
};
|
||||
|
||||
Handlebars.helpers = {};
|
||||
Handlebars.partials = {};
|
||||
|
||||
Handlebars.registerHelper = function(name, fn, inverse) {
|
||||
if(inverse) { fn.not = inverse; }
|
||||
this.helpers[name] = fn;
|
||||
};
|
||||
|
||||
Handlebars.registerPartial = function(name, str) {
|
||||
this.partials[name] = str;
|
||||
};
|
||||
|
||||
Handlebars.registerHelper('helperMissing', function(arg) {
|
||||
if(arguments.length === 2) {
|
||||
return undefined;
|
||||
} else {
|
||||
throw new Error("Could not find property '" + arg + "'");
|
||||
}
|
||||
});
|
||||
|
||||
var toString = Object.prototype.toString, functionType = "[object Function]";
|
||||
|
||||
Handlebars.registerHelper('blockHelperMissing', function(context, options) {
|
||||
var inverse = options.inverse || function() {}, fn = options.fn;
|
||||
|
||||
var type = toString.call(context);
|
||||
|
||||
if(type === functionType) { context = context.call(this); }
|
||||
|
||||
if(context === true) {
|
||||
return fn(this);
|
||||
} else if(context === false || context == null) {
|
||||
return inverse(this);
|
||||
} else if(type === "[object Array]") {
|
||||
if(context.length > 0) {
|
||||
return Handlebars.helpers.each(context, options);
|
||||
} else {
|
||||
return inverse(this);
|
||||
}
|
||||
} else {
|
||||
return fn(context);
|
||||
}
|
||||
});
|
||||
|
||||
Handlebars.K = function() {};
|
||||
|
||||
Handlebars.createFrame = Object.create || function(object) {
|
||||
Handlebars.K.prototype = object;
|
||||
var obj = new Handlebars.K();
|
||||
Handlebars.K.prototype = null;
|
||||
return obj;
|
||||
};
|
||||
|
||||
Handlebars.logger = {
|
||||
DEBUG: 0, INFO: 1, WARN: 2, ERROR: 3, level: 3,
|
||||
|
||||
methodMap: {0: 'debug', 1: 'info', 2: 'warn', 3: 'error'},
|
||||
|
||||
// can be overridden in the host environment
|
||||
log: function(level, obj) {
|
||||
if (Handlebars.logger.level <= level) {
|
||||
var method = Handlebars.logger.methodMap[level];
|
||||
if (typeof console !== 'undefined' && console[method]) {
|
||||
console[method].call(console, obj);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
Handlebars.log = function(level, obj) { Handlebars.logger.log(level, obj); };
|
||||
|
||||
Handlebars.registerHelper('each', function(context, options) {
|
||||
var fn = options.fn, inverse = options.inverse;
|
||||
var i = 0, ret = "", data;
|
||||
|
||||
if (options.data) {
|
||||
data = Handlebars.createFrame(options.data);
|
||||
}
|
||||
|
||||
if(context && typeof context === 'object') {
|
||||
if(context instanceof Array){
|
||||
for(var j = context.length; i<j; i++) {
|
||||
if (data) { data.index = i; }
|
||||
ret = ret + fn(context[i], { data: data });
|
||||
}
|
||||
} else {
|
||||
for(var key in context) {
|
||||
if(context.hasOwnProperty(key)) {
|
||||
if(data) { data.key = key; }
|
||||
ret = ret + fn(context[key], {data: data});
|
||||
i++;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(i === 0){
|
||||
ret = inverse(this);
|
||||
}
|
||||
|
||||
return ret;
|
||||
});
|
||||
|
||||
Handlebars.registerHelper('if', function(context, options) {
|
||||
var type = toString.call(context);
|
||||
if(type === functionType) { context = context.call(this); }
|
||||
|
||||
if(!context || Handlebars.Utils.isEmpty(context)) {
|
||||
return options.inverse(this);
|
||||
} else {
|
||||
return options.fn(this);
|
||||
}
|
||||
});
|
||||
|
||||
Handlebars.registerHelper('unless', function(context, options) {
|
||||
var fn = options.fn, inverse = options.inverse;
|
||||
options.fn = inverse;
|
||||
options.inverse = fn;
|
||||
|
||||
return Handlebars.helpers['if'].call(this, context, options);
|
||||
});
|
||||
|
||||
Handlebars.registerHelper('with', function(context, options) {
|
||||
return options.fn(context);
|
||||
});
|
||||
|
||||
Handlebars.registerHelper('log', function(context, options) {
|
||||
var level = options.data && options.data.level != null ? parseInt(options.data.level, 10) : 1;
|
||||
Handlebars.log(level, context);
|
||||
});
|
||||
|
||||
}(Handlebars));
|
||||
;
|
||||
// lib/handlebars/utils.js
|
||||
|
||||
var errorProps = ['description', 'fileName', 'lineNumber', 'message', 'name', 'number', 'stack'];
|
||||
|
||||
Handlebars.Exception = function(message) {
|
||||
var tmp = Error.prototype.constructor.apply(this, arguments);
|
||||
|
||||
// Unfortunately errors are not enumerable in Chrome (at least), so `for prop in tmp` doesn't work.
|
||||
for (var idx = 0; idx < errorProps.length; idx++) {
|
||||
this[errorProps[idx]] = tmp[errorProps[idx]];
|
||||
}
|
||||
};
|
||||
Handlebars.Exception.prototype = new Error();
|
||||
|
||||
// Build out our basic SafeString type
|
||||
Handlebars.SafeString = function(string) {
|
||||
this.string = string;
|
||||
};
|
||||
Handlebars.SafeString.prototype.toString = function() {
|
||||
return this.string.toString();
|
||||
};
|
||||
|
||||
(function() {
|
||||
var escape = {
|
||||
"&": "&",
|
||||
"<": "<",
|
||||
">": ">",
|
||||
'"': """,
|
||||
"'": "'",
|
||||
"`": "`"
|
||||
};
|
||||
|
||||
var badChars = /[&<>"'`]/g;
|
||||
var possible = /[&<>"'`]/;
|
||||
|
||||
var escapeChar = function(chr) {
|
||||
return escape[chr] || "&";
|
||||
};
|
||||
|
||||
Handlebars.Utils = {
|
||||
escapeExpression: function(string) {
|
||||
// don't escape SafeStrings, since they're already safe
|
||||
if (string instanceof Handlebars.SafeString) {
|
||||
return string.toString();
|
||||
} else if (string == null || string === false) {
|
||||
return "";
|
||||
}
|
||||
|
||||
if(!possible.test(string)) { return string; }
|
||||
return string.replace(badChars, escapeChar);
|
||||
},
|
||||
|
||||
isEmpty: function(value) {
|
||||
if (!value && value !== 0) {
|
||||
return true;
|
||||
} else if(Object.prototype.toString.call(value) === "[object Array]" && value.length === 0) {
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
};
|
||||
})();
|
||||
;
|
||||
// lib/handlebars/runtime.js
|
||||
|
||||
Handlebars.VM = {
|
||||
template: function(templateSpec) {
|
||||
// Just add water
|
||||
var container = {
|
||||
escapeExpression: Handlebars.Utils.escapeExpression,
|
||||
invokePartial: Handlebars.VM.invokePartial,
|
||||
programs: [],
|
||||
program: function(i, fn, data) {
|
||||
var programWrapper = this.programs[i];
|
||||
if(data) {
|
||||
return Handlebars.VM.program(fn, data);
|
||||
} else if(programWrapper) {
|
||||
return programWrapper;
|
||||
} else {
|
||||
programWrapper = this.programs[i] = Handlebars.VM.program(fn);
|
||||
return programWrapper;
|
||||
}
|
||||
},
|
||||
programWithDepth: Handlebars.VM.programWithDepth,
|
||||
noop: Handlebars.VM.noop,
|
||||
compilerInfo: null
|
||||
};
|
||||
|
||||
return function(context, options) {
|
||||
options = options || {};
|
||||
var result = templateSpec.call(container, Handlebars, context, options.helpers, options.partials, options.data);
|
||||
|
||||
var compilerInfo = container.compilerInfo || [],
|
||||
compilerRevision = compilerInfo[0] || 1,
|
||||
currentRevision = Handlebars.COMPILER_REVISION;
|
||||
|
||||
if (compilerRevision !== currentRevision) {
|
||||
if (compilerRevision < currentRevision) {
|
||||
var runtimeVersions = Handlebars.REVISION_CHANGES[currentRevision],
|
||||
compilerVersions = Handlebars.REVISION_CHANGES[compilerRevision];
|
||||
throw "Template was precompiled with an older version of Handlebars than the current runtime. "+
|
||||
"Please update your precompiler to a newer version ("+runtimeVersions+") or downgrade your runtime to an older version ("+compilerVersions+").";
|
||||
} else {
|
||||
// Use the embedded version info since the runtime doesn't know about this revision yet
|
||||
throw "Template was precompiled with a newer version of Handlebars than the current runtime. "+
|
||||
"Please update your runtime to a newer version ("+compilerInfo[1]+").";
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
};
|
||||
},
|
||||
|
||||
programWithDepth: function(fn, data, $depth) {
|
||||
var args = Array.prototype.slice.call(arguments, 2);
|
||||
|
||||
return function(context, options) {
|
||||
options = options || {};
|
||||
|
||||
return fn.apply(this, [context, options.data || data].concat(args));
|
||||
};
|
||||
},
|
||||
program: function(fn, data) {
|
||||
return function(context, options) {
|
||||
options = options || {};
|
||||
|
||||
return fn(context, options.data || data);
|
||||
};
|
||||
},
|
||||
noop: function() { return ""; },
|
||||
invokePartial: function(partial, name, context, helpers, partials, data) {
|
||||
var options = { helpers: helpers, partials: partials, data: data };
|
||||
|
||||
if(partial === undefined) {
|
||||
throw new Handlebars.Exception("The partial " + name + " could not be found");
|
||||
} else if(partial instanceof Function) {
|
||||
return partial(context, options);
|
||||
} else if (!Handlebars.compile) {
|
||||
throw new Handlebars.Exception("The partial " + name + " could not be compiled when running in runtime-only mode");
|
||||
} else {
|
||||
partials[name] = Handlebars.compile(partial, {data: data !== undefined});
|
||||
return partials[name](context, options);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
Handlebars.template = Handlebars.VM.template;
|
||||
;
|
||||
+23
-5
@@ -1,14 +1,32 @@
|
||||
var Handlebars = require("./handlebars/base");
|
||||
module.exports = Handlebars;
|
||||
var handlebars = require("./handlebars/base"),
|
||||
|
||||
// Each of these augment the Handlebars object. No need to setup here.
|
||||
// (This is done to easily share code between commonjs and browse envs)
|
||||
require("./handlebars/utils");
|
||||
utils = require("./handlebars/utils"),
|
||||
compiler = require("./handlebars/compiler"),
|
||||
runtime = require("./handlebars/runtime");
|
||||
|
||||
require("./handlebars/compiler");
|
||||
require("./handlebars/runtime");
|
||||
var create = function() {
|
||||
var hb = handlebars.create();
|
||||
|
||||
utils.attach(hb);
|
||||
compiler.attach(hb);
|
||||
runtime.attach(hb);
|
||||
|
||||
return hb;
|
||||
};
|
||||
|
||||
var Handlebars = create();
|
||||
Handlebars.create = create;
|
||||
|
||||
module.exports = Handlebars; // instantiate an instance
|
||||
|
||||
// BEGIN(BROWSER)
|
||||
|
||||
// END(BROWSER)
|
||||
|
||||
// USAGE:
|
||||
// var handlebars = require('handlebars');
|
||||
|
||||
// var singleton = handlebars.Handlebars,
|
||||
// local = handlebars.create();
|
||||
|
||||
+57
-21
@@ -1,11 +1,20 @@
|
||||
/*jshint eqnull: true */
|
||||
|
||||
module.exports.create = function() {
|
||||
|
||||
// BEGIN(BROWSER)
|
||||
|
||||
/*jshint eqnull:true*/
|
||||
this.Handlebars = {};
|
||||
var Handlebars = {};
|
||||
|
||||
(function(Handlebars) {
|
||||
|
||||
Handlebars.VERSION = "1.0.rc.1";
|
||||
Handlebars.VERSION = "1.0.0-rc.3";
|
||||
Handlebars.COMPILER_REVISION = 2;
|
||||
|
||||
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'
|
||||
};
|
||||
|
||||
Handlebars.helpers = {};
|
||||
Handlebars.partials = {};
|
||||
@@ -32,8 +41,6 @@ var toString = Object.prototype.toString, functionType = "[object Function]";
|
||||
Handlebars.registerHelper('blockHelperMissing', function(context, options) {
|
||||
var inverse = options.inverse || function() {}, fn = options.fn;
|
||||
|
||||
|
||||
var ret = "";
|
||||
var type = toString.call(context);
|
||||
|
||||
if(type === functionType) { context = context.call(this); }
|
||||
@@ -44,13 +51,10 @@ Handlebars.registerHelper('blockHelperMissing', function(context, options) {
|
||||
return inverse(this);
|
||||
} else if(type === "[object Array]") {
|
||||
if(context.length > 0) {
|
||||
for(var i=0, j=context.length; i<j; i++) {
|
||||
ret = ret + fn(context[i]);
|
||||
}
|
||||
return Handlebars.helpers.each(context, options);
|
||||
} else {
|
||||
ret = inverse(this);
|
||||
return inverse(this);
|
||||
}
|
||||
return ret;
|
||||
} else {
|
||||
return fn(context);
|
||||
}
|
||||
@@ -65,22 +69,53 @@ Handlebars.createFrame = Object.create || function(object) {
|
||||
return obj;
|
||||
};
|
||||
|
||||
Handlebars.logger = {
|
||||
DEBUG: 0, INFO: 1, WARN: 2, ERROR: 3, level: 3,
|
||||
|
||||
methodMap: {0: 'debug', 1: 'info', 2: 'warn', 3: 'error'},
|
||||
|
||||
// can be overridden in the host environment
|
||||
log: function(level, obj) {
|
||||
if (Handlebars.logger.level <= level) {
|
||||
var method = Handlebars.logger.methodMap[level];
|
||||
if (typeof console !== 'undefined' && console[method]) {
|
||||
console[method].call(console, obj);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
Handlebars.log = function(level, obj) { Handlebars.logger.log(level, obj); };
|
||||
|
||||
Handlebars.registerHelper('each', function(context, options) {
|
||||
var fn = options.fn, inverse = options.inverse;
|
||||
var ret = "", data;
|
||||
var i = 0, ret = "", data;
|
||||
|
||||
if (options.data) {
|
||||
data = Handlebars.createFrame(options.data);
|
||||
}
|
||||
|
||||
if(context && context.length > 0) {
|
||||
for(var i=0, j=context.length; i<j; i++) {
|
||||
if (data) { data.index = i; }
|
||||
ret = ret + fn(context[i], { data: data });
|
||||
if(context && typeof context === 'object') {
|
||||
if(context instanceof Array){
|
||||
for(var j = context.length; i<j; i++) {
|
||||
if (data) { data.index = i; }
|
||||
ret = ret + fn(context[i], { data: data });
|
||||
}
|
||||
} else {
|
||||
for(var key in context) {
|
||||
if(context.hasOwnProperty(key)) {
|
||||
if(data) { data.key = key; }
|
||||
ret = ret + fn(context[key], {data: data});
|
||||
i++;
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
}
|
||||
|
||||
if(i === 0){
|
||||
ret = inverse(this);
|
||||
}
|
||||
|
||||
return ret;
|
||||
});
|
||||
|
||||
@@ -107,13 +142,14 @@ Handlebars.registerHelper('with', function(context, options) {
|
||||
return options.fn(context);
|
||||
});
|
||||
|
||||
Handlebars.registerHelper('log', function(context) {
|
||||
Handlebars.log(context);
|
||||
Handlebars.registerHelper('log', function(context, options) {
|
||||
var level = options.data && options.data.level != null ? parseInt(options.data.level, 10) : 1;
|
||||
Handlebars.log(level, context);
|
||||
});
|
||||
|
||||
}(this.Handlebars));
|
||||
}(Handlebars));
|
||||
|
||||
// END(BROWSER)
|
||||
|
||||
module.exports = this.Handlebars;
|
||||
|
||||
return Handlebars;
|
||||
};
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
var Handlebars = require('./base');
|
||||
exports.attach = function(Handlebars) {
|
||||
|
||||
// BEGIN(BROWSER)
|
||||
(function() {
|
||||
@@ -33,22 +33,19 @@ var Handlebars = require('./base');
|
||||
// pass or at runtime.
|
||||
};
|
||||
|
||||
Handlebars.AST.PartialNode = function(id, context) {
|
||||
this.type = "partial";
|
||||
|
||||
// TODO: disallow complex IDs
|
||||
|
||||
this.id = id;
|
||||
this.context = context;
|
||||
};
|
||||
|
||||
var verifyMatch = function(open, close) {
|
||||
if(open.original !== close.original) {
|
||||
throw new Handlebars.Exception(open.original + " doesn't match " + close.original);
|
||||
}
|
||||
Handlebars.AST.PartialNode = function(partialName, context) {
|
||||
this.type = "partial";
|
||||
this.partialName = partialName;
|
||||
this.context = context;
|
||||
};
|
||||
|
||||
Handlebars.AST.BlockNode = function(mustache, program, inverse, close) {
|
||||
var verifyMatch = function(open, close) {
|
||||
if(open.original !== close.original) {
|
||||
throw new Handlebars.Exception(open.original + " doesn't match " + close.original);
|
||||
}
|
||||
};
|
||||
|
||||
verifyMatch(mustache.id, close);
|
||||
this.type = "block";
|
||||
this.mustache = mustache;
|
||||
@@ -79,8 +76,11 @@ var Handlebars = require('./base');
|
||||
for(var i=0,l=parts.length; i<l; i++) {
|
||||
var part = parts[i];
|
||||
|
||||
if(part === "..") { depth++; }
|
||||
else if(part === "." || part === "this") { this.isScoped = true; }
|
||||
if (part === ".." || part === "." || part === "this") {
|
||||
if (dig.length > 0) { throw new Handlebars.Exception("Invalid path: " + this.original); }
|
||||
else if (part === "..") { depth++; }
|
||||
else { this.isScoped = true; }
|
||||
}
|
||||
else { dig.push(part); }
|
||||
}
|
||||
|
||||
@@ -91,6 +91,13 @@ var Handlebars = require('./base');
|
||||
// an ID is simple if it only has one part, and that part is not
|
||||
// `..` or `this`.
|
||||
this.isSimple = parts.length === 1 && !this.isScoped && depth === 0;
|
||||
|
||||
this.stringModeValue = this.string;
|
||||
};
|
||||
|
||||
Handlebars.AST.PartialNameNode = function(name) {
|
||||
this.type = "PARTIAL_NAME";
|
||||
this.name = name;
|
||||
};
|
||||
|
||||
Handlebars.AST.DataNode = function(id) {
|
||||
@@ -101,16 +108,19 @@ var Handlebars = require('./base');
|
||||
Handlebars.AST.StringNode = function(string) {
|
||||
this.type = "STRING";
|
||||
this.string = string;
|
||||
this.stringModeValue = string;
|
||||
};
|
||||
|
||||
Handlebars.AST.IntegerNode = function(integer) {
|
||||
this.type = "INTEGER";
|
||||
this.integer = integer;
|
||||
this.stringModeValue = Number(integer);
|
||||
};
|
||||
|
||||
Handlebars.AST.BooleanNode = function(bool) {
|
||||
this.type = "BOOLEAN";
|
||||
this.bool = bool;
|
||||
this.stringModeValue = bool === "true";
|
||||
};
|
||||
|
||||
Handlebars.AST.CommentNode = function(comment) {
|
||||
@@ -121,3 +131,6 @@ var Handlebars = require('./base');
|
||||
})();
|
||||
// END(BROWSER)
|
||||
|
||||
return Handlebars;
|
||||
};
|
||||
|
||||
|
||||
@@ -1,27 +1,24 @@
|
||||
var handlebars = require("./parser").parser;
|
||||
var Handlebars = require("../base");
|
||||
var handlebars = require("./parser");
|
||||
|
||||
exports.attach = function(Handlebars) {
|
||||
|
||||
// BEGIN(BROWSER)
|
||||
|
||||
Handlebars.Parser = handlebars;
|
||||
|
||||
Handlebars.parse = function(string) {
|
||||
Handlebars.parse = function(input) {
|
||||
|
||||
// Just return if an already-compile AST was passed in.
|
||||
if(input.constructor === Handlebars.AST.ProgramNode) { return input; }
|
||||
|
||||
Handlebars.Parser.yy = Handlebars.AST;
|
||||
return Handlebars.Parser.parse(string);
|
||||
return Handlebars.Parser.parse(input);
|
||||
};
|
||||
|
||||
Handlebars.print = function(ast) {
|
||||
return new Handlebars.PrintVisitor().accept(ast);
|
||||
};
|
||||
|
||||
Handlebars.logger = {
|
||||
DEBUG: 0, INFO: 1, WARN: 2, ERROR: 3, level: 3,
|
||||
|
||||
// override in the host environment
|
||||
log: function(level, str) {}
|
||||
};
|
||||
|
||||
Handlebars.log = function(level, str) { Handlebars.logger.log(level, str); };
|
||||
|
||||
// END(BROWSER)
|
||||
|
||||
module.exports = Handlebars;
|
||||
return Handlebars;
|
||||
};
|
||||
|
||||
@@ -1,4 +1,8 @@
|
||||
var Handlebars = require("./base");
|
||||
var compilerbase = require("./base");
|
||||
|
||||
exports.attach = function(Handlebars) {
|
||||
|
||||
compilerbase.attach(Handlebars);
|
||||
|
||||
// BEGIN(BROWSER)
|
||||
|
||||
@@ -38,6 +42,26 @@ Handlebars.JavaScriptCompiler = function() {};
|
||||
|
||||
return out.join("\n");
|
||||
},
|
||||
equals: function(other) {
|
||||
var len = this.opcodes.length;
|
||||
if (other.opcodes.length !== len) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (var i = 0; i < len; i++) {
|
||||
var opcode = this.opcodes[i],
|
||||
otherOpcode = other.opcodes[i];
|
||||
if (opcode.opcode !== otherOpcode.opcode || opcode.args.length !== otherOpcode.args.length) {
|
||||
return false;
|
||||
}
|
||||
for (var j = 0; j < opcode.args.length; j++) {
|
||||
if (opcode.args[j] !== otherOpcode.args[j]) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
},
|
||||
|
||||
guid: 0,
|
||||
|
||||
@@ -129,7 +153,7 @@ Handlebars.JavaScriptCompiler = function() {};
|
||||
// evaluate it by executing `blockHelperMissing`
|
||||
this.opcode('pushProgram', program);
|
||||
this.opcode('pushProgram', inverse);
|
||||
this.opcode('pushLiteral', '{}');
|
||||
this.opcode('emptyHash');
|
||||
this.opcode('blockValue');
|
||||
} else {
|
||||
this.ambiguousMustache(mustache, program, inverse);
|
||||
@@ -138,7 +162,7 @@ Handlebars.JavaScriptCompiler = function() {};
|
||||
// evaluate it by executing `blockHelperMissing`
|
||||
this.opcode('pushProgram', program);
|
||||
this.opcode('pushProgram', inverse);
|
||||
this.opcode('pushLiteral', '{}');
|
||||
this.opcode('emptyHash');
|
||||
this.opcode('ambiguousBlockValue');
|
||||
}
|
||||
|
||||
@@ -148,19 +172,25 @@ Handlebars.JavaScriptCompiler = function() {};
|
||||
hash: function(hash) {
|
||||
var pairs = hash.pairs, pair, val;
|
||||
|
||||
this.opcode('push', '{}');
|
||||
this.opcode('pushHash');
|
||||
|
||||
for(var i=0, l=pairs.length; i<l; i++) {
|
||||
pair = pairs[i];
|
||||
val = pair[1];
|
||||
|
||||
this.accept(val);
|
||||
if (this.options.stringParams) {
|
||||
this.opcode('pushStringParam', val.stringModeValue, val.type);
|
||||
} else {
|
||||
this.accept(val);
|
||||
}
|
||||
|
||||
this.opcode('assignToHash', pair[0]);
|
||||
}
|
||||
this.opcode('popHash');
|
||||
},
|
||||
|
||||
partial: function(partial) {
|
||||
var id = partial.id;
|
||||
var partialName = partial.partialName;
|
||||
this.usePartial = true;
|
||||
|
||||
if(partial.context) {
|
||||
@@ -169,7 +199,7 @@ Handlebars.JavaScriptCompiler = function() {};
|
||||
this.opcode('push', 'depth0');
|
||||
}
|
||||
|
||||
this.opcode('invokePartial', id.original);
|
||||
this.opcode('invokePartial', partialName.name);
|
||||
this.opcode('append');
|
||||
},
|
||||
|
||||
@@ -197,17 +227,19 @@ Handlebars.JavaScriptCompiler = function() {};
|
||||
},
|
||||
|
||||
ambiguousMustache: function(mustache, program, inverse) {
|
||||
var id = mustache.id, name = id.parts[0];
|
||||
var id = mustache.id,
|
||||
name = id.parts[0],
|
||||
isBlock = program != null || inverse != null;
|
||||
|
||||
this.opcode('getContext', id.depth);
|
||||
|
||||
this.opcode('pushProgram', program);
|
||||
this.opcode('pushProgram', inverse);
|
||||
|
||||
this.opcode('invokeAmbiguous', name);
|
||||
this.opcode('invokeAmbiguous', name, isBlock);
|
||||
},
|
||||
|
||||
simpleMustache: function(mustache, program, inverse) {
|
||||
simpleMustache: function(mustache) {
|
||||
var id = mustache.id;
|
||||
|
||||
if (id.type === 'DATA') {
|
||||
@@ -324,7 +356,7 @@ Handlebars.JavaScriptCompiler = function() {};
|
||||
}
|
||||
|
||||
this.opcode('getContext', param.depth || 0);
|
||||
this.opcode('pushStringParam', param.string);
|
||||
this.opcode('pushStringParam', param.stringModeValue, param.type);
|
||||
} else {
|
||||
this[param.type](param);
|
||||
}
|
||||
@@ -338,7 +370,7 @@ Handlebars.JavaScriptCompiler = function() {};
|
||||
if(mustache.hash) {
|
||||
this.hash(mustache.hash);
|
||||
} else {
|
||||
this.opcode('pushLiteral', '{}');
|
||||
this.opcode('emptyHash');
|
||||
}
|
||||
|
||||
return params;
|
||||
@@ -355,7 +387,7 @@ Handlebars.JavaScriptCompiler = function() {};
|
||||
if(mustache.hash) {
|
||||
this.hash(mustache.hash);
|
||||
} else {
|
||||
this.opcode('pushLiteral', '{}');
|
||||
this.opcode('emptyHash');
|
||||
}
|
||||
|
||||
return params;
|
||||
@@ -369,7 +401,7 @@ Handlebars.JavaScriptCompiler = function() {};
|
||||
JavaScriptCompiler.prototype = {
|
||||
// PUBLIC API: You can override these methods in a subclass to provide
|
||||
// alternative compiled forms for name lookup and buffering semantics
|
||||
nameLookup: function(parent, name, type) {
|
||||
nameLookup: function(parent, name /* , type*/) {
|
||||
if (/^[0-9]+$/.test(name)) {
|
||||
return parent + "[" + name + "]";
|
||||
} else if (JavaScriptCompiler.isValidJavaScriptVariableName(name)) {
|
||||
@@ -384,7 +416,11 @@ Handlebars.JavaScriptCompiler = function() {};
|
||||
if (this.environment.isSimple) {
|
||||
return "return " + string + ";";
|
||||
} else {
|
||||
return "buffer += " + string + ";";
|
||||
return {
|
||||
appendToBuffer: true,
|
||||
content: string,
|
||||
toString: function() { return "buffer += " + string + ";"; }
|
||||
};
|
||||
}
|
||||
},
|
||||
|
||||
@@ -405,6 +441,7 @@ Handlebars.JavaScriptCompiler = function() {};
|
||||
this.isChild = !!context;
|
||||
this.context = context || {
|
||||
programs: [],
|
||||
environments: [],
|
||||
aliases: { }
|
||||
};
|
||||
|
||||
@@ -414,6 +451,7 @@ Handlebars.JavaScriptCompiler = function() {};
|
||||
this.stackVars = [];
|
||||
this.registers = { list: [] };
|
||||
this.compileStack = [];
|
||||
this.inlineStack = [];
|
||||
|
||||
this.compileChildren(environment, options);
|
||||
|
||||
@@ -435,11 +473,11 @@ Handlebars.JavaScriptCompiler = function() {};
|
||||
},
|
||||
|
||||
nextOpcode: function() {
|
||||
var opcodes = this.environment.opcodes, opcode = opcodes[this.i + 1];
|
||||
var opcodes = this.environment.opcodes;
|
||||
return opcodes[this.i + 1];
|
||||
},
|
||||
|
||||
eat: function(opcode) {
|
||||
eat: function() {
|
||||
this.i = this.i + 1;
|
||||
},
|
||||
|
||||
@@ -477,7 +515,6 @@ Handlebars.JavaScriptCompiler = function() {};
|
||||
|
||||
// Generate minimizer alias mappings
|
||||
if (!this.isChild) {
|
||||
var aliases = [];
|
||||
for (var alias in this.context.aliases) {
|
||||
this.source[1] = this.source[1] + ', ' + alias + '=' + this.context.aliases[alias];
|
||||
}
|
||||
@@ -502,16 +539,48 @@ Handlebars.JavaScriptCompiler = function() {};
|
||||
params.push("depth" + this.environment.depths.list[i]);
|
||||
}
|
||||
|
||||
// Perform a second pass over the output to merge content when possible
|
||||
var source = this.mergeSource();
|
||||
|
||||
if (!this.isChild) {
|
||||
var revision = Handlebars.COMPILER_REVISION,
|
||||
versions = Handlebars.REVISION_CHANGES[revision];
|
||||
source = "this.compilerInfo = ["+revision+",'"+versions+"'];\n"+source;
|
||||
}
|
||||
|
||||
if (asObject) {
|
||||
params.push(this.source.join("\n "));
|
||||
params.push(source);
|
||||
|
||||
return Function.apply(this, params);
|
||||
} else {
|
||||
var functionSource = 'function ' + (this.name || '') + '(' + params.join(',') + ') {\n ' + this.source.join("\n ") + '}';
|
||||
var functionSource = 'function ' + (this.name || '') + '(' + params.join(',') + ') {\n ' + source + '}';
|
||||
Handlebars.log(Handlebars.logger.DEBUG, functionSource + "\n\n");
|
||||
return functionSource;
|
||||
}
|
||||
},
|
||||
mergeSource: function() {
|
||||
// WARN: We are not handling the case where buffer is still populated as the source should
|
||||
// not have buffer append operations as their final action.
|
||||
var source = '',
|
||||
buffer;
|
||||
for (var i = 0, len = this.source.length; i < len; i++) {
|
||||
var line = this.source[i];
|
||||
if (line.appendToBuffer) {
|
||||
if (buffer) {
|
||||
buffer = buffer + '\n + ' + line.content;
|
||||
} else {
|
||||
buffer = line.content;
|
||||
}
|
||||
} else {
|
||||
if (buffer) {
|
||||
source += 'buffer += ' + buffer + ';\n ';
|
||||
buffer = undefined;
|
||||
}
|
||||
source += line + '\n ';
|
||||
}
|
||||
}
|
||||
return source;
|
||||
},
|
||||
|
||||
// [blockValue]
|
||||
//
|
||||
@@ -530,7 +599,7 @@ Handlebars.JavaScriptCompiler = function() {};
|
||||
|
||||
this.replaceStack(function(current) {
|
||||
params.splice(1, 0, current);
|
||||
return current + " = blockHelperMissing.call(" + params.join(", ") + ")";
|
||||
return "blockHelperMissing.call(" + params.join(", ") + ")";
|
||||
});
|
||||
},
|
||||
|
||||
@@ -549,6 +618,9 @@ Handlebars.JavaScriptCompiler = function() {};
|
||||
var current = this.topStack();
|
||||
params.splice(1, 0, current);
|
||||
|
||||
// Use the options value generated from the invocation
|
||||
params[params.length-1] = 'options';
|
||||
|
||||
this.source.push("if (!" + this.lastHelper + ") { " + current + " = blockHelperMissing.call(" + params.join(", ") + "); }");
|
||||
},
|
||||
|
||||
@@ -572,6 +644,9 @@ Handlebars.JavaScriptCompiler = function() {};
|
||||
// If `value` is truthy, or 0, it is coerced into a string and appended
|
||||
// Otherwise, the empty string is appended
|
||||
append: function() {
|
||||
// Force anything that is inlined onto the stack so we don't have duplication
|
||||
// when we examine local
|
||||
this.flushInline();
|
||||
var local = this.popStack();
|
||||
this.source.push("if(" + local + " || " + local + " === 0) { " + this.appendToBuffer(local) + " }");
|
||||
if (this.environment.isSimple) {
|
||||
@@ -586,15 +661,9 @@ Handlebars.JavaScriptCompiler = function() {};
|
||||
//
|
||||
// Escape `value` and append it to the buffer
|
||||
appendEscaped: function() {
|
||||
var opcode = this.nextOpcode(), extra = "";
|
||||
this.context.aliases.escapeExpression = 'this.escapeExpression';
|
||||
|
||||
if(opcode && opcode.opcode === 'appendContent') {
|
||||
extra = " + " + this.quotedString(opcode.args[0]);
|
||||
this.eat(opcode);
|
||||
}
|
||||
|
||||
this.source.push(this.appendToBuffer("escapeExpression(" + this.popStack() + ")" + extra));
|
||||
this.source.push(this.appendToBuffer("escapeExpression(" + this.popStack() + ")"));
|
||||
},
|
||||
|
||||
// [getContext]
|
||||
@@ -618,7 +687,7 @@ Handlebars.JavaScriptCompiler = function() {};
|
||||
// Looks up the value of `name` on the current context and pushes
|
||||
// it onto the stack.
|
||||
lookupOnContext: function(name) {
|
||||
this.pushStack(this.nameLookup('depth' + this.lastContext, name, 'context'));
|
||||
this.push(this.nameLookup('depth' + this.lastContext, name, 'context'));
|
||||
},
|
||||
|
||||
// [pushContext]
|
||||
@@ -642,7 +711,7 @@ Handlebars.JavaScriptCompiler = function() {};
|
||||
this.context.aliases.functionType = '"function"';
|
||||
|
||||
this.replaceStack(function(current) {
|
||||
return "typeof " + current + " === functionType ? " + current + "() : " + current;
|
||||
return "typeof " + current + " === functionType ? " + current + ".apply(depth0) : " + current;
|
||||
});
|
||||
},
|
||||
|
||||
@@ -666,7 +735,7 @@ Handlebars.JavaScriptCompiler = function() {};
|
||||
//
|
||||
// Push the result of looking up `id` on the current data
|
||||
lookupData: function(id) {
|
||||
this.pushStack(this.nameLookup('data', id, 'data'));
|
||||
this.push(this.nameLookup('data', id, 'data'));
|
||||
},
|
||||
|
||||
// [pushStringParam]
|
||||
@@ -677,9 +746,36 @@ Handlebars.JavaScriptCompiler = function() {};
|
||||
// This opcode is designed for use in string mode, which
|
||||
// provides the string value of a parameter along with its
|
||||
// depth rather than resolving it immediately.
|
||||
pushStringParam: function(string) {
|
||||
pushStringParam: function(string, type) {
|
||||
this.pushStackLiteral('depth' + this.lastContext);
|
||||
this.pushString(string);
|
||||
|
||||
this.pushString(type);
|
||||
|
||||
if (typeof string === 'string') {
|
||||
this.pushString(string);
|
||||
} else {
|
||||
this.pushStackLiteral(string);
|
||||
}
|
||||
},
|
||||
|
||||
emptyHash: function() {
|
||||
this.pushStackLiteral('{}');
|
||||
|
||||
if (this.options.stringParams) {
|
||||
this.register('hashTypes', '{}');
|
||||
}
|
||||
},
|
||||
pushHash: function() {
|
||||
this.hash = {values: [], types: []};
|
||||
},
|
||||
popHash: function() {
|
||||
var hash = this.hash;
|
||||
this.hash = undefined;
|
||||
|
||||
if (this.options.stringParams) {
|
||||
this.register('hashTypes', '{' + hash.types.join(',') + '}');
|
||||
}
|
||||
this.push('{\n ' + hash.values.join(',\n ') + '\n }');
|
||||
},
|
||||
|
||||
// [pushString]
|
||||
@@ -699,7 +795,8 @@ Handlebars.JavaScriptCompiler = function() {};
|
||||
//
|
||||
// Push an expression onto the stack
|
||||
push: function(expr) {
|
||||
this.pushStack(expr);
|
||||
this.inlineStack.push(expr);
|
||||
return expr;
|
||||
},
|
||||
|
||||
// [pushLiteral]
|
||||
@@ -742,12 +839,14 @@ Handlebars.JavaScriptCompiler = function() {};
|
||||
invokeHelper: function(paramSize, name) {
|
||||
this.context.aliases.helperMissing = 'helpers.helperMissing';
|
||||
|
||||
var helper = this.lastHelper = this.setupHelper(paramSize, name);
|
||||
this.register('foundHelper', helper.name);
|
||||
var helper = this.lastHelper = this.setupHelper(paramSize, name, true);
|
||||
|
||||
this.pushStack("foundHelper ? foundHelper.call(" +
|
||||
helper.callParams + ") " + ": helperMissing.call(" +
|
||||
helper.helperMissingParams + ")");
|
||||
this.push(helper.name);
|
||||
this.replaceStack(function(name) {
|
||||
return name + ' ? ' + name + '.call(' +
|
||||
helper.callParams + ") " + ": helperMissing.call(" +
|
||||
helper.helperMissingParams + ")";
|
||||
});
|
||||
},
|
||||
|
||||
// [invokeKnownHelper]
|
||||
@@ -759,7 +858,7 @@ Handlebars.JavaScriptCompiler = function() {};
|
||||
// so a `helperMissing` fallback is not required.
|
||||
invokeKnownHelper: function(paramSize, name) {
|
||||
var helper = this.setupHelper(paramSize, name);
|
||||
this.pushStack(helper.name + ".call(" + helper.callParams + ")");
|
||||
this.push(helper.name + ".call(" + helper.callParams + ")");
|
||||
},
|
||||
|
||||
// [invokeAmbiguous]
|
||||
@@ -774,20 +873,19 @@ Handlebars.JavaScriptCompiler = function() {};
|
||||
// This operation emits more code than the other options,
|
||||
// and can be avoided by passing the `knownHelpers` and
|
||||
// `knownHelpersOnly` flags at compile-time.
|
||||
invokeAmbiguous: function(name) {
|
||||
invokeAmbiguous: function(name, helperCall) {
|
||||
this.context.aliases.functionType = '"function"';
|
||||
|
||||
this.pushStackLiteral('{}');
|
||||
var helper = this.setupHelper(0, name);
|
||||
this.pushStackLiteral('{}'); // Hash value
|
||||
var helper = this.setupHelper(0, name, helperCall);
|
||||
|
||||
var helperName = this.lastHelper = this.nameLookup('helpers', name, 'helper');
|
||||
this.register('foundHelper', helperName);
|
||||
|
||||
var nonHelper = this.nameLookup('depth' + this.lastContext, name, 'context');
|
||||
var nextStack = this.nextStack();
|
||||
|
||||
this.source.push('if (foundHelper) { ' + nextStack + ' = foundHelper.call(' + helper.callParams + '); }');
|
||||
this.source.push('else { ' + nextStack + ' = ' + nonHelper + '; ' + nextStack + ' = typeof ' + nextStack + ' === functionType ? ' + nextStack + '() : ' + nextStack + '; }');
|
||||
this.source.push('if (' + nextStack + ' = ' + helperName + ') { ' + nextStack + ' = ' + nextStack + '.call(' + helper.callParams + '); }');
|
||||
this.source.push('else { ' + nextStack + ' = ' + nonHelper + '; ' + nextStack + ' = typeof ' + nextStack + ' === functionType ? ' + nextStack + '.apply(depth0) : ' + nextStack + '; }');
|
||||
},
|
||||
|
||||
// [invokePartial]
|
||||
@@ -805,7 +903,7 @@ Handlebars.JavaScriptCompiler = function() {};
|
||||
}
|
||||
|
||||
this.context.aliases.self = "this";
|
||||
this.pushStack("self.invokePartial(" + params.join(", ") + ");");
|
||||
this.push("self.invokePartial(" + params.join(", ") + ")");
|
||||
},
|
||||
|
||||
// [assignToHash]
|
||||
@@ -816,10 +914,19 @@ Handlebars.JavaScriptCompiler = function() {};
|
||||
// Pops a value and hash off the stack, assigns `hash[key] = value`
|
||||
// and pushes the hash back onto the stack.
|
||||
assignToHash: function(key) {
|
||||
var value = this.popStack();
|
||||
var hash = this.topStack();
|
||||
var value = this.popStack(),
|
||||
type;
|
||||
|
||||
this.source.push(hash + "['" + key + "'] = " + value + ";");
|
||||
if (this.options.stringParams) {
|
||||
type = this.popStack();
|
||||
this.popStack();
|
||||
}
|
||||
|
||||
var hash = this.hash;
|
||||
if (type) {
|
||||
hash.types.push("'" + key + "': " + type);
|
||||
}
|
||||
hash.values.push("'" + key + "': (" + value + ")");
|
||||
},
|
||||
|
||||
// HELPERS
|
||||
@@ -833,11 +940,27 @@ Handlebars.JavaScriptCompiler = function() {};
|
||||
child = children[i];
|
||||
compiler = new this.compiler();
|
||||
|
||||
this.context.programs.push(''); // Placeholder to prevent name conflicts for nested children
|
||||
var index = this.context.programs.length;
|
||||
child.index = index;
|
||||
child.name = 'program' + index;
|
||||
this.context.programs[index] = compiler.compile(child, options, this.context);
|
||||
var index = this.matchExistingProgram(child);
|
||||
|
||||
if (index == null) {
|
||||
this.context.programs.push(''); // Placeholder to prevent name conflicts for nested children
|
||||
index = this.context.programs.length;
|
||||
child.index = index;
|
||||
child.name = 'program' + index;
|
||||
this.context.programs[index] = compiler.compile(child, options, this.context);
|
||||
this.context.environments[index] = child;
|
||||
} else {
|
||||
child.index = index;
|
||||
child.name = 'program' + index;
|
||||
}
|
||||
}
|
||||
},
|
||||
matchExistingProgram: function(child) {
|
||||
for (var i = 0, len = this.context.environments.length; i < len; i++) {
|
||||
var environment = this.context.environments[i];
|
||||
if (environment && environment.equals(child)) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
@@ -881,50 +1004,111 @@ Handlebars.JavaScriptCompiler = function() {};
|
||||
},
|
||||
|
||||
pushStackLiteral: function(item) {
|
||||
this.compileStack.push(new Literal(item));
|
||||
return item;
|
||||
return this.push(new Literal(item));
|
||||
},
|
||||
|
||||
pushStack: function(item) {
|
||||
this.source.push(this.incrStack() + " = " + item + ";");
|
||||
this.compileStack.push("stack" + this.stackSlot);
|
||||
return "stack" + this.stackSlot;
|
||||
this.flushInline();
|
||||
|
||||
var stack = this.incrStack();
|
||||
if (item) {
|
||||
this.source.push(stack + " = " + item + ";");
|
||||
}
|
||||
this.compileStack.push(stack);
|
||||
return stack;
|
||||
},
|
||||
|
||||
replaceStack: function(callback) {
|
||||
var item = callback.call(this, this.topStack());
|
||||
var prefix = '',
|
||||
inline = this.isInline(),
|
||||
stack;
|
||||
|
||||
this.source.push(this.topStack() + " = " + item + ";");
|
||||
return "stack" + this.stackSlot;
|
||||
// If we are currently inline then we want to merge the inline statement into the
|
||||
// replacement statement via ','
|
||||
if (inline) {
|
||||
var top = this.popStack(true);
|
||||
|
||||
if (top instanceof Literal) {
|
||||
// Literals do not need to be inlined
|
||||
stack = top.value;
|
||||
} else {
|
||||
// Get or create the current stack name for use by the inline
|
||||
var name = this.stackSlot ? this.topStackName() : this.incrStack();
|
||||
|
||||
prefix = '(' + this.push(name) + ' = ' + top + '),';
|
||||
stack = this.topStack();
|
||||
}
|
||||
} else {
|
||||
stack = this.topStack();
|
||||
}
|
||||
|
||||
var item = callback.call(this, stack);
|
||||
|
||||
if (inline) {
|
||||
if (this.inlineStack.length || this.compileStack.length) {
|
||||
this.popStack();
|
||||
}
|
||||
this.push('(' + prefix + item + ')');
|
||||
} else {
|
||||
// Prevent modification of the context depth variable. Through replaceStack
|
||||
if (!/^stack/.test(stack)) {
|
||||
stack = this.nextStack();
|
||||
}
|
||||
|
||||
this.source.push(stack + " = (" + prefix + item + ");");
|
||||
}
|
||||
return stack;
|
||||
},
|
||||
|
||||
nextStack: function(skipCompileStack) {
|
||||
var name = this.incrStack();
|
||||
this.compileStack.push("stack" + this.stackSlot);
|
||||
return name;
|
||||
nextStack: function() {
|
||||
return this.pushStack();
|
||||
},
|
||||
|
||||
incrStack: function() {
|
||||
this.stackSlot++;
|
||||
if(this.stackSlot > this.stackVars.length) { this.stackVars.push("stack" + this.stackSlot); }
|
||||
return this.topStackName();
|
||||
},
|
||||
topStackName: function() {
|
||||
return "stack" + this.stackSlot;
|
||||
},
|
||||
flushInline: function() {
|
||||
var inlineStack = this.inlineStack;
|
||||
if (inlineStack.length) {
|
||||
this.inlineStack = [];
|
||||
for (var i = 0, len = inlineStack.length; i < len; i++) {
|
||||
var entry = inlineStack[i];
|
||||
if (entry instanceof Literal) {
|
||||
this.compileStack.push(entry);
|
||||
} else {
|
||||
this.pushStack(entry);
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
isInline: function() {
|
||||
return this.inlineStack.length;
|
||||
},
|
||||
|
||||
popStack: function() {
|
||||
var item = this.compileStack.pop();
|
||||
popStack: function(wrapped) {
|
||||
var inline = this.isInline(),
|
||||
item = (inline ? this.inlineStack : this.compileStack).pop();
|
||||
|
||||
if (item instanceof Literal) {
|
||||
if (!wrapped && (item instanceof Literal)) {
|
||||
return item.value;
|
||||
} else {
|
||||
this.stackSlot--;
|
||||
if (!inline) {
|
||||
this.stackSlot--;
|
||||
}
|
||||
return item;
|
||||
}
|
||||
},
|
||||
|
||||
topStack: function() {
|
||||
var item = this.compileStack[this.compileStack.length - 1];
|
||||
topStack: function(wrapped) {
|
||||
var stack = (this.isInline() ? this.inlineStack : this.compileStack),
|
||||
item = stack[stack.length - 1];
|
||||
|
||||
if (item instanceof Literal) {
|
||||
if (!wrapped && (item instanceof Literal)) {
|
||||
return item.value;
|
||||
} else {
|
||||
return item;
|
||||
@@ -939,23 +1123,23 @@ Handlebars.JavaScriptCompiler = function() {};
|
||||
.replace(/\r/g, '\\r') + '"';
|
||||
},
|
||||
|
||||
setupHelper: function(paramSize, name) {
|
||||
setupHelper: function(paramSize, name, missingParams) {
|
||||
var params = [];
|
||||
this.setupParams(paramSize, params);
|
||||
this.setupParams(paramSize, params, missingParams);
|
||||
var foundHelper = this.nameLookup('helpers', name, 'helper');
|
||||
|
||||
return {
|
||||
params: params,
|
||||
name: foundHelper,
|
||||
callParams: ["depth0"].concat(params).join(", "),
|
||||
helperMissingParams: ["depth0", this.quotedString(name)].concat(params).join(", ")
|
||||
helperMissingParams: missingParams && ["depth0", this.quotedString(name)].concat(params).join(", ")
|
||||
};
|
||||
},
|
||||
|
||||
// the params and contexts arguments are passed in arrays
|
||||
// to fill in
|
||||
setupParams: function(paramSize, params) {
|
||||
var options = [], contexts = [], param, inverse, program;
|
||||
setupParams: function(paramSize, params, useRegister) {
|
||||
var options = [], contexts = [], types = [], param, inverse, program;
|
||||
|
||||
options.push("hash:" + this.popStack());
|
||||
|
||||
@@ -984,19 +1168,28 @@ Handlebars.JavaScriptCompiler = function() {};
|
||||
params.push(param);
|
||||
|
||||
if(this.options.stringParams) {
|
||||
types.push(this.popStack());
|
||||
contexts.push(this.popStack());
|
||||
}
|
||||
}
|
||||
|
||||
if (this.options.stringParams) {
|
||||
options.push("contexts:[" + contexts.join(",") + "]");
|
||||
options.push("types:[" + types.join(",") + "]");
|
||||
options.push("hashTypes:hashTypes");
|
||||
}
|
||||
|
||||
if(this.options.data) {
|
||||
options.push("data:data");
|
||||
}
|
||||
|
||||
params.push("{" + options.join(",") + "}");
|
||||
options = "{" + options.join(",") + "}";
|
||||
if (useRegister) {
|
||||
this.register('options', options);
|
||||
params.push('options');
|
||||
} else {
|
||||
params.push(options);
|
||||
}
|
||||
return params.join(", ");
|
||||
}
|
||||
};
|
||||
@@ -1034,20 +1227,32 @@ Handlebars.JavaScriptCompiler = function() {};
|
||||
|
||||
})(Handlebars.Compiler, Handlebars.JavaScriptCompiler);
|
||||
|
||||
Handlebars.precompile = function(string, options) {
|
||||
options = options || {};
|
||||
Handlebars.precompile = function(input, options) {
|
||||
if (!input || (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);
|
||||
}
|
||||
|
||||
var ast = Handlebars.parse(string);
|
||||
options = options || {};
|
||||
if (!('data' in options)) {
|
||||
options.data = true;
|
||||
}
|
||||
var ast = Handlebars.parse(input);
|
||||
var environment = new Handlebars.Compiler().compile(ast, options);
|
||||
return new Handlebars.JavaScriptCompiler().compile(environment, options);
|
||||
};
|
||||
|
||||
Handlebars.compile = function(string, options) {
|
||||
options = options || {};
|
||||
Handlebars.compile = function(input, options) {
|
||||
if (!input || (typeof input !== 'string' && input.constructor !== Handlebars.AST.ProgramNode)) {
|
||||
throw new Handlebars.Exception("You must pass a string or Handlebars AST to Handlebars.compile. You passed " + input);
|
||||
}
|
||||
|
||||
options = options || {};
|
||||
if (!('data' in options)) {
|
||||
options.data = true;
|
||||
}
|
||||
var compiled;
|
||||
function compile() {
|
||||
var ast = Handlebars.parse(string);
|
||||
var ast = Handlebars.parse(input);
|
||||
var environment = new Handlebars.Compiler().compile(ast, options);
|
||||
var templateSpec = new Handlebars.JavaScriptCompiler().compile(environment, options, undefined, true);
|
||||
return Handlebars.template(templateSpec);
|
||||
@@ -1062,5 +1267,11 @@ Handlebars.compile = function(string, options) {
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
// END(BROWSER)
|
||||
|
||||
return Handlebars;
|
||||
|
||||
};
|
||||
|
||||
|
||||
|
||||
@@ -1,7 +1,16 @@
|
||||
// Each of these module will augment the Handlebars object as it loads. No need to perform addition operations
|
||||
module.exports = require("./base");
|
||||
require("./visitor");
|
||||
require("./printer");
|
||||
module.exports.attach = function(Handlebars) {
|
||||
|
||||
require("./ast");
|
||||
require("./compiler");
|
||||
var visitor = require("./visitor"),
|
||||
printer = require("./printer"),
|
||||
ast = require("./ast"),
|
||||
compiler = require("./compiler");
|
||||
|
||||
visitor.attach(Handlebars);
|
||||
printer.attach(Handlebars);
|
||||
ast.attach(Handlebars);
|
||||
compiler.attach(Handlebars);
|
||||
|
||||
return Handlebars;
|
||||
|
||||
};
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
var Handlebars = require("./base");
|
||||
exports.attach = function(Handlebars) {
|
||||
|
||||
// BEGIN(BROWSER)
|
||||
|
||||
Handlebars.PrintVisitor = function() { this.padding = 0; };
|
||||
Handlebars.PrintVisitor.prototype = new Handlebars.Visitor();
|
||||
|
||||
@@ -72,7 +73,7 @@ Handlebars.PrintVisitor.prototype.mustache = function(mustache) {
|
||||
};
|
||||
|
||||
Handlebars.PrintVisitor.prototype.partial = function(partial) {
|
||||
var content = this.accept(partial.id);
|
||||
var content = this.accept(partial.partialName);
|
||||
if(partial.context) { content = content + " " + this.accept(partial.context); }
|
||||
return this.pad("{{> " + content + " }}");
|
||||
};
|
||||
@@ -111,6 +112,10 @@ Handlebars.PrintVisitor.prototype.ID = function(id) {
|
||||
}
|
||||
};
|
||||
|
||||
Handlebars.PrintVisitor.prototype.PARTIAL_NAME = function(partialName) {
|
||||
return "PARTIAL:" + partialName.name;
|
||||
};
|
||||
|
||||
Handlebars.PrintVisitor.prototype.DATA = function(data) {
|
||||
return "@" + data.id;
|
||||
};
|
||||
@@ -124,4 +129,6 @@ Handlebars.PrintVisitor.prototype.comment = function(comment) {
|
||||
};
|
||||
// END(BROWSER)
|
||||
|
||||
exports.PrintVisitor = Handlebars.PrintVisitor;
|
||||
return Handlebars;
|
||||
};
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
var Handlebars = require("./base");
|
||||
exports.attach = function(Handlebars) {
|
||||
|
||||
// BEGIN(BROWSER)
|
||||
|
||||
@@ -9,5 +9,10 @@ Handlebars.Visitor.prototype = {
|
||||
return this[object.type](object);
|
||||
}
|
||||
};
|
||||
|
||||
// END(BROWSER)
|
||||
|
||||
return Handlebars;
|
||||
};
|
||||
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
var Handlebars = require("./base");
|
||||
exports.attach = function(Handlebars) {
|
||||
|
||||
// BEGIN(BROWSER)
|
||||
|
||||
Handlebars.VM = {
|
||||
template: function(templateSpec) {
|
||||
// Just add water
|
||||
@@ -20,12 +21,32 @@ Handlebars.VM = {
|
||||
}
|
||||
},
|
||||
programWithDepth: Handlebars.VM.programWithDepth,
|
||||
noop: Handlebars.VM.noop
|
||||
noop: Handlebars.VM.noop,
|
||||
compilerInfo: null
|
||||
};
|
||||
|
||||
return function(context, options) {
|
||||
options = options || {};
|
||||
return templateSpec.call(container, Handlebars, context, options.helpers, options.partials, options.data);
|
||||
var result = templateSpec.call(container, Handlebars, context, options.helpers, options.partials, options.data);
|
||||
|
||||
var compilerInfo = container.compilerInfo || [],
|
||||
compilerRevision = compilerInfo[0] || 1,
|
||||
currentRevision = Handlebars.COMPILER_REVISION;
|
||||
|
||||
if (compilerRevision !== currentRevision) {
|
||||
if (compilerRevision < currentRevision) {
|
||||
var runtimeVersions = Handlebars.REVISION_CHANGES[currentRevision],
|
||||
compilerVersions = Handlebars.REVISION_CHANGES[compilerRevision];
|
||||
throw "Template was precompiled with an older version of Handlebars than the current runtime. "+
|
||||
"Please update your precompiler to a newer version ("+runtimeVersions+") or downgrade your runtime to an older version ("+compilerVersions+").";
|
||||
} else {
|
||||
// Use the embedded version info since the runtime doesn't know about this revision yet
|
||||
throw "Template was precompiled with a newer version of Handlebars than the current runtime. "+
|
||||
"Please update your runtime to a newer version ("+compilerInfo[1]+").";
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
};
|
||||
},
|
||||
|
||||
@@ -56,7 +77,7 @@ Handlebars.VM = {
|
||||
} else if (!Handlebars.compile) {
|
||||
throw new Handlebars.Exception("The partial " + name + " could not be compiled when running in runtime-only mode");
|
||||
} else {
|
||||
partials[name] = Handlebars.compile(partial);
|
||||
partials[name] = Handlebars.compile(partial, {data: data !== undefined});
|
||||
return partials[name](context, options);
|
||||
}
|
||||
}
|
||||
@@ -66,3 +87,6 @@ Handlebars.template = Handlebars.VM.template;
|
||||
|
||||
// END(BROWSER)
|
||||
|
||||
return Handlebars;
|
||||
|
||||
};
|
||||
|
||||
+13
-11
@@ -1,14 +1,16 @@
|
||||
var Handlebars = require("./base");
|
||||
exports.attach = function(Handlebars) {
|
||||
|
||||
// BEGIN(BROWSER)
|
||||
|
||||
var errorProps = ['description', 'fileName', 'lineNumber', 'message', 'name', 'number', 'stack'];
|
||||
|
||||
Handlebars.Exception = function(message) {
|
||||
var tmp = Error.prototype.constructor.apply(this, arguments);
|
||||
|
||||
for (var p in tmp) {
|
||||
if (tmp.hasOwnProperty(p)) { this[p] = tmp[p]; }
|
||||
// Unfortunately errors are not enumerable in Chrome (at least), so `for prop in tmp` doesn't work.
|
||||
for (var idx = 0; idx < errorProps.length; idx++) {
|
||||
this[errorProps[idx]] = tmp[errorProps[idx]];
|
||||
}
|
||||
|
||||
this.message = tmp.message;
|
||||
};
|
||||
Handlebars.Exception.prototype = new Error();
|
||||
|
||||
@@ -22,6 +24,7 @@ Handlebars.SafeString.prototype.toString = function() {
|
||||
|
||||
(function() {
|
||||
var escape = {
|
||||
"&": "&",
|
||||
"<": "<",
|
||||
">": ">",
|
||||
'"': """,
|
||||
@@ -29,7 +32,7 @@ Handlebars.SafeString.prototype.toString = function() {
|
||||
"`": "`"
|
||||
};
|
||||
|
||||
var badChars = /&(?!\w+;)|[<>"'`]/g;
|
||||
var badChars = /[&<>"'`]/g;
|
||||
var possible = /[&<>"'`]/;
|
||||
|
||||
var escapeChar = function(chr) {
|
||||
@@ -50,11 +53,7 @@ Handlebars.SafeString.prototype.toString = function() {
|
||||
},
|
||||
|
||||
isEmpty: function(value) {
|
||||
if (typeof value === "undefined") {
|
||||
return true;
|
||||
} else if (value === null) {
|
||||
return true;
|
||||
} else if (value === false) {
|
||||
if (!value && value !== 0) {
|
||||
return true;
|
||||
} else if(Object.prototype.toString.call(value) === "[object Array]" && value.length === 0) {
|
||||
return true;
|
||||
@@ -64,5 +63,8 @@ Handlebars.SafeString.prototype.toString = function() {
|
||||
}
|
||||
};
|
||||
})();
|
||||
|
||||
// END(BROWSER)
|
||||
|
||||
return Handlebars;
|
||||
};
|
||||
|
||||
+7
-3
@@ -1,14 +1,14 @@
|
||||
{
|
||||
"name": "handlebars",
|
||||
"description": "Extension of the Mustache logicless template language",
|
||||
"version": "1.0.6-2",
|
||||
"version": "1.0.9",
|
||||
"homepage": "http://www.handlebarsjs.com/",
|
||||
"keywords": [
|
||||
"handlebars mustache template html"
|
||||
],
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git://github.com/kpdecker/handlebars.js.git"
|
||||
"url": "git://github.com/wycats/handlebars.js.git"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=0.4.7"
|
||||
@@ -18,7 +18,11 @@
|
||||
"uglify-js": "~1.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"mocha": "*"
|
||||
"benchmark": "~1.0",
|
||||
"dust": "~0.3",
|
||||
"jison": "~0.3",
|
||||
"mocha": "*",
|
||||
"mustache": "~0.7.2"
|
||||
},
|
||||
"main": "lib/handlebars.js",
|
||||
"bin": {
|
||||
|
||||
+157
-2
@@ -114,6 +114,10 @@ describe "Parser" do
|
||||
"@#{id}"
|
||||
end
|
||||
|
||||
def partial_name(name)
|
||||
"PARTIAL:#{name}"
|
||||
end
|
||||
|
||||
def path(*parts)
|
||||
"PATH:#{parts.join("/")}"
|
||||
end
|
||||
@@ -172,6 +176,10 @@ describe "Parser" do
|
||||
mustache id("foo"), [], hash(["bar", "ID:baz"], ["bat", "\"bam\""])
|
||||
end
|
||||
|
||||
ast_for("{{foo bat='bam'}}").should == root do
|
||||
mustache id("foo"), [], hash(["bat", "\"bam\""])
|
||||
end
|
||||
|
||||
ast_for("{{foo omg bar=baz bat=\"bam\"}}").should == root do
|
||||
mustache id("foo"), [id("omg")], hash(["bar", id("baz")], ["bat", string("bam")])
|
||||
end
|
||||
@@ -214,11 +222,15 @@ describe "Parser" do
|
||||
end
|
||||
|
||||
it "parses a partial" do
|
||||
ast_for("{{> foo }}").should == root { partial id("foo") }
|
||||
ast_for("{{> foo }}").should == root { partial partial_name("foo") }
|
||||
end
|
||||
|
||||
it "parses a partial with context" do
|
||||
ast_for("{{> foo bar}}").should == root { partial id("foo"), id("bar") }
|
||||
ast_for("{{> foo bar}}").should == root { partial partial_name("foo"), id("bar") }
|
||||
end
|
||||
|
||||
it "parses a partial with a complex name" do
|
||||
ast_for("{{> shared/partial}}").should == root { partial partial_name("shared/partial") }
|
||||
end
|
||||
|
||||
it "parses a comment" do
|
||||
@@ -249,6 +261,130 @@ describe "Parser" do
|
||||
end
|
||||
end
|
||||
|
||||
it "parses an inverse ('else'-style) section" do
|
||||
ast_for("{{#foo}} bar {{else}} baz {{/foo}}").should == root do
|
||||
block do
|
||||
mustache id("foo")
|
||||
|
||||
program do
|
||||
content " bar "
|
||||
end
|
||||
|
||||
inverse do
|
||||
content " baz "
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
it "parses empty blocks" do
|
||||
ast_for("{{#foo}}{{/foo}}").should == root do
|
||||
block do
|
||||
mustache id("foo")
|
||||
|
||||
program do
|
||||
# empty program
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
it "parses empty blocks with empty inverse section" do
|
||||
ast_for("{{#foo}}{{^}}{{/foo}}").should == root do
|
||||
block do
|
||||
mustache id("foo")
|
||||
|
||||
program do
|
||||
# empty program
|
||||
end
|
||||
|
||||
inverse do
|
||||
# empty inverse
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
it "parses empty blocks with empty inverse ('else'-style) section" do
|
||||
ast_for("{{#foo}}{{else}}{{/foo}}").should == root do
|
||||
block do
|
||||
mustache id("foo")
|
||||
|
||||
program do
|
||||
# empty program
|
||||
end
|
||||
|
||||
inverse do
|
||||
# empty inverse
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
it "parses non-empty blocks with empty inverse section" do
|
||||
ast_for("{{#foo}} bar {{^}}{{/foo}}").should == root do
|
||||
block do
|
||||
mustache id("foo")
|
||||
|
||||
program do
|
||||
content " bar "
|
||||
end
|
||||
|
||||
inverse do
|
||||
# empty inverse
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
it "parses non-empty blocks with empty inverse ('else'-style) section" do
|
||||
ast_for("{{#foo}} bar {{else}}{{/foo}}").should == root do
|
||||
block do
|
||||
mustache id("foo")
|
||||
|
||||
program do
|
||||
content " bar "
|
||||
end
|
||||
|
||||
inverse do
|
||||
# empty inverse
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
it "parses empty blocks with non-empty inverse section" do
|
||||
ast_for("{{#foo}}{{^}} bar {{/foo}}").should == root do
|
||||
block do
|
||||
mustache id("foo")
|
||||
|
||||
program do
|
||||
# empty program
|
||||
end
|
||||
|
||||
inverse do
|
||||
content " bar "
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
it "parses empty blocks with non-empty inverse ('else'-style) section" do
|
||||
ast_for("{{#foo}}{{else}} bar {{/foo}}").should == root do
|
||||
block do
|
||||
mustache id("foo")
|
||||
|
||||
program do
|
||||
# empty program
|
||||
end
|
||||
|
||||
inverse do
|
||||
content " bar "
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
it "parses a standalone inverse section" do
|
||||
ast_for("{{^foo}}bar{{/foo}}").should == root do
|
||||
block do
|
||||
@@ -275,4 +411,23 @@ describe "Parser" do
|
||||
it "knows how to report the correct line number in errors when the first character is a newline" do
|
||||
lambda { ast_for("\n\nhello\n\nmy\n\n{{foo}") }.should raise_error(V8::JSError, /Parse error on line 7/m)
|
||||
end
|
||||
|
||||
context "externally compiled AST" do
|
||||
|
||||
it "can pass through an already-compiled AST" do
|
||||
ast_for(@context.eval('new Handlebars.AST.ProgramNode([ new Handlebars.AST.ContentNode("Hello")]);')).should == root do
|
||||
content "Hello"
|
||||
end
|
||||
end
|
||||
|
||||
it "can pass through an already-compiled AST via compile/precompile" do
|
||||
@context = Handlebars::Spec::FULL_CONTEXT
|
||||
|
||||
code = 'Handlebars.compile(new Handlebars.AST.ProgramNode([ new Handlebars.AST.ContentNode("Hello")]))();'
|
||||
@context.eval(code).should == "Hello"
|
||||
|
||||
code = @context.eval 'Handlebars.precompile(new Handlebars.AST.ProgramNode([ new Handlebars.AST.ContentNode("Hello")]))'
|
||||
@context.eval("(#{code})(this)").should == "Hello"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
+234
-37
@@ -20,27 +20,33 @@ if (!Handlebars) {
|
||||
return Handlebars.compile(template, options);
|
||||
}
|
||||
};
|
||||
} else {
|
||||
var _equal = equal;
|
||||
equals = equal = function(a, b, msg) {
|
||||
// Allow exec with missing message params
|
||||
_equal(a, b, msg || '');
|
||||
};
|
||||
}
|
||||
|
||||
suite("basic context");
|
||||
|
||||
Handlebars.registerHelper('helperMissing', function(helper, context) {
|
||||
if(helper === "link_to") {
|
||||
return new Handlebars.SafeString("<a>" + context + "</a>");
|
||||
}
|
||||
});
|
||||
|
||||
function shouldCompileTo(string, hashOrArray, expected, message) {
|
||||
shouldCompileToWithPartials(string, hashOrArray, false, expected, message);
|
||||
}
|
||||
|
||||
function shouldCompileToWithPartials(string, hashOrArray, partials, expected, message) {
|
||||
var result = compileWithPartials(string, hashOrArray, partials);
|
||||
equal(result, expected, "'" + expected + "' should === '" + result + "': " + message);
|
||||
}
|
||||
|
||||
function compileWithPartials(string, hashOrArray, partials) {
|
||||
var template = CompilerContext[partials ? 'compileWithPartial' : 'compile'](string), ary;
|
||||
if(Object.prototype.toString.call(hashOrArray) === "[object Array]") {
|
||||
var helpers = hashOrArray[1];
|
||||
|
||||
if(helpers) {
|
||||
for(var prop in Handlebars.helpers) {
|
||||
helpers[prop] = Handlebars.helpers[prop];
|
||||
helpers[prop] = helpers[prop] || Handlebars.helpers[prop];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -51,18 +57,31 @@ function shouldCompileToWithPartials(string, hashOrArray, partials, expected, me
|
||||
ary = [hashOrArray];
|
||||
}
|
||||
|
||||
var result = template.apply(this, ary);
|
||||
equal(result, expected, "'" + expected + "' should === '" + result + "': " + message);
|
||||
return template.apply(this, ary);
|
||||
}
|
||||
|
||||
function shouldThrow(fn, exception, message) {
|
||||
var caught = false;
|
||||
var caught = false,
|
||||
exType, exMessage;
|
||||
|
||||
if (exception instanceof Array) {
|
||||
exType = exception[0];
|
||||
exMessage = exception[1];
|
||||
} else if (typeof exception === 'string') {
|
||||
exType = Error;
|
||||
exMessage = exception;
|
||||
} else {
|
||||
exType = exception;
|
||||
}
|
||||
|
||||
try {
|
||||
fn();
|
||||
}
|
||||
catch (e) {
|
||||
if (e instanceof exception) {
|
||||
caught = true;
|
||||
if (e instanceof exType) {
|
||||
if (!exMessage || e.message === exMessage) {
|
||||
caught = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -123,6 +142,8 @@ test("escaping expressions", function() {
|
||||
shouldCompileTo("{{awesome}}", {awesome: "&\"'`\\<>"}, '&"'`\\<>',
|
||||
"by default expressions should be escaped");
|
||||
|
||||
shouldCompileTo("{{awesome}}", {awesome: "Escaped, <b> looks like: <b>"}, 'Escaped, <b> looks like: &lt;b&gt;',
|
||||
"escaping should properly handle amperstands");
|
||||
});
|
||||
|
||||
test("functions returning safestrings shouldn't be escaped", function() {
|
||||
@@ -134,10 +155,14 @@ test("functions returning safestrings shouldn't be escaped", function() {
|
||||
test("functions", function() {
|
||||
shouldCompileTo("{{awesome}}", {awesome: function() { return "Awesome"; }}, "Awesome",
|
||||
"functions are called and render their output");
|
||||
shouldCompileTo("{{awesome}}", {awesome: function() { return this.more; }, more: "More awesome"}, "More awesome",
|
||||
"functions are bound to the context");
|
||||
});
|
||||
|
||||
test("paths with hyphens", function() {
|
||||
shouldCompileTo("{{foo-bar}}", {"foo-bar": "baz"}, "baz", "Paths can contain hyphens (-)");
|
||||
shouldCompileTo("{{foo.foo-bar}}", {foo: {"foo-bar": "baz"}}, "baz", "Paths can contain hyphens (-)");
|
||||
shouldCompileTo("{{foo/foo-bar}}", {foo: {"foo-bar": "baz"}}, "baz", "Paths can contain hyphens (-)");
|
||||
});
|
||||
|
||||
test("nested paths", function() {
|
||||
@@ -151,20 +176,15 @@ test("nested paths with empty string value", function() {
|
||||
});
|
||||
|
||||
test("literal paths", function() {
|
||||
shouldCompileTo("Goodbye {{[@alan]/expression}} world!", {"@alan": {expression: "beautiful"}},
|
||||
"Goodbye beautiful world!", "Literal paths can be used");
|
||||
shouldCompileTo("Goodbye {{[@alan]/expression}} world!", {"@alan": {expression: "beautiful"}},
|
||||
"Goodbye beautiful world!", "Literal paths can be used");
|
||||
shouldCompileTo("Goodbye {{[foo bar]/expression}} world!", {"foo bar": {expression: "beautiful"}},
|
||||
"Goodbye beautiful world!", "Literal paths can be used");
|
||||
});
|
||||
|
||||
test("--- TODO --- bad idea nested paths", function() {
|
||||
return;
|
||||
var hash = {goodbyes: [{text: "goodbye"}, {text: "Goodbye"}, {text: "GOODBYE"}], world: "world"};
|
||||
shouldThrow(function() {
|
||||
CompilerContext.compile("{{#goodbyes}}{{../name/../name}}{{/goodbyes}}")(hash);
|
||||
}, Handlebars.Exception,
|
||||
"Cannot jump (..) into previous context after moving into a context.");
|
||||
|
||||
var string = "{{#goodbyes}}{{.././world}} {{/goodbyes}}";
|
||||
shouldCompileTo(string, hash, "world world world ", "Same context (.) is ignored in paths");
|
||||
test('literal references', function() {
|
||||
shouldCompileTo("Goodbye {{[foo bar]}} world!", {"foo bar": "beautiful"},
|
||||
"Goodbye beautiful world!", "Literal paths can be used");
|
||||
});
|
||||
|
||||
test("that current context path ({{.}}) doesn't hit helpers", function() {
|
||||
@@ -187,8 +207,15 @@ test("this keyword in paths", function() {
|
||||
shouldCompileTo(string, hash, "helloHelloHELLO", "This keyword evaluates in more complex paths");
|
||||
});
|
||||
|
||||
test("this keyword nested inside path", function() {
|
||||
var string = "{{#hellos}}{{text/this/foo}}{{/hellos}}";
|
||||
shouldThrow(function() {
|
||||
CompilerContext.compile(string);
|
||||
}, Error, "Should throw exception");
|
||||
});
|
||||
|
||||
test("this keyword in helpers", function() {
|
||||
var helpers = {foo: function(value, options) {
|
||||
var helpers = {foo: function(value) {
|
||||
return 'bar ' + value;
|
||||
}};
|
||||
var string = "{{#goodbyes}}{{foo this}}{{/goodbyes}}";
|
||||
@@ -201,6 +228,13 @@ test("this keyword in helpers", function() {
|
||||
shouldCompileTo(string, [hash, helpers], "bar hellobar Hellobar HELLO", "This keyword evaluates in more complex paths");
|
||||
});
|
||||
|
||||
test("this keyword nested inside helpers param", function() {
|
||||
var string = "{{#hellos}}{{foo text/this/foo}}{{/hellos}}";
|
||||
shouldThrow(function() {
|
||||
CompilerContext.compile(string);
|
||||
}, Error, "Should throw exception");
|
||||
});
|
||||
|
||||
suite("inverted sections");
|
||||
|
||||
test("inverted sections with unset value", function() {
|
||||
@@ -234,6 +268,16 @@ test("array", function() {
|
||||
|
||||
});
|
||||
|
||||
test("array with @index", function() {
|
||||
var string = "{{#goodbyes}}{{@index}}. {{text}}! {{/goodbyes}}cruel {{world}}!";
|
||||
var hash = {goodbyes: [{text: "goodbye"}, {text: "Goodbye"}, {text: "GOODBYE"}], world: "world"};
|
||||
|
||||
var template = CompilerContext.compile(string);
|
||||
var result = template(hash);
|
||||
|
||||
equal(result, "0. goodbye! 1. Goodbye! 2. GOODBYE! cruel world!", "The @index variable is used");
|
||||
});
|
||||
|
||||
test("empty block", function() {
|
||||
var string = "{{#goodbyes}}{{/goodbyes}}cruel {{world}}!";
|
||||
var hash = {goodbyes: [{text: "goodbye"}, {text: "Goodbye"}, {text: "GOODBYE"}], world: "world"};
|
||||
@@ -256,6 +300,14 @@ test("block with complex lookup", function() {
|
||||
"Templates can access variables in contexts up the stack with relative path syntax");
|
||||
});
|
||||
|
||||
test("block with complex lookup using nested context", function() {
|
||||
var string = "{{#goodbyes}}{{text}} cruel {{foo/../name}}! {{/goodbyes}}";
|
||||
|
||||
shouldThrow(function() {
|
||||
CompilerContext.compile(string);
|
||||
}, Error, "Should throw exception");
|
||||
});
|
||||
|
||||
test("helper with complex lookup$", function() {
|
||||
var string = "{{#goodbyes}}{{{link ../prefix}}}{{/goodbyes}}";
|
||||
var hash = {prefix: "/root", goodbyes: [{text: "Goodbye", url: "goodbye"}]};
|
||||
@@ -421,12 +473,17 @@ test("providing a helpers hash", function() {
|
||||
"Goodbye cruel world!", "helpers hash is available inside other blocks");
|
||||
});
|
||||
|
||||
test("in cases of conflict, the explicit hash wins", function() {
|
||||
|
||||
test("in cases of conflict, helpers win", function() {
|
||||
shouldCompileTo("{{{lookup}}}", [{lookup: 'Explicit'}, {lookup: function() { return 'helpers'; }}], "helpers",
|
||||
"helpers hash has precedence escaped expansion");
|
||||
shouldCompileTo("{{lookup}}", [{lookup: 'Explicit'}, {lookup: function() { return 'helpers'; }}], "helpers",
|
||||
"helpers hash has precedence simple expansion");
|
||||
});
|
||||
|
||||
test("the helpers hash is available is nested contexts", function() {
|
||||
|
||||
shouldCompileTo("{{#outer}}{{#inner}}{{helper}}{{/inner}}{{/outer}}",
|
||||
[{'outer': {'inner': {'unused':[]}}}, {'helper': function() { return 'helper'; }}], "helper",
|
||||
"helpers hash is available in nested contexts.");
|
||||
});
|
||||
|
||||
suite("partials");
|
||||
@@ -459,14 +516,14 @@ test("rendering undefined partial throws an exception", function() {
|
||||
shouldThrow(function() {
|
||||
var template = CompilerContext.compile("{{> whatever}}");
|
||||
template();
|
||||
}, Handlebars.Exception, "Should throw exception");
|
||||
}, [Handlebars.Exception, 'The partial whatever could not be found'], "Should throw exception");
|
||||
});
|
||||
|
||||
test("rendering template partial in vm mode throws an exception", function() {
|
||||
shouldThrow(function() {
|
||||
var template = CompilerContext.compile("{{> whatever}}");
|
||||
template();
|
||||
}, Handlebars.Exception, "Should throw exception");
|
||||
}, [Handlebars.Exception, 'The partial whatever could not be found'], "Should throw exception");
|
||||
});
|
||||
|
||||
test("rendering function partial in vm mode", function() {
|
||||
@@ -486,13 +543,21 @@ test("GH-14: a partial preceding a selector", function() {
|
||||
shouldCompileToWithPartials(string, [hash, {}, {dude:dude}], true, "Dudes: Jeepers Creepers", "Regular selectors can follow a partial");
|
||||
});
|
||||
|
||||
test("Partials with literal paths", function() {
|
||||
var string = "Dudes: {{> [dude]}}";
|
||||
test("Partials with slash paths", function() {
|
||||
var string = "Dudes: {{> shared/dude}}";
|
||||
var dude = "{{name}}";
|
||||
var hash = {name:"Jeepers", another_dude:"Creepers"};
|
||||
shouldCompileToWithPartials(string, [hash, {}, {dude:dude}], true, "Dudes: Jeepers", "Partials can use literal paths");
|
||||
shouldCompileToWithPartials(string, [hash, {}, {'shared/dude':dude}], true, "Dudes: Jeepers", "Partials can use literal paths");
|
||||
});
|
||||
|
||||
test("Partials with integer path", function() {
|
||||
var string = "Dudes: {{> 404}}";
|
||||
var dude = "{{name}}";
|
||||
var hash = {name:"Jeepers", another_dude:"Creepers"};
|
||||
shouldCompileToWithPartials(string, [hash, {}, {404:dude}], true, "Dudes: Jeepers", "Partials can use literal paths");
|
||||
});
|
||||
|
||||
|
||||
suite("String literal parameters");
|
||||
|
||||
test("simple literals work", function() {
|
||||
@@ -557,10 +622,25 @@ test("constructing a safestring from a string and checking its type", function()
|
||||
suite("helperMissing");
|
||||
|
||||
test("if a context is not found, helperMissing is used", function() {
|
||||
shouldThrow(function() {
|
||||
var template = CompilerContext.compile("{{hello}} {{link_to world}}");
|
||||
template({});
|
||||
}, [Error, "Could not find property 'link_to'"], "Should throw exception");
|
||||
});
|
||||
|
||||
test("if a context is not found, custom helperMissing is used", function() {
|
||||
var string = "{{hello}} {{link_to world}}";
|
||||
var context = { hello: "Hello", world: "world" };
|
||||
|
||||
shouldCompileTo(string, context, "Hello <a>world</a>");
|
||||
var helpers = {
|
||||
helperMissing: function(helper, context) {
|
||||
if(helper === "link_to") {
|
||||
return new Handlebars.SafeString("<a>" + context + "</a>");
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
shouldCompileTo(string, [context, helpers], "Hello <a>world</a>");
|
||||
});
|
||||
|
||||
suite("knownHelpers");
|
||||
@@ -602,6 +682,11 @@ test("Invert blocks work in knownHelpers only mode", function() {
|
||||
var result = template({foo: false});
|
||||
equal(result, "bar", "'bar' should === '" + result);
|
||||
});
|
||||
test("Functions are bound to the context in knownHelpers only mode", function() {
|
||||
var template = CompilerContext.compile("{{foo}}", {knownHelpersOnly: true});
|
||||
var result = template({foo: function() { return this.bar; }, bar: 'bar'});
|
||||
equal(result, "bar", "'bar' should === '" + result);
|
||||
});
|
||||
|
||||
suite("blockHelperMissing");
|
||||
|
||||
@@ -610,6 +695,11 @@ test("lambdas are resolved by blockHelperMissing, not handlebars proper", functi
|
||||
var data = { truthy: function() { return true; } };
|
||||
shouldCompileTo(string, data, "yep");
|
||||
});
|
||||
test("lambdas resolved by blockHelperMissing are bound to the context", function() {
|
||||
var string = "{{#truthy}}yep{{/truthy}}";
|
||||
var boundData = { truthy: function() { return this.truthiness(); }, truthiness: function() { return false; } };
|
||||
shouldCompileTo(string, boundData, "");
|
||||
});
|
||||
|
||||
var teardown;
|
||||
suite("built-in helpers", {
|
||||
@@ -659,6 +749,22 @@ test("each", function() {
|
||||
"each with array argument ignores the contents when empty");
|
||||
});
|
||||
|
||||
test("each with an object and @key", function() {
|
||||
var string = "{{#each goodbyes}}{{@key}}. {{text}}! {{/each}}cruel {{world}}!";
|
||||
var hash = {goodbyes: {"<b>#1</b>": {text: "goodbye"}, 2: {text: "GOODBYE"}}, world: "world"};
|
||||
|
||||
// Object property iteration order is undefined according to ECMA spec,
|
||||
// so we need to check both possible orders
|
||||
// @see http://stackoverflow.com/questions/280713/elements-order-in-a-for-in-loop
|
||||
var actual = compileWithPartials(string, hash);
|
||||
var expected1 = "<b>#1</b>. goodbye! 2. GOODBYE! cruel world!";
|
||||
var expected2 = "2. GOODBYE! <b>#1</b>. goodbye! cruel world!";
|
||||
|
||||
ok(actual === expected1 || actual === expected2, "each with object argument iterates over the contents when not empty");
|
||||
shouldCompileTo(string, {goodbyes: [], world: "world"}, "cruel world!",
|
||||
"each with object argument ignores the contents when empty");
|
||||
});
|
||||
|
||||
test("each with @index", function() {
|
||||
var string = "{{#each goodbyes}}{{@index}}. {{text}}! {{/each}}cruel {{world}}!";
|
||||
var hash = {goodbyes: [{text: "goodbye"}, {text: "Goodbye"}, {text: "GOODBYE"}], world: "world"};
|
||||
@@ -669,16 +775,34 @@ test("each with @index", function() {
|
||||
equal(result, "0. goodbye! 1. Goodbye! 2. GOODBYE! cruel world!", "The @index variable is used");
|
||||
});
|
||||
|
||||
test("data passed to helpers", function() {
|
||||
var string = "{{#each letters}}{{this}}{{detectDataInsideEach}}{{/each}}";
|
||||
var hash = {letters: ['a', 'b', 'c']};
|
||||
|
||||
var template = CompilerContext.compile(string);
|
||||
var result = template(hash, {
|
||||
data: {
|
||||
exclaim: '!'
|
||||
}
|
||||
});
|
||||
equal(result, 'a!b!c!', 'should output data');
|
||||
});
|
||||
|
||||
Handlebars.registerHelper('detectDataInsideEach', function(options) {
|
||||
return options.data && options.data.exclaim;
|
||||
});
|
||||
|
||||
test("log", function() {
|
||||
var string = "{{log blah}}";
|
||||
var hash = { blah: "whee" };
|
||||
|
||||
var logArg;
|
||||
var levelArg, logArg;
|
||||
var originalLog = Handlebars.log;
|
||||
Handlebars.log = function(arg){ logArg = arg; };
|
||||
Handlebars.log = function(level, arg){ levelArg = level, logArg = arg; };
|
||||
teardown = function(){ Handlebars.log = originalLog; };
|
||||
|
||||
shouldCompileTo(string, hash, "", "log should not display");
|
||||
equals(1, levelArg, "should call log with 1");
|
||||
equals("whee", logArg, "should call log with 'whee'");
|
||||
});
|
||||
|
||||
@@ -1016,6 +1140,19 @@ test("block helpers can take an optional hash", function() {
|
||||
equals(result, "GOODBYE CRUEL world 12 TIMES", "Hash parameters output");
|
||||
});
|
||||
|
||||
test("block helpers can take an optional hash with single quoted stings", function() {
|
||||
var template = CompilerContext.compile("{{#goodbye cruel='CRUEL' times=12}}world{{/goodbye}}");
|
||||
|
||||
var helpers = {
|
||||
goodbye: function(options) {
|
||||
return "GOODBYE " + options.hash.cruel + " " + options.fn(this) + " " + options.hash.times + " TIMES";
|
||||
}
|
||||
};
|
||||
|
||||
var result = template({}, {helpers: helpers});
|
||||
equals(result, "GOODBYE CRUEL world 12 TIMES", "Hash parameters output");
|
||||
});
|
||||
|
||||
test("block helpers can take an optional hash with booleans", function() {
|
||||
var helpers = {
|
||||
goodbye: function(options) {
|
||||
@@ -1043,7 +1180,7 @@ test("arguments to helpers can be retrieved from options hash in string form", f
|
||||
var template = CompilerContext.compile('{{wycats is.a slave.driver}}', {stringParams: true});
|
||||
|
||||
var helpers = {
|
||||
wycats: function(passiveVoice, noun, options) {
|
||||
wycats: function(passiveVoice, noun) {
|
||||
return "HELP ME MY BOSS " + passiveVoice + ' ' + noun;
|
||||
}
|
||||
};
|
||||
@@ -1091,6 +1228,48 @@ test("when inside a block in String mode, .. passes the appropriate context in t
|
||||
equals(result, "STOP ME FROM READING HACKER NEWS I need-a dad.joke", "Proper context variable output");
|
||||
});
|
||||
|
||||
test("in string mode, information about the types is passed along", function() {
|
||||
var template = CompilerContext.compile('{{tomdale "need" dad.joke true false}}', { stringParams: true });
|
||||
|
||||
var helpers = {
|
||||
tomdale: function(desire, noun, trueBool, falseBool, options) {
|
||||
equal(options.types[0], 'STRING', "the string type is passed");
|
||||
equal(options.types[1], 'ID', "the expression type is passed");
|
||||
equal(options.types[2], 'BOOLEAN', "the expression type is passed");
|
||||
equal(desire, "need", "the string form is passed for strings");
|
||||
equal(noun, "dad.joke", "the string form is passed for expressions");
|
||||
equal(trueBool, true, "raw booleans are passed through");
|
||||
equal(falseBool, false, "raw booleans are passed through");
|
||||
return "Helper called";
|
||||
}
|
||||
};
|
||||
|
||||
var result = template({}, { helpers: helpers });
|
||||
equal(result, "Helper called");
|
||||
});
|
||||
|
||||
test("in string mode, hash parameters get type information", function() {
|
||||
var template = CompilerContext.compile('{{tomdale he.says desire="need" noun=dad.joke bool=true}}', { stringParams: true });
|
||||
|
||||
var helpers = {
|
||||
tomdale: function(exclamation, options) {
|
||||
equal(exclamation, "he.says");
|
||||
equal(options.types[0], "ID");
|
||||
|
||||
equal(options.hashTypes.desire, "STRING");
|
||||
equal(options.hashTypes.noun, "ID");
|
||||
equal(options.hashTypes.bool, "BOOLEAN");
|
||||
equal(options.hash.desire, "need");
|
||||
equal(options.hash.noun, "dad.joke");
|
||||
equal(options.hash.bool, true);
|
||||
return "Helper called";
|
||||
}
|
||||
};
|
||||
|
||||
var result = template({}, { helpers: helpers });
|
||||
equal(result, "Helper called");
|
||||
});
|
||||
|
||||
test("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});
|
||||
|
||||
@@ -1171,3 +1350,21 @@ test("bug reported by @fat where lambdas weren't being properly resolved", funct
|
||||
var output = "<strong>This is a slightly more complicated blah.</strong>.\n\nCheck this out:\n\n<ul>\n\n<li class=one>@fat</li>\n\n<li class=two>@dhg</li>\n\n<li class=three>@sayrer</li>\n</ul>.\n\n";
|
||||
shouldCompileTo(string, data, output);
|
||||
});
|
||||
|
||||
test("Passing falsy values to Handlebars.compile throws an error", function() {
|
||||
shouldThrow(function() {
|
||||
CompilerContext.compile(null);
|
||||
}, "You must pass a string or Handlebars AST to Handlebars.compile. You passed null");
|
||||
});
|
||||
|
||||
test('GH-408: Multiple loops fail', function() {
|
||||
var context = [
|
||||
{ name: "John Doe", location: { city: "Chicago" } },
|
||||
{ name: "Jane Doe", location: { city: "New York"} }
|
||||
];
|
||||
|
||||
var template = CompilerContext.compile('{{#.}}{{name}}{{/.}}{{#.}}{{name}}{{/.}}{{#.}}{{name}}{{/.}}');
|
||||
|
||||
var result = template(context);
|
||||
equals(result, "John DoeJane DoeJohn DoeJane DoeJohn DoeJane Doe", 'It should output multiple times');
|
||||
});
|
||||
|
||||
+41
-8
@@ -51,6 +51,15 @@ describe "Tokenizer" do
|
||||
result[4].should be_token("CONTENT", "{{bar}} ")
|
||||
end
|
||||
|
||||
it "supports escaping multiple delimiters" do
|
||||
result = tokenize("{{foo}} \\{{bar}} \\{{baz}}")
|
||||
result.should match_tokens(%w(OPEN ID CLOSE CONTENT CONTENT CONTENT))
|
||||
|
||||
result[3].should be_token("CONTENT", " ")
|
||||
result[4].should be_token("CONTENT", "{{bar}} ")
|
||||
result[5].should be_token("CONTENT", "{{baz}}")
|
||||
end
|
||||
|
||||
it "supports escaping a triple stash" do
|
||||
result = tokenize("{{foo}} \\{{{bar}}} {{baz}}")
|
||||
result.should match_tokens(%w(OPEN ID CLOSE CONTENT CONTENT OPEN ID CLOSE))
|
||||
@@ -123,24 +132,24 @@ describe "Tokenizer" do
|
||||
result[4].should be_token("CONTENT", " baz")
|
||||
end
|
||||
|
||||
it "tokenizes a partial as 'OPEN_PARTIAL ID CLOSE'" do
|
||||
it "tokenizes a partial as 'OPEN_PARTIAL PARTIAL_NAME CLOSE'" do
|
||||
result = tokenize("{{> foo}}")
|
||||
result.should match_tokens(%w(OPEN_PARTIAL ID CLOSE))
|
||||
result.should match_tokens(%w(OPEN_PARTIAL PARTIAL_NAME CLOSE))
|
||||
end
|
||||
|
||||
it "tokenizes a partial with context as 'OPEN_PARTIAL ID ID CLOSE'" do
|
||||
it "tokenizes a partial with context as 'OPEN_PARTIAL PARTIAL_NAME ID CLOSE'" do
|
||||
result = tokenize("{{> foo bar }}")
|
||||
result.should match_tokens(%w(OPEN_PARTIAL ID ID CLOSE))
|
||||
result.should match_tokens(%w(OPEN_PARTIAL PARTIAL_NAME ID CLOSE))
|
||||
end
|
||||
|
||||
it "tokenizes a partial without spaces as 'OPEN_PARTIAL ID CLOSE'" do
|
||||
it "tokenizes a partial without spaces as 'OPEN_PARTIAL PARTIAL_NAME CLOSE'" do
|
||||
result = tokenize("{{>foo}}")
|
||||
result.should match_tokens(%w(OPEN_PARTIAL ID CLOSE))
|
||||
result.should match_tokens(%w(OPEN_PARTIAL PARTIAL_NAME CLOSE))
|
||||
end
|
||||
|
||||
it "tokenizes a partial space at the end as 'OPEN_PARTIAL ID CLOSE'" do
|
||||
it "tokenizes a partial space at the end as 'OPEN_PARTIAL PARTIAL_NAME CLOSE'" do
|
||||
result = tokenize("{{>foo }}")
|
||||
result.should match_tokens(%w(OPEN_PARTIAL ID CLOSE))
|
||||
result.should match_tokens(%w(OPEN_PARTIAL PARTIAL_NAME CLOSE))
|
||||
end
|
||||
|
||||
it "tokenizes a comment as 'COMMENT'" do
|
||||
@@ -149,6 +158,18 @@ describe "Tokenizer" do
|
||||
result[1].should be_token("COMMENT", " this is a comment ")
|
||||
end
|
||||
|
||||
it "tokenizes a block comment as 'COMMENT'" do
|
||||
result = tokenize("foo {{!-- this is a {{comment}} --}} bar {{ baz }}")
|
||||
result.should match_tokens(%w(CONTENT COMMENT CONTENT OPEN ID CLOSE))
|
||||
result[1].should be_token("COMMENT", " this is a {{comment}} ")
|
||||
end
|
||||
|
||||
it "tokenizes a block comment with whitespace as 'COMMENT'" do
|
||||
result = tokenize("foo {{!-- this is a\n{{comment}}\n--}} bar {{ baz }}")
|
||||
result.should match_tokens(%w(CONTENT COMMENT CONTENT OPEN ID CLOSE))
|
||||
result[1].should be_token("COMMENT", " this is a\n{{comment}}\n")
|
||||
end
|
||||
|
||||
it "tokenizes open and closing blocks as 'OPEN_BLOCK ID CLOSE ... OPEN_ENDBLOCK ID CLOSE'" do
|
||||
result = tokenize("{{#foo}}content{{/foo}}")
|
||||
result.should match_tokens(%w(OPEN_BLOCK ID CLOSE CONTENT OPEN_ENDBLOCK ID CLOSE))
|
||||
@@ -186,6 +207,12 @@ describe "Tokenizer" do
|
||||
result[3].should be_token("STRING", "baz")
|
||||
end
|
||||
|
||||
it "tokenizes mustaches with String params using single quotes as 'OPEN ID ID STRING CLOSE'" do
|
||||
result = tokenize("{{ foo bar \'baz\' }}")
|
||||
result.should match_tokens(%w(OPEN ID ID STRING CLOSE))
|
||||
result[3].should be_token("STRING", "baz")
|
||||
end
|
||||
|
||||
it "tokenizes String params with spaces inside as 'STRING'" do
|
||||
result = tokenize("{{ foo bar \"baz bat\" }}")
|
||||
result.should match_tokens(%w(OPEN ID ID STRING CLOSE))
|
||||
@@ -198,6 +225,12 @@ describe "Tokenizer" do
|
||||
result[2].should be_token("STRING", %{bar"baz})
|
||||
end
|
||||
|
||||
it "tokenizes String params using single quotes with escapes quotes as 'STRING'" do
|
||||
result = tokenize(%|{{ foo 'bar\\'baz' }}|)
|
||||
result.should match_tokens(%w(OPEN ID STRING CLOSE))
|
||||
result[2].should be_token("STRING", %{bar'baz})
|
||||
end
|
||||
|
||||
it "tokenizes numbers" do
|
||||
result = tokenize(%|{{ foo 1 }}|)
|
||||
result.should match_tokens(%w(OPEN ID INTEGER CLOSE))
|
||||
|
||||
+13
-3
@@ -1,5 +1,5 @@
|
||||
|
||||
%x mu emu
|
||||
%x mu emu com par
|
||||
|
||||
%%
|
||||
|
||||
@@ -11,15 +11,22 @@
|
||||
|
||||
[^\x00]+ { return 'CONTENT'; }
|
||||
|
||||
<emu>[^\x00]{2,}?/("{{") { this.popState(); return 'CONTENT'; }
|
||||
<emu>[^\x00]{2,}?/("{{"|<<EOF>>) {
|
||||
if(yytext.slice(-1) !== "\\") this.popState();
|
||||
if(yytext.slice(-1) === "\\") yytext = yytext.substr(0,yyleng-1);
|
||||
return 'CONTENT';
|
||||
}
|
||||
|
||||
<mu>"{{>" { return 'OPEN_PARTIAL'; }
|
||||
<com>[\s\S]*?"--}}" { yytext = yytext.substr(0, yyleng-4); this.popState(); return 'COMMENT'; }
|
||||
|
||||
<mu>"{{>" { this.begin("par"); return 'OPEN_PARTIAL'; }
|
||||
<mu>"{{#" { return 'OPEN_BLOCK'; }
|
||||
<mu>"{{/" { return 'OPEN_ENDBLOCK'; }
|
||||
<mu>"{{^" { return 'OPEN_INVERSE'; }
|
||||
<mu>"{{"\s*"else" { return 'OPEN_INVERSE'; }
|
||||
<mu>"{{{" { return 'OPEN_UNESCAPED'; }
|
||||
<mu>"{{&" { return 'OPEN_UNESCAPED'; }
|
||||
<mu>"{{!--" { this.popState(); this.begin('com'); }
|
||||
<mu>"{{!"[\s\S]*?"}}" { yytext = yytext.substr(3,yyleng-5); this.popState(); return 'COMMENT'; }
|
||||
<mu>"{{" { return 'OPEN'; }
|
||||
|
||||
@@ -31,6 +38,7 @@
|
||||
<mu>"}}}" { this.popState(); return 'CLOSE'; }
|
||||
<mu>"}}" { this.popState(); return 'CLOSE'; }
|
||||
<mu>'"'("\\"["]|[^"])*'"' { yytext = yytext.substr(1,yyleng-2).replace(/\\"/g,'"'); return 'STRING'; }
|
||||
<mu>"'"("\\"[']|[^'])*"'" { yytext = yytext.substr(1,yyleng-2).replace(/\\'/g,"'"); return 'STRING'; }
|
||||
<mu>"@"[a-zA-Z]+ { yytext = yytext.substr(1); return 'DATA'; }
|
||||
<mu>"true"/[}\s] { return 'BOOLEAN'; }
|
||||
<mu>"false"/[}\s] { return 'BOOLEAN'; }
|
||||
@@ -38,6 +46,8 @@
|
||||
<mu>[a-zA-Z0-9_$-]+/[=}\s\/.] { return 'ID'; }
|
||||
<mu>'['[^\]]*']' { yytext = yytext.substr(1, yyleng-2); return 'ID'; }
|
||||
<mu>. { return 'INVALID'; }
|
||||
<par>\s+ { /*ignore whitespace*/ }
|
||||
<par>[a-zA-Z0-9_$-/]+ { this.popState(); return 'PARTIAL_NAME'; }
|
||||
|
||||
<INITIAL,mu><<EOF>> { return 'EOF'; }
|
||||
|
||||
|
||||
+10
-3
@@ -7,8 +7,11 @@ root
|
||||
;
|
||||
|
||||
program
|
||||
: statements simpleInverse statements { $$ = new yy.ProgramNode($1, $3); }
|
||||
: simpleInverse statements { $$ = new yy.ProgramNode([], $2); }
|
||||
| statements simpleInverse statements { $$ = new yy.ProgramNode($1, $3); }
|
||||
| statements simpleInverse { $$ = new yy.ProgramNode($1, []); }
|
||||
| statements { $$ = new yy.ProgramNode($1); }
|
||||
| simpleInverse { $$ = new yy.ProgramNode([], []); }
|
||||
| "" { $$ = new yy.ProgramNode([]); }
|
||||
;
|
||||
|
||||
@@ -45,8 +48,8 @@ mustache
|
||||
|
||||
|
||||
partial
|
||||
: OPEN_PARTIAL path CLOSE { $$ = new yy.PartialNode($2); }
|
||||
| OPEN_PARTIAL path path CLOSE { $$ = new yy.PartialNode($2, $3); }
|
||||
: OPEN_PARTIAL partialName CLOSE { $$ = new yy.PartialNode($2); }
|
||||
| OPEN_PARTIAL partialName path CLOSE { $$ = new yy.PartialNode($2, $3); }
|
||||
;
|
||||
|
||||
simpleInverse
|
||||
@@ -91,6 +94,10 @@ hashSegment
|
||||
| ID EQUALS DATA { $$ = [$1, new yy.DataNode($3)]; }
|
||||
;
|
||||
|
||||
partialName
|
||||
: PARTIAL_NAME { $$ = new yy.PartialNameNode($1); }
|
||||
;
|
||||
|
||||
path
|
||||
: pathSegments { $$ = new yy.IdNode($1); }
|
||||
;
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
// BEGIN(BROWSER)
|
||||
@@ -0,0 +1,4 @@
|
||||
|
||||
// END(BROWSER)
|
||||
|
||||
module.exports = handlebars;
|
||||
Reference in New Issue
Block a user