Merge branch 'master' into es6-modules

Conflicts:
	Gruntfile.js
	Rakefile
	dist/handlebars.js
	dist/handlebars.runtime.js
	lib/handlebars.js
	lib/handlebars/base.js
	lib/handlebars/runtime.js
	lib/handlebars/utils.js
	package.json
This commit is contained in:
kpdecker
2013-09-02 18:19:18 -05:00
44 changed files with 1012 additions and 530 deletions
-1
View File
@@ -29,7 +29,6 @@
],
"node" : true,
"es5" : true,
"browser" : true,
"boss" : true,
+1
View File
@@ -12,4 +12,5 @@ Rakefile
bench/*
spec/*
src/*
tasks/*
vendor/*
+11 -2
View File
@@ -1,5 +1,14 @@
---
after_success: bundle exec rake publish
language: node_js
node_js:
- "0.8"
- "0.10"
before_script:
- npm install -g grunt-cli
script:
- grunt build metrics publish:latest
email:
on_failure: change
on_success: never
-7
View File
@@ -1,7 +0,0 @@
source "http://rubygems.org"
gem "rake"
gem "json"
gem "nokogiri"
gem "aws-sdk"
gem "uuidtools"
-21
View File
@@ -1,21 +0,0 @@
GEM
remote: http://rubygems.org/
specs:
aws-sdk (1.10.0)
json (~> 1.4)
nokogiri (>= 1.4.4)
uuidtools (~> 2.1)
json (1.8.0)
nokogiri (1.5.9)
rake (10.0.3)
uuidtools (2.1.4)
PLATFORMS
ruby
DEPENDENCIES
aws-sdk
json
nokogiri
rake
uuidtools
+47 -1
View File
@@ -1,3 +1,5 @@
var childProcess = require('child_process');
function config(name) {
return require('./configurations/' + name);
}
@@ -7,11 +9,37 @@ module.exports = function(grunt) {
grunt.initConfig({
pkg: grunt.file.readJSON('package.json'),
jshint: {
options: {
jshintrc: '.jshintrc',
force: true
},
files: [
'lib/**/!(parser|browser-prefix|browser-suffix).js'
]
},
clean: ["dist"],
watch: config('watch') ,
concat: config('concat'),
connect: config('connect'),
transpile: config('transpile')
transpile: config('transpile'),
uglify: {
options: {
mangle: true,
compress: true,
preserveComments: 'some'
},
dist: {
src: 'dist/<%= pkg.name %>.js',
dest: 'dist/<%= pkg.name %>.min.js'
},
runtime: {
src: 'dist/<%= pkg.name %>.runtime.js',
dest: 'dist/<%= pkg.name %>.runtime.min.js'
}
}
});
// By default, (i.e., if you invoke `grunt` without arguments), do
@@ -40,8 +68,26 @@ module.exports = function(grunt) {
grunt.loadNpmTasks('grunt-contrib-clean');
grunt.loadNpmTasks('grunt-contrib-concat');
grunt.loadNpmTasks('grunt-contrib-connect');
grunt.loadNpmTasks('grunt-contrib-jshint');
grunt.loadNpmTasks('grunt-contrib-uglify');
grunt.loadNpmTasks('grunt-contrib-watch');
grunt.loadNpmTasks('grunt-es6-module-transpiler');
grunt.task.loadTasks('tasks');
grunt.registerTask('test', function() {
var done = this.async();
var runner = childProcess.fork('./spec/env/runner', [], {stdio: 'inherit'});
runner.on('close', function(code) {
if (code != 0) {
grunt.fatal(code + ' tests failed');
}
done();
});
});
grunt.registerTask('bench', ['metrics']);
grunt.registerTask('build', ['jshint', 'parser', 'clean', 'concat', 'uglify', 'test']);
grunt.registerTask('default', 'build');
};
+9 -9
View File
@@ -307,6 +307,8 @@ normal.
helpers for size and speed.
- When all helpers are known in advance the `--knownOnly` argument may be used
to optimize all block helper references.
- Implementations that do not use `@data` variables can improve performance of
iteration centric templates by specifying `{data: false}` in the compiler options.
Supported Environments
----------------------
@@ -340,8 +342,7 @@ and we will have some benchmarks in the near future.
Building
--------
To build handlebars, just run `rake build`, and you will get two files
in the `dist` directory.
To build handlebars, just run `grunt build`, and the build will output to the `dist` directory.
Upgrading
@@ -379,6 +380,7 @@ Handlebars in the Wild
* [SproutCore](http://www.sproutcore.com) uses Handlebars.js as its main
templating engine, extending it with automatic data binding support.
* [YUI](http://yuilibrary.com/yui/docs/handlebars/) implements a port of handlebars
* [Swag](https://github.com/elving/swag) by [@elving](https://github.com/elving) is a growing collection of helpers for handlebars.js. Give your handlebars.js templates some swag son!
External Resources
------------------
@@ -393,16 +395,14 @@ Helping Out
To build Handlebars.js you'll need a few things installed.
* Node.js
* Ruby
* Rake
* [Grunt](http://gruntjs.com/getting-started)
There's a Gemfile in the repo, so you can run `bundle` to install rake
if you've got bundler installed.
Project dependencies may be installed via `npm install`.
To build Handlebars.js from scratch, you'll want to run `rake build`
To build Handlebars.js from scratch, you'll want to run `grunt`
in the root of the project. That will build Handlebars and output the
results to the dist/ folder. To run tests, run `rake spec` or `npm test`.
You can also run our set of benchmarks with `rake bench`.
results to the dist/ folder. To re-run tests, run `grunt test` or `npm test`.
You can also run our set of benchmarks with `grunt bench`.
If you notice any problems, please report them to the GitHub issue tracker at
[http://github.com/wycats/handlebars.js/issues](http://github.com/wycats/handlebars.js/issues).
-196
View File
@@ -1,196 +0,0 @@
require "rubygems"
require "bundler/setup"
def compile_parser
system "./node_modules/.bin/jison -m js src/handlebars.yy src/handlebars.l"
if $?.success?
File.open("lib/handlebars/compiler/parser.js", "w") do |file|
file.puts File.read("handlebars.js")
end
sh "rm handlebars.js"
else
fail "Failed to run Jison."
end
end
file "lib/handlebars/compiler/parser.js" => ["src/handlebars.yy","src/handlebars.l"] do
if File.exists?('./node_modules/jison')
compile_parser
else
puts "Jison is not installed. Trying `npm install jison`."
sh "npm install"
compile_parser
end
end
task :compile => "lib/handlebars/compiler/parser.js"
desc "run the spec suite"
task :spec => [:build] do
rc = system "npm test"
fail "npm test failed with exit code #{$?.exitstatus}" if (rc.nil? || ! rc || $?.exitstatus != 0)
end
task :default => [:compile, :spec]
def remove_exports(string)
match = string.match(%r{^// BEGIN\(BROWSER\)\n(.*)\n^// END\(BROWSER\)}m)
match ? match[1] : string
end
minimal_deps = %w(base compiler/parser compiler/base compiler/ast utils compiler/compiler compiler/javascript-compiler runtime).map do |file|
"lib/handlebars/#{file}.js"
end
runtime_deps = %w(base utils runtime).map do |file|
"lib/handlebars/#{file}.js"
end
directory "dist"
minimal_deps.unshift "dist"
def build_for_task(task)
FileUtils.rm_rf("dist/*") if File.directory?("dist")
FileUtils.mkdir_p("dist")
contents = ["/*\n\n" + File.read('LICENSE') + "\n@license\n*/\n"]
task.prerequisites.each do |filename|
next if filename == "dist"
contents << "// #{filename}\n" + remove_exports(File.read(filename)) + ";"
end
File.open(task.name, "w") do |file|
file.puts contents.join("\n")
end
end
file "dist/handlebars.js" => minimal_deps do |task|
build_for_task(task)
end
file "dist/handlebars.runtime.js" => runtime_deps do |task|
build_for_task(task)
end
task :build => [:compile] do |task|
Rake::Task["dist/handlebars.js"].execute
Rake::Task["dist/handlebars.runtime.js"].execute
system "./node_modules/.bin/uglifyjs -m -c --comments -o dist/handlebars.min.js dist/handlebars.js"
system "./node_modules/.bin/uglifyjs -m -c --comments -o dist/handlebars.runtime.min.js dist/handlebars.runtime.js"
end
# Updates the various version numbers.
desc "Updates the current release version"
task :version, [:version] => [] do |task, args|
version = args.version
fail "Must provide a version number" unless version
changed = %x{git diff-index --name-only HEAD --}
fail "The repository must be clean" unless $?.success? && changed.empty?
puts "Updating to version #{version}"
content = File.read("lib/handlebars/base.js")
File.open("lib/handlebars/base.js", "w") do | file|
file.puts content.gsub(/Handlebars.VERSION = "(.*)";/, "Handlebars.VERSION = \"#{version}\";")
end
content = File.read("bower.json")
File.open("bower.json", "w") do |file|
file.puts content.gsub(/"version":.*/, "\"version\": \"#{version}\",")
end
content = File.read("handlebars.js.nuspec")
File.open("handlebars.js.nuspec", "w") do |file|
file.puts content.gsub(/<version>.*<\/version>/, "<version>#{version}</version>")
end
Rake::Task[:build].invoke
Rake::Task[:spec].invoke
# TODO : Make sure that all of these files are updated properly in git then run npm version
end
directory "vendor"
desc "benchmark against dust.js and mustache.js"
task :bench => "vendor" do
require "open-uri"
#if File.directory?("vendor/coffee")
#system "cd vendor/coffee && git pull"
#else
#system "git clone git://github.com/jashkenas/coffee-script.git vendor/coffee"
#end
#if File.directory?("vendor/eco")
#system "cd vendor/eco && git pull && npm update"
#else
#system "git clone git://github.com/sstephenson/eco.git vendor/eco && cd vendor/eco && npm update"
#end
system "node bench/handlebars.js"
end
def dist_files(&block)
map = {}
root = File.expand_path(File.dirname(__FILE__)) + '/dist/'
files = ['handlebars.js', 'handlebars.min.js', 'handlebars.runtime.js', 'handlebars.runtime.min.js'].map { |file| root + file }
files = files.map do |file|
basename = Pathname.new(file).basename.sub_ext('')
map[file] = yield basename
end
map
end
def publish_s3(files)
access_key_id = ENV['S3_ACCESS_KEY_ID']
secret_access_key = ENV['S3_SECRET_ACCESS_KEY']
bucket_name = ENV['S3_BUCKET_NAME']
if files && access_key_id && secret_access_key && bucket_name
require 'aws-sdk'
s3 = AWS::S3.new(access_key_id: access_key_id,secret_access_key: secret_access_key)
bucket = s3.buckets[bucket_name]
files.each do |source, outputs|
s3_objs = outputs.map do |file|
bucket.objects[file]
end
s3_objs.each { |obj| obj.write(Pathname.new(source)) }
end
else
puts "Not uploading any files to S3!"
end
end
task :publish do
rev = `git rev-parse --short HEAD`.to_s.strip
master_rev = `git rev-parse --short origin/master`.to_s.strip
if rev == master_rev
files = dist_files do |basename|
["#{basename}-latest.js", "#{basename}-#{rev}.js"]
end
end
publish_s3 files
end
task :publish_version do
tag = `git tag -l --points-at HEAD`.to_s.strip.split(/\n/)
fail "The current commit must be tagged." if tag.empty?
fail "Multiple tags, aborting: #{tag}" if tag.length > 1
tag = tag.first
files = dist_files do |basename|
["#{basename}-#{tag}.js"]
end
publish_s3 files
end
+29
View File
@@ -0,0 +1,29 @@
var _ = require('underscore'),
async = require('async'),
fs = require('fs'),
zlib = require('zlib');
module.exports = function(grunt, callback) {
var distFiles = fs.readdirSync('dist'),
distSizes = {};
async.each(distFiles, function(file, callback) {
var content = fs.readFileSync('dist/' + file);
file = file.replace(/\.js/, '').replace(/\./g, '_');
distSizes[file] = content.length;
zlib.gzip(content, function(err, data) {
if (err) {
throw err;
}
distSizes[file + '_gz'] = data.length;
callback();
});
},
function() {
grunt.log.writeln('Distribution sizes: ' + JSON.stringify(distSizes, undefined, 2));
callback([distSizes]);
});
};
-172
View File
@@ -1,172 +0,0 @@
var BenchWarmer = require("./benchwarmer");
Handlebars = require("../lib/handlebars");
var dust, Mustache, eco;
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: {
context: {},
handlebars: "Hello world",
dust: "Hello world",
mustache: "Hello world",
eco: "Hello world"
},
variables: {
context: {name: "Mick", count: 30},
handlebars: "Hello {{name}}! You have {{count}} new messages.",
dust: "Hello {name}! You have {count} new messages.",
mustache: "Hello {{name}}! You have {{count}} new messages.",
eco: "Hello <%= @name %>! You have <%= @count %> new messages."
},
object: {
context: { person: { name: "Larry", age: 45 } },
handlebars: "{{#with person}}{{name}}{{age}}{{/with}}",
dust: "{#person}{name}{age}{/person}",
mustache: "{{#person}}{{name}}{{age}}{{/person}}"
},
array: {
context: { names: [{name: "Moe"}, {name: "Larry"}, {name: "Curly"}, {name: "Shemp"}] },
handlebars: "{{#each names}}{{name}}{{/each}}",
dust: "{#names}{name}{/names}",
mustache: "{{#names}}{{name}}{{/names}}",
eco: "<% for item in @names: %><%= item.name %><% end %>"
},
partial: {
context: { peeps: [{name: "Moe", count: 15}, {name: "Larry", count: 5}, {name: "Curly", count: 1}] },
partials: {
mustache: { variables: "Hello {{name}}! You have {{count}} new messages." },
handlebars: { variables: "Hello {{name}}! You have {{count}} new messages." }
},
handlebars: "{{#each peeps}}{{>variables}}{{/each}}",
dust: "{#peeps}{>variables/}{/peeps}",
mustache: "{{#peeps}}{{>variables}}{{/peeps}}"
},
recursion: {
context: { name: '1', kids: [{ name: '1.1', kids: [{name: '1.1.1', kids: []}] }] },
partials: {
mustache: { recursion: "{{name}}{{#kids}}{{>recursion}}{{/kids}}" },
handlebars: { recursion: "{{name}}{{#each kids}}{{>recursion}}{{/each}}" }
},
handlebars: "{{name}}{{#each kids}}{{>recursion}}{{/each}}",
dust: "{name}{#kids}{>recursion:./}{/kids}",
mustache: "{{name}}{{#kids}}{{>recursion}}{{/kids}}"
},
complex: {
handlebars: "<h1>{{header}}</h1>{{#if items}}<ul>{{#each items}}{{#if current}}" +
"<li><strong>{{name}}</strong></li>{{^}}" +
"<li><a href=\"{{url}}\">{{name}}</a></li>{{/if}}" +
"{{/each}}</ul>{{^}}<p>The list is empty.</p>{{/if}}",
dust: "<h1>{header}</h1>\n" +
"{?items}\n" +
" <ul>\n" +
" {#items}\n" +
" {#current}\n" +
" <li><strong>{name}</strong></li>\n" +
" {:else}\n" +
" <li><a href=\"{url}\">{name}</a></li>\n" +
" {/current}\n" +
" {/items}\n" +
" </ul>\n" +
"{:else}\n" +
" <p>The list is empty.</p>\n" +
"{/items}",
context: {
header: function() {
return "Colors";
},
items: [
{name: "red", current: true, url: "#Red"},
{name: "green", current: false, url: "#Green"},
{name: "blue", current: false, url: "#Blue"}
]
}
}
};
handlebarsTemplates = {};
ecoTemplates = {};
var warmer = new BenchWarmer();
var makeSuite = function(name) {
warmer.suite(name, function(bench) {
var templateName = name;
var details = benchDetails[templateName];
var mustachePartials = details.partials && details.partials.mustache;
var mustacheSource = details.mustache;
var context = details.context;
var error = function() { throw new Error("EWOT"); };
if (dust) {
bench("dust", function() {
dust.render(templateName, context, function(err, out) { });
});
}
bench("handlebars", function() {
handlebarsTemplates[templateName](context);
});
if (eco) {
if(ecoTemplates[templateName]) {
bench("eco", function() {
ecoTemplates[templateName](context);
});
} else {
bench("eco", 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)) {
if (dust) {
dust.loadSource(dust.compile(benchDetails[name].dust, name));
}
handlebarsTemplates[name] = Handlebars.compile(benchDetails[name].handlebars);
if (eco && benchDetails[name].eco) {
ecoTemplates[name] = eco(benchDetails[name].eco);
}
var partials = benchDetails[name].partials;
if(partials) {
for(var partialName in partials.handlebars) {
if(partials.handlebars.hasOwnProperty(partialName)) {
Handlebars.registerPartial(partialName, partials.handlebars[partialName]);
}
}
}
makeSuite(name);
}
}
warmer.bench();
+14
View File
@@ -0,0 +1,14 @@
var fs = require('fs');
var metrics = fs.readdirSync(__dirname);
metrics.forEach(function(metric) {
if (metric === 'index.js' || !/(.*)\.js$/.test(metric)) {
return;
}
var name = RegExp.$1;
metric = require('./' + name);
if (metric instanceof Function) {
module.exports[name] = metric;
}
});
+19
View File
@@ -0,0 +1,19 @@
var _ = require('underscore'),
templates = require('./templates');
module.exports = function(grunt, callback) {
// Deferring to here in case we have a build for parser, etc as part of this grunt exec
var Handlebars = require('../lib/handlebars');
var templateSizes = {};
_.each(templates, function(info, template) {
var src = info.handlebars,
compiled = Handlebars.precompile(src, {}),
knownHelpers = Handlebars.precompile(src, {knownHelpersOnly: true, knownHelpers: info.helpers});
templateSizes[template] = compiled.length;
templateSizes['knownOnly_' + template] = knownHelpers.length;
});
grunt.log.writeln('Precompiled sizes: ' + JSON.stringify(templateSizes, undefined, 2));
callback([templateSizes]);
};
+12
View File
@@ -0,0 +1,12 @@
module.exports = {
helpers: {
foo: function(options) {
return '';
}
},
context: {
bar: true
},
handlebars: '{{foo person "person" 1 true foo=bar foo="person" foo=1 foo=true}}'
};
+7
View File
@@ -0,0 +1,7 @@
module.exports = {
context: { names: [{name: "Moe"}, {name: "Larry"}, {name: "Curly"}, {name: "Shemp"}] },
handlebars: "{{#each names}}{{name}}{{/each}}",
dust: "{#names}{name}{/names}",
mustache: "{{#names}}{{name}}{{/names}}",
eco: "<% for item in @names: %><%= item.name %><% end %>"
};
+4
View File
@@ -0,0 +1,4 @@
module.exports = {
context: { names: [{name: "Moe"}, {name: "Larry"}, {name: "Curly"}, {name: "Shemp"}] },
handlebars: "{{#names}}{{name}}{{/names}}"
}
+14
View File
@@ -0,0 +1,14 @@
<h1>{header}</h1>
{?items}
<ul>
{#items}
{#current}
<li><strong>{name}</strong></li>
{:else}
<li><a href="{url}">{name}</a></li>
{/current}
{/items}
</ul>
{:else}
<p>The list is empty.</p>
{/items}
+14
View File
@@ -0,0 +1,14 @@
<h1><%= @header() %></h1>
<% if @items.length: %>
<ul>
<% for item in @items: %>
<% if item.current: %>
<li><strong><%= item.name %></strong></li>
<% else: %>
<li><a href="<%= item.url %>"><%= item.name %></a></li>
<% end %>
<% end %>
</ul>
<% else: %>
<p>The list is empty.</p>
<% end %>
+14
View File
@@ -0,0 +1,14 @@
<h1>{{header}}</h1>
{{#if items}}
<ul>
{{#each items}}
{{#if current}}
<li><strong>{{name}}</strong></li>
{{^}}
<li><a href="{{url}}">{{name}}</a></li>
{{/if}}
{{/each}}
</ul>
{{^}}
<p>The list is empty.</p>
{{/if}}
+20
View File
@@ -0,0 +1,20 @@
var fs = require('fs');
module.exports = {
context: {
header: function() {
return "Colors";
},
hasItems: true, // To make things fairer in mustache land due to no `{{if}}` construct on arrays
items: [
{name: "red", current: true, url: "#Red"},
{name: "green", current: false, url: "#Green"},
{name: "blue", current: false, url: "#Blue"}
]
},
handlebars: fs.readFileSync(__dirname + '/complex.handlebars').toString(),
dust: fs.readFileSync(__dirname + '/complex.dust').toString(),
eco: fs.readFileSync(__dirname + '/complex.eco').toString(),
mustache: fs.readFileSync(__dirname + '/complex.mustache').toString()
};
+13
View File
@@ -0,0 +1,13 @@
<h1>{{header}}</h1>
{{#hasItems}}
<ul>
{{#items}}
{{#current}}
<li><strong>{{name}}</strong></li>
{{/current}}
{{^current}}
<li><a href="{{url}}">{{name}}</a></li>
{{/current}}
{{/items}}
</ul>
{{/hasItems}}
+4
View File
@@ -0,0 +1,4 @@
module.exports = {
context: { names: [{name: "Moe"}, {name: "Larry"}, {name: "Curly"}, {name: "Shemp"}] },
handlebars: "{{#each names}}{{@index}}{{name}}{{/each}}"
}
+9
View File
@@ -0,0 +1,9 @@
var fs = require('fs');
var templates = fs.readdirSync(__dirname);
templates.forEach(function(template) {
if (template === 'index.js' || !/(.*)\.js$/.test(template)) {
return;
}
module.exports[RegExp.$1] = require('./' + RegExp.$1);
});
+4
View File
@@ -0,0 +1,4 @@
module.exports = {
context: { person: { name: "Larry", age: 45 } },
handlebars: "{{#person}}{{name}}{{age}}{{/person}}"
};
+7
View File
@@ -0,0 +1,7 @@
module.exports = {
context: { person: { name: "Larry", age: 45 } },
handlebars: "{{#with person}}{{name}}{{age}}{{/with}}",
dust: "{#person}{name}{age}{/person}",
eco: "<%= @person.name %><%= @person.age %>",
mustache: "{{#person}}{{name}}{{age}}{{/person}}"
};
+10
View File
@@ -0,0 +1,10 @@
module.exports = {
context: { name: '1', kids: [{ name: '1.1', kids: [{name: '1.1.1', kids: []}] }] },
partials: {
mustache: { recursion: "{{name}}{{#kids}}{{>recursion}}{{/kids}}" },
handlebars: { recursion: "{{name}}{{#each kids}}{{>recursion}}{{/each}}" }
},
handlebars: "{{name}}{{#each kids}}{{>recursion}}{{/each}}",
dust: "{name}{#kids}{>recursion:./}{/kids}",
mustache: "{{name}}{{#kids}}{{>recursion}}{{/kids}}"
};
+11
View File
@@ -0,0 +1,11 @@
module.exports = {
context: { peeps: [{name: "Moe", count: 15}, {name: "Larry", count: 5}, {name: "Curly", count: 1}] },
partials: {
mustache: { variables: "Hello {{name}}! You have {{count}} new messages." },
handlebars: { variables: "Hello {{name}}! You have {{count}} new messages." }
},
handlebars: "{{#each peeps}}{{>variables}}{{/each}}",
dust: "{#peeps}{>variables/}{/peeps}",
mustache: "{{#peeps}}{{>variables}}{{/peeps}}"
};
+7
View File
@@ -0,0 +1,7 @@
module.exports = {
context: { person: { name: "Larry", age: 45 } },
handlebars: "{{person.name}}{{person.age}}{{person.foo}}{{animal.age}}",
dust: "{person.name}{person.age}{person.foo}{animal.age}",
eco: "<%= @person.name %><%= @person.age %><%= @person.foo %><% if @animal: %><%= @animal.age %><% end %>",
mustache: "{{person.name}}{{person.age}}{{person.foo}}{{animal.age}}"
};
+7
View File
@@ -0,0 +1,7 @@
module.exports = {
context: {},
handlebars: "Hello world",
dust: "Hello world",
mustache: "Hello world",
eco: "Hello world"
};
+8
View File
@@ -0,0 +1,8 @@
module.exports = {
context: {name: "Mick", count: 30},
handlebars: "Hello {{name}}! You have {{count}} new messages.",
dust: "Hello {name}! You have {count} new messages.",
mustache: "Hello {{name}}! You have {{count}} new messages.",
eco: "Hello <%= @name %>! You have <%= @count %> new messages."
};
+123
View File
@@ -0,0 +1,123 @@
var _ = require('underscore'),
runner = require('./util/template-runner'),
templates = require('./templates'),
eco, dust, Handlebars, Mustache, eco;
try {
dust = require("dustjs-linkedin");
} catch (err) { /* NOP */ }
try {
Mustache = require("mustache");
} catch (err) { /* NOP */ }
try {
eco = require("eco");
} catch (err) { /* NOP */ }
function error() {
throw new Error("EWOT");
}
function makeSuite(bench, name, template, handlebarsOnly) {
// Create aliases to minimize any impact from having to walk up the closure tree.
var templateName = name,
context = template.context,
partials = template.partials,
handlebarsOut,
dustOut,
ecoOut,
mustacheOut;
var handlebar = Handlebars.compile(template.handlebars, {data: false}),
options = {helpers: template.helpers};
_.each(template.partials && template.partials.handlebars, function(partial, name) {
Handlebars.registerPartial(name, Handlebars.compile(partial, {data: false}));
});
handlebarsOut = handlebar(context, options);
bench("handlebars", function() {
handlebar(context, options);
});
if (handlebarsOnly) {
return;
}
if (dust) {
if (template.dust) {
dustOut = false;
dust.loadSource(dust.compile(template.dust, templateName));
dust.render(templateName, context, function(err, out) { dustOut = out; });
bench("dust", function() {
dust.render(templateName, context, function(err, out) { });
});
} else {
bench('dust', error);
}
}
if (eco) {
if (template.eco) {
var ecoTemplate = eco.compile(template.eco);
ecoOut = ecoTemplate(context);
bench("eco", function() {
ecoTemplate(context);
});
} else {
bench("eco", error);
}
}
if (Mustache) {
var mustacheSource = template.mustache,
mustachePartials = partials && partials.mustache;
if (mustacheSource) {
mustacheOut = Mustache.to_html(mustacheSource, context, mustachePartials);
bench("mustache", function() {
Mustache.to_html(mustacheSource, context, mustachePartials);
});
} else {
bench("mustache", error);
}
}
// Hack around whitespace until we have whitespace control
handlebarsOut = handlebarsOut.replace(/\s/g, '');
function compare(b, lang) {
if (b == null) {
return;
}
b = b.replace(/\s/g, '');
if (handlebarsOut !== b) {
throw new Error('Template output mismatch: ' + name
+ '\n\nHandlebars: ' + handlebarsOut
+ '\n\n' + lang + ': ' + b);
}
}
compare(dustOut, 'dust');
compare(ecoOut, 'eco');
compare(mustacheOut, 'mustache');
}
module.exports = function(grunt, callback) {
// Deferring load incase we are being run inline with the grunt build
Handlebars = require('../lib/handlebars');
console.log('Execution Throughput');
runner(grunt, makeSuite, function(times, scaled) {
callback(scaled);
});
};
@@ -1,10 +1,13 @@
var Benchmark = require("benchmark");
var _ = require('underscore'),
Benchmark = require("benchmark");
var BenchWarmer = function(names) {
this.benchmarks = [];
this.currentBenches = [];
this.names = [];
this.times = {};
this.minimum = Infinity;
this.maximum = -Infinity;
this.errors = {};
};
@@ -12,28 +15,11 @@ var print = require("sys").print;
BenchWarmer.prototype = {
winners: function(benches) {
var result = Benchmark.filter(benches, function(bench) { return bench.cycles; });
if (result.length > 1) {
result.sort(function(a, b) { return b.compare(a); });
first = result[0];
last = result[result.length - 1];
var winners = [];
Benchmark.each(result, function(bench) {
if (bench.compare(first) === 0) {
winners.push(bench);
}
});
return winners;
} else {
return result;
}
return Benchmark.filter(benches, 'fastest');
},
suite: function(suite, fn) {
this.suiteName = suite;
this.times[suite] = {};
this.first = true;
var self = this;
@@ -50,9 +36,7 @@ BenchWarmer.prototype = {
var first = this.first, suiteName = this.suiteName, self = this;
this.first = false;
var bench = new Benchmark(function() {
fn();
}, {
var bench = new Benchmark(fn, {
name: this.suiteName + ": " + name,
onComplete: function() {
if(first) { self.startLine(suiteName); }
@@ -62,11 +46,80 @@ BenchWarmer.prototype = {
self.errors[this.name] = this;
}
});
bench.suiteName = this.suiteName;
bench.benchName = name;
this.benchmarks.push(bench);
},
bench: function() {
var benchSize = 0, names = this.names, self = this, i, l;
bench: function(callback) {
var self = this;
this.printHeader('ops/msec', true);
Benchmark.invoke(this.benchmarks, {
name: "run",
onComplete: function() {
self.scaleTimes();
self.startLine('');
print('\n');
self.printHeader('scaled');
_.each(self.scaled, function(value, name) {
self.startLine(name);
_.each(self.names, function(lang) {
self.writeValue(value[lang] || '');
});
});
print('\n');
var errors = false, prop, bench;
for(prop in self.errors) {
if (self.errors.hasOwnProperty(prop)
&& self.errors[prop].error.message !== 'EWOT') {
errors = true;
break;
}
}
if(errors) {
print("\n\nErrors:\n");
for(prop in self.errors) {
if (self.errors.hasOwnProperty(prop)
&& self.errors[prop].error.message !== 'EWOT') {
bench = self.errors[prop];
print("\n" + bench.name + ":\n");
print(bench.error.message);
if(bench.error.stack) {
print(bench.error.stack.join("\n"));
}
print("\n");
}
}
}
callback();
}
});
print("\n");
},
scaleTimes: function() {
var scaled = this.scaled = {};
_.each(this.times, function(times, name) {
var output = scaled[name] = {};
_.each(times, function(time, lang) {
output[lang] = ((time - this.minimum) / (this.maximum - this.minimum) * 100).toFixed(2);
}, this);
}, this);
},
printHeader: function(title, winners) {
var benchSize = 0, names = this.names, i, l;
for(i=0, l=names.length; i<l; i++) {
var name = names[i];
@@ -78,43 +131,21 @@ BenchWarmer.prototype = {
this.benchSize = 20;
var horSize = 0;
this.startLine("ops/msec");
horSize = horSize + "ops/msec ".length;
this.startLine(title);
horSize = horSize + this.benchSize;
for(i=0, l=names.length; i<l; i++) {
print(names[i] + new Array(this.benchSize - names[i].length + 1).join(" "));
this.writeValue(names[i]);
horSize = horSize + this.benchSize;
}
print("WINNER(S)");
horSize = horSize + "WINNER(S)".length;
if (winners) {
print("WINNER(S)");
horSize = horSize + "WINNER(S)".length;
}
print("\n" + new Array(horSize + 1).join("-"));
Benchmark.invoke(this.benchmarks, {
name: "run",
onComplete: function() {
var errors = false, prop, bench;
for(prop in self.errors) { if(self.errors.hasOwnProperty(prop)) { errors = true; break; } }
if(errors) {
print("\n\nErrors:\n");
for(prop in self.errors) {
if(self.errors.hasOwnProperty(prop)) {
bench = self.errors[prop];
print("\n" + bench.name + ":\n");
print(bench.error.message);
if(bench.error.stack) {
print(bench.error.stack.join("\n"));
}
print("\n");
}
}
}
}
});
print("\n");
},
startLine: function(name) {
var winners = Benchmark.map(this.winners(this.currentBenches), function(bench) {
return bench.name.split(": ")[1];
@@ -124,22 +155,41 @@ BenchWarmer.prototype = {
print(winners.join(", "));
print("\n");
var padding = this.nameSize - name.length + 1;
name = name + new Array(padding).join(" ");
print(name);
if (name) {
this.writeValue(name);
}
},
writeBench: function(bench) {
var out;
if(!bench.error) {
var count = bench.hz,
moe = count * bench.stats.rme / 100;
moe = count * bench.stats.rme / 100,
minimum,
maximum;
out = Math.round(count / 1000) + " ±" + Math.round(moe / 1000) + " (" + bench.cycles + ")";
count = Math.round(count / 1000);
moe = Math.round(moe / 1000);
minimum = count - moe;
maximum = count + moe;
out = count + " ±" + moe + " (" + bench.cycles + ")";
this.times[bench.suiteName][bench.benchName] = count;
this.minimum = Math.min(this.minimum, minimum);
this.maximum = Math.max(this.maximum, maximum);
} else {
out = "E";
if (bench.error.message === 'EWOT') {
out = 'NA';
} else {
out = 'E';
}
}
this.writeValue(out);
},
writeValue: function(out) {
var padding = this.benchSize - out.length + 1;
out = out + new Array(padding).join(" ");
print(out);
+27
View File
@@ -0,0 +1,27 @@
var _ = require('underscore'),
BenchWarmer = require('./benchwarmer'),
templates = require('../templates');
module.exports = function(grunt, makeSuite, callback) {
var warmer = new BenchWarmer();
var handlebarsOnly = grunt.option('handlebars-only'),
grep = grunt.option('grep');
if (grep) {
grep = new RegExp(grep);
}
_.each(templates, function(template, name) {
if (!template.handlebars || (grep && !grep.test(name))) {
return;
}
warmer.suite(name, function(bench) {
makeSuite(bench, name, template, handlebarsOnly);
});
});
warmer.bench(function() {
callback && callback(warmer.times, warmer.scaled);
});
};
+30 -11
View File
@@ -1,16 +1,35 @@
module.exports = {
library: {
src: ['tmp/<%= pkg.barename %>.amd.js', 'tmp/<%= pkg.barename %>/*.js'],
dest: 'dist/<%= pkg.name %>-<%= pkg.version %>.amd.js'
options: {
banner: '/*!\n\n <%= pkg.name %> v<%= pkg.version %>\n\n<%= grunt.file.read("LICENSE") %>\n@license\n*/\n',
process: function(src, name) {
var match = /\/\/ BEGIN\(BROWSER\)\n((?:.|\n)*)\n\/\/ END\(BROWSER\)/.exec(src);
return '\n// ' + name + '\n' + (match ? match[1] : src);
},
separator: ';'
},
deps: {
src: ['vendor/deps/*.js'],
dest: 'tmp/deps.amd.js'
dist: {
src: [
'lib/handlebars/browser-prefix.js',
'lib/handlebars/base.js',
'lib/handlebars/compiler/parser.js',
'lib/handlebars/compiler/base.js',
'lib/handlebars/compiler/ast.js',
'lib/handlebars/utils.js',
'lib/handlebars/compiler/compiler.js',
'lib/handlebars/compiler/javascript-compiler.js',
'lib/handlebars/runtime.js',
'lib/handlebars/browser-suffix.js'
],
dest: 'dist/handlebars.js'
},
browser: {
src: ['vendor/loader.js', 'tmp/<%= pkg.barename %>.amd.js'],
dest: 'tmp/<%= pkg.barename %>.browser1.js'
runtime: {
src: [
'lib/handlebars/browser-prefix.js',
'lib/handlebars/base.js',
'lib/handlebars/utils.js',
'lib/handlebars/runtime.js',
'lib/handlebars/browser-suffix.js'
],
dest: 'dist/handlebars.runtime.js'
}
};
+25 -16
View File
@@ -15,9 +15,24 @@ export var REVISION_CHANGES = {
};
var toString = Object.prototype.toString,
functionType = '[object Function]',
objectType = '[object Object]';
// Sourced from lodash
// https://github.com/bestiejs/lodash/blob/master/LICENSE.txt
function isFunction(value) {
return typeof value === 'function';
}
// fallback for older versions of Chrome and Safari
if (isFunction(/x/)) {
isFunction = function(value) {
return typeof value === 'function' && toString.call(value) === '[object Function]';
};
}
function isArray(value) {
return (value && typeof value === 'object') ? toString.call(value) === '[object Array]' : false;
};
export function HandlebarsEnvironment(helpers, partials) {
this.helpers = helpers || {};
this.partials = partials || {};
@@ -59,15 +74,13 @@ function registerDefaultHelpers(instance) {
instance.registerHelper('blockHelperMissing', function(context, options) {
var inverse = options.inverse || function() {}, fn = options.fn;
var type = toString.call(context);
if(type === functionType) { context = context.call(this); }
if (isFunction(context)) { context = context.call(this); }
if(context === true) {
return fn(this);
} else if(context === false || context == null) {
return inverse(this);
} else if(type === "[object Array]") {
} else if (isArray(context)) {
if(context.length > 0) {
return instance.helpers.each(context, options);
} else {
@@ -82,15 +95,14 @@ function registerDefaultHelpers(instance) {
var fn = options.fn, inverse = options.inverse;
var i = 0, ret = "", data;
var type = toString.call(context);
if(type === functionType) { context = context.call(this); }
if (isFunction(context)) { context = context.call(this); }
if (options.data) {
data = createFrame(options.data);
}
if(context && typeof context === 'object') {
if(context instanceof Array){
if (isArray(context)) {
for(var j = context.length; i<j; i++) {
if (data) { data.index = i; }
ret = ret + fn(context[i], { data: data });
@@ -114,8 +126,7 @@ function registerDefaultHelpers(instance) {
});
instance.registerHelper('if', function(conditional, options) {
var type = toString.call(conditional);
if(type === functionType) { conditional = conditional.call(this); }
if (isFunction(conditional)) { conditional = conditional.call(this); }
if (isEmpty(conditional)) {
return options.inverse(this);
@@ -129,8 +140,7 @@ function registerDefaultHelpers(instance) {
});
instance.registerHelper('with', function(context, options) {
var type = toString.call(context);
if(type === functionType) { context = context.call(this); }
if (isFunction(context)) { context = context.call(this); }
if (!isEmpty(context)) return options.fn(context);
});
@@ -161,9 +171,8 @@ export var logger = {
export function log(level, obj) { logger.log(level, obj); };
export var createFrame = Object.create || function(object) {
K.prototype = object;
var obj = new K();
K.prototype = null;
export var createFrame = function(object) {
var obj = {};
Handlebars.Utils.extend(obj, object);
return obj;
};
+37 -18
View File
@@ -1,11 +1,31 @@
import { escapeExpression, extend, Exception } from "./utils";
import { COMPILER_REVISION, REVISION_CHANGES } from "./base";
function checkRevision(compilerInfo) {
var compilerRevision = compilerInfo && compilerInfo[0] || 1,
currentRevision = Handlebars.COMPILER_REVISION;
if (compilerRevision !== currentRevision) {
if (compilerRevision < currentRevision) {
var runtimeVersions = Handlebars.REVISION_CHANGES[currentRevision],
compilerVersions = Handlebars.REVISION_CHANGES[compilerRevision];
throw "Template was precompiled with an older version of Handlebars than the current runtime. "+
"Please update your precompiler to a newer version ("+runtimeVersions+") or downgrade your runtime to an older version ("+compilerVersions+").";
} else {
// Use the embedded version info since the runtime doesn't know about this revision yet
throw "Template was precompiled with a newer version of Handlebars than the current runtime. "+
"Please update your runtime to a newer version ("+compilerInfo[1]+").";
}
}
}
// TODO: Remove this line and break up compilePartial
export function template(templateSpec, Hbars, compile) {
if (compile) {
var invokePartialWrapper = function(partial, name, context, helpers, partials, data) {
// TODO : Check this for all inputs and the options handling (partial flag, etc). This feels
// like there should be a common exec path
var result = invokePartial.apply(this, arguments);
if (result) { return result; }
@@ -42,7 +62,7 @@ export function template(templateSpec, Hbars, compile) {
merge: function(param, common) {
var ret = param || common;
if (param && common) {
if (param && common && (param !== common)) {
ret = {};
extend(ret, common);
extend(ret, param);
@@ -56,24 +76,23 @@ export function template(templateSpec, Hbars, compile) {
return function(context, options) {
options = options || {};
var namespace = options.partial ? options : Handlebars,
helpers,
partials;
var result = templateSpec.call(container, Hbars, context, options.helpers, options.partials, options.data);
if (!options.partial) {
helpers = options.helpers;
partials = options.partials;
}
var result = templateSpec.call(
container,
namespace, context,
helpers,
partials,
options.data);
var compilerInfo = container.compilerInfo || [],
compilerRevision = compilerInfo[0] || 1,
currentRevision = COMPILER_REVISION;
if (compilerRevision !== currentRevision) {
if (compilerRevision < currentRevision) {
var runtimeVersions = REVISION_CHANGES[currentRevision],
compilerVersions = REVISION_CHANGES[compilerRevision];
throw "Template was precompiled with an older version of Handlebars than the current runtime. "+
"Please update your precompiler to a newer version ("+runtimeVersions+") or downgrade your runtime to an older version ("+compilerVersions+").";
} else {
// Use the embedded version info since the runtime doesn't know about this revision yet
throw "Template was precompiled with a newer version of Handlebars than the current runtime. "+
"Please update your runtime to a newer version ("+compilerInfo[1]+").";
}
if (!options.partial) {
checkRevision(container.compilerInfo);
}
return result;
@@ -105,7 +124,7 @@ export function program(i, fn, data) {
}
export function invokePartial(partial, name, context, helpers, partials, data) {
var options = { helpers: helpers, partials: partials, data: data };
var options = { partial: true, helpers: helpers, partials: partials, data: data };
if(partial === undefined) {
throw new Exception("The partial " + name + " could not be found");
+4 -3
View File
@@ -1,4 +1,5 @@
var toString = Object.prototype.toString;
var toString = Object.prototype.toString,
isArray = Array.isArray;
var errorProps = ['description', 'fileName', 'lineNumber', 'message', 'name', 'number', 'stack'];
@@ -50,7 +51,7 @@ export function escapeExpression(string) {
// don't escape SafeStrings, since they're already safe
if (string instanceof SafeString) {
return string.toString();
} else if (string == null || string === false) {
} else if (!string && string !== 0) {
return "";
}
@@ -66,7 +67,7 @@ export function escapeExpression(string) {
export function isEmpty(value) {
if (!value && value !== 0) {
return true;
} else if(toString.call(value) === "[object Array]" && value.length === 0) {
} else if (isArray(value) && value.length === 0) {
return true;
} else {
return false;
+22 -7
View File
@@ -1,12 +1,8 @@
{
"name": "handlebars.js",
"barename": "handlebars",
"version": "1.1.0",
"version": "1.0.12",
"description": "Handlebars provides the power necessary to let you build semantic templates effectively with no frustration",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"repository": {
"type": "git",
"url": "https://github.com/wycats/handlebars.js.git"
@@ -15,17 +11,36 @@
"license": "BSD",
"readmeFilename": "README.md",
"devDependencies": {
"async": "~0.2.9",
"aws-sdk": "~1.5.0",
"benchmark": "~1.0",
"dustjs-linkedin": "~2.0.2",
"eco": "~1.1.0-rc-3",
"grunt": "~0.4.1",
"connect": "~2.7.4",
"grunt-contrib-concat": "~0.3.0",
"grunt-contrib-clean": "~0.4.1",
"grunt-contrib-connect": "~0.3.0",
"grunt-contrib-jshint": "~0.6.3",
"grunt-contrib-uglify": "~0.2.2",
"grunt-contrib-watch": "~0.4.4",
"grunt-hang": "~0.1.2",
"grunt-es6-module-transpiler": "~0.4.1",
"es6-module-transpiler": "*",
"jison": "~0.3.0",
"keen.io": "0.0.3",
"mocha": "*",
"should": "~1.2.2"
}
"mustache": "~0.7.2",
"semver": "~2.1.0",
"should": "~1.2.2",
"underscore": "~1.5.1"
},
"main": "lib/handlebars.js",
"bin": {
"handlebars": "bin/handlebars"
},
"scripts": {
"test": "node ./spec/env/runner"
},
"optionalDependencies": {}
}
+10
View File
@@ -90,6 +90,16 @@ describe('builtin helpers', function() {
equal(result, "0. goodbye! 1. Goodbye! 2. GOODBYE! cruel world!", "The @index variable is used");
});
it("each with nested @index", function() {
var string = "{{#each goodbyes}}{{@index}}. {{text}}! {{#each ../goodbyes}}{{@index}} {{/each}}After {{@index}} {{/each}}{{@index}}cruel {{world}}!";
var hash = {goodbyes: [{text: "goodbye"}, {text: "Goodbye"}, {text: "GOODBYE"}], world: "world"};
var template = CompilerContext.compile(string);
var result = template(hash);
equal(result, "0. goodbye! 0 1 2 After 0 1. Goodbye! 0 1 2 After 1 2. GOODBYE! 0 1 2 After 2 cruel world!", "The @index variable is used");
});
it("each with function argument", function() {
var string = "{{#each goodbyes}}{{text}}! {{/each}}cruel {{world}}!";
var hash = {goodbyes: function () { return [{text: "goodbye"}, {text: "Goodbye"}, {text: "GOODBYE"}];}, world: "world"};
+5 -4
View File
@@ -110,18 +110,19 @@ describe('data', function() {
});
it("data is inherited downstream", function() {
var template = CompilerContext.compile("{{#let foo=bar.baz}}{{@foo}}{{/let}}", { data: true });
var template = CompilerContext.compile("{{#let foo=1 bar=2}}{{#let foo=bar.baz}}{{@bar}}{{@foo}}{{/let}}{{@foo}}{{/let}}", { data: true });
var helpers = {
let: function(options) {
var frame = Handlebars.createFrame(options.data);
for (var prop in options.hash) {
options.data[prop] = options.hash[prop];
frame[prop] = options.hash[prop];
}
return options.fn(this);
return options.fn(this, {data: frame});
}
};
var result = template({ bar: { baz: "hello world" } }, { helpers: helpers, data: {} });
equals("hello world", result, "data variables are inherited downstream");
equals("2hello world1", result, "data variables are inherited downstream");
});
it("passing in data to a compiled function that expects data - works with helpers in partials", function() {
+63
View File
@@ -0,0 +1,63 @@
var _ = require('underscore'),
async = require('async'),
git = require('./util/git'),
Keen = require('keen.io'),
metrics = require('../bench');
module.exports = function(grunt) {
grunt.registerTask('metrics', function() {
var done = this.async(),
execName = grunt.option('name'),
events = {},
projectId = process.env.KEEN_PROJECTID,
writeKey = process.env.KEEN_WRITEKEY,
keen;
if (!execName && projectId && writeKey) {
keen = Keen.configure({
projectId: projectId,
writeKey: writeKey
});
}
async.each(_.keys(metrics), function(name, complete) {
if (/^_/.test(name) || (execName && name !== execName)) {
return complete();
}
metrics[name](grunt, function(data) {
events[name] = data;
complete();
});
},
function() {
if (!keen) {
return done();
}
emit(keen, events, function(err, res) {
if (err) {
throw err;
}
grunt.log.writeln('Metrics recorded.');
done();
});
});
});
};
function emit(keen, collections, callback) {
git.commitInfo(function(err, info) {
_.each(collections, function(collection) {
_.each(collection, function(event) {
if (info.tagName) {
event.tag = info.tagName;
}
event.sha = info.head;
});
});
keen.addEvents(collections, callback);
});
}
+23
View File
@@ -0,0 +1,23 @@
var childProcess = require('child_process');
module.exports = function(grunt) {
grunt.registerTask('parser', 'Generate jison parser.', function() {
var done = this.async();
var child = childProcess.spawn('./node_modules/.bin/jison', ['-m', 'js', 'src/handlebars.yy', 'src/handlebars.l'], {stdio: 'inherit'});
child.on('exit', function(code) {
if (code != 0) {
grunt.fatal('Jison failure: ' + code);
done();
return;
}
var src = ['src/parser-prefix.js', 'handlebars.js', 'src/parser-suffix.js'].map(grunt.file.read).join('');
grunt.file.delete('handlebars.js');
grunt.file.write('lib/handlebars/compiler/parser.js', src);
grunt.log.writeln('Parser "lib/handlebars/compiler/parser.js" created.');
done();
});
});
};
+74
View File
@@ -0,0 +1,74 @@
var _ = require('underscore'),
async = require('async'),
AWS = require('aws-sdk'),
git = require('./util/git');
module.exports = function(grunt) {
grunt.registerTask('publish:latest', function() {
var done = this.async();
initSDK();
git.debug(function(remotes, branches) {
grunt.log.writeln('remotes: ' + remotes);
grunt.log.writeln('branches: ' + branches);
git.commitInfo(function(err, info) {
if (info.isMaster) {
publish(fileMap(['-latest', '-' + info.head]), done);
} else {
// Silently ignore for branches
done();
}
});
});
});
grunt.registerTask('publish:version', function() {
var done = this.async();
initSDK();
git.commitInfo(function(err, info) {
if (!info.tagName) {
throw new Error('The current commit must be tagged');
}
publish(fileMap(['-' + info.tagName]), done);
});
});
function initSDK() {
var bucket = process.env.S3_BUCKET_NAME,
key = process.env.S3_ACCESS_KEY_ID,
secret = process.env.S3_SECRET_ACCESS_KEY;
if (!bucket || !key || !secret) {
throw new Error('Missing S3 config values');
}
AWS.config.update({accessKeyId: key, secretAccessKey: secret});
}
function publish(files, callback) {
var s3 = new AWS.S3(),
bucket = process.env.S3_BUCKET_NAME;
async.forEach(_.keys(files), function(file, callback) {
var params = {Bucket: bucket, Key: file, Body: grunt.file.read(files[file])};
s3.putObject(params, function(err, data) {
if (err) {
throw err;
} else {
grunt.log.writeln('Published ' + file + ' to build server.');
callback();
}
});
},
callback);
}
function fileMap(suffixes) {
var map = {};
_.each(['handlebars.js', 'handlebars.min.js', 'handlebars.runtime.js', 'handlebars.runtime.min.js'], function(file) {
_.each(suffixes, function(suffix) {
map[file.replace(/\.js$/, suffix + '.js')] = 'dist/' + file;
});
});
return map;
}
};
+62
View File
@@ -0,0 +1,62 @@
var async = require('async'),
git = require('./util/git'),
semver = require('semver');
module.exports = function(grunt) {
grunt.registerTask('version', 'Updates the current release version', function() {
var done = this.async(),
pkg = grunt.config('pkg'),
version = grunt.option('ver');
if (version === 'major' || version === 'minor' || version === 'patch' || version === 'prerelease') {
version = semver.inc(pkg.version, version);
}
if (!semver.valid(version)) {
throw new Error('Must provide a version number (Ex: --ver=patch):\n\t' + version + '\n\n');
}
pkg.version = version;
grunt.config('pkg', pkg);
git.clean(function(err, clean) {
if (err || !clean) {
throw new Error('The repository must be clean');
}
grunt.log.write('Updating to version ' + version);
grunt.task.run(['build', 'tag']);
async.each([
['lib/handlebars/base.js', /Handlebars.VERSION = "(.*)";/, 'Handlebars.VERSION = "' + version + '";'],
['package.json', /"version":.*/, '"version": "' + version + '",'],
['bower.json', /"version":.*/, '"version": "' + version + '",'],
['handlebars.js.nuspec', /<version>.*<\/version>/, '<version>' + version + '</version>']
],
function(args, callback) {
replace.apply(undefined, args);
git.add(args[0], callback);
},
done);
});
});
grunt.registerTask('tag', 'Tags the current release version', function() {
var done = this.async(),
name = 'v' + grunt.config('pkg').version;
async.series([
function(callback) { git.add('dist/handlebars.js', callback); },
function(callback) { git.add('dist/handlebars.runtime.js', callback); },
function(callback) { git.commit(name, callback); },
function(callback) { git.tag(name, callback); }
],
done);
});
function replace(path, regex, replace) {
var content = grunt.file.read(path);
content = content.replace(regex, replace);
grunt.file.write(path, content);
}
};
+100
View File
@@ -0,0 +1,100 @@
var childProcess = require('child_process');
module.exports = {
debug: function(callback) {
childProcess.exec('git remote -v', {}, function(err, remotes) {
if (err) {
throw new Error('git.remote: ' + err.message);
}
childProcess.exec('git branch -a', {}, function(err, branches) {
if (err) {
throw new Error('git.branch: ' + err.message);
}
callback(remotes, branches);
});
});
},
clean: function(callback) {
childProcess.exec('git diff-index --name-only HEAD --', {}, function(err, stdout) {
callback(undefined, !err && !stdout);
});
},
commitInfo: function(callback) {
module.exports.head(function(err, headSha) {
module.exports.master(function(err, masterSha) {
module.exports.tagName(function(err, tagName) {
callback(undefined, {
head: headSha,
master: masterSha,
tagName: tagName,
isMaster: headSha === masterSha
});
});
});
});
},
head: function(callback) {
childProcess.exec('git rev-parse --short HEAD', {}, function(err, stdout) {
if (err) {
throw new Error('git.head: ' + err.message);
}
callback(undefined, stdout.trim());
});
},
master: function(callback) {
childProcess.exec('git rev-parse --short origin/master', {}, function(err, stdout) {
// This will error if master was not checked out but in this case we know we are not master
// so we can ignore.
if (err && !/Needed a single revision/.test(err.message)) {
throw new Error('git.master: ' + err.message);
}
callback(undefined, stdout.trim());
});
},
add: function(path, callback) {
childProcess.exec('git add -f ' + path, {}, function(err, stdout) {
if (err) {
throw new Error('git.add: ' + err.message);
}
callback();
});
},
commit: function(name, callback) {
childProcess.exec('git commit --message=' + name, {}, function(err, stdout) {
if (err) {
throw new Error('git.commit: ' + err.message);
}
callback();
});
},
tag: function(name, callback) {
childProcess.exec('git tag -a --message=' + name + ' ' + name, {}, function(err, stdout, stderr) {
if (err) {
throw new Error('git.tag: ' + err.message);
throw err;
}
callback();
});
},
tagName: function(callback) {
childProcess.exec('git tag -l --points-at HEAD', {}, function(err, stdout) {
if (err) {
throw new Error('git.tagName: ' + err.message);
}
var tags = stdout.trim().split(/\n/),
versionTags = tags.filter(function(tag) { return /^v/.test(tag); });
callback(undefined, versionTags[0] || tags[0]);
});
}
};