diff --git a/Gruntfile.js b/Gruntfile.js index 3468e4d38..29611c7f5 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -10,7 +10,7 @@ module.exports = function(grunt) { jshintrc: '.jshintrc' }, files: [ - 'dist/**/!(*.min|parser).js' + 'dist/cjs/**/!(*.min|parser).js' ] }, @@ -141,14 +141,11 @@ module.exports = function(grunt) { {browserName: 'firefox', version: '3.6'}, {browserName: 'safari', version: 7, platform: 'OS X 10.9'}, {browserName: 'safari', version: 6, platform: 'OS X 10.8'}, - {browserName: 'safari', version: 5}, {browserName: 'opera', version: 12}, {browserName: 'opera', version: 11}, {browserName: 'internet explorer', version: 11, platform: 'Windows 8.1'}, {browserName: 'internet explorer', version: 10, platform: 'Windows 8'}, {browserName: 'internet explorer', version: 9, platform: 'Windows 7'}, - {browserName: 'internet explorer', version: 8, platform: 'XP'}, - {browserName: 'internet explorer', version: 7, platform: 'XP'}, {browserName: 'internet explorer', version: 6, platform: 'XP'} ] } diff --git a/LICENSE b/LICENSE index f466a93fd..a2d22cbb4 100644 --- a/LICENSE +++ b/LICENSE @@ -1,4 +1,4 @@ -Copyright (C) 2011 by Yehuda Katz +Copyright (C) 2011-2014 by Yehuda Katz Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/README.markdown b/README.markdown index 0297c3589..1a284c1f0 100644 --- a/README.markdown +++ b/README.markdown @@ -132,7 +132,7 @@ into the person object you could still display the company's name with an expression like `{{../company.name}}`, so: ``` -{{#person}}{{name}} - {{../company.name}}{{/person}} +{{#with person}}{{name}} - {{../company.name}}{{/person}} ``` would render: @@ -352,9 +352,17 @@ See [release-notes.md](https://github.com/wycats/handlebars.js/blob/master/relea Known Issues ------------ +* Runtime/precompiler mismatches: Often result in errors like "can not find method match of object" or similar. Please verify the version of the runtime and the version used to precompile templates if odd issues occur after upgrading one component or another. * Handlebars.js can be cryptic when there's an error while rendering. * Using a variable, helper, or partial named `class` causes errors in IE browsers. (Instead, use `className`) +Reporting Issues +---------------- + +Should you run into other issues with the project, please file an [issue][issue]. When filing issues a repo case running against the latest version of the code is appreciated. A [jsfiddle template][jsfiddle] is available for this purpose. As new versions are released the bitly link will be updated to point to a fiddle template with the latest version. + +We also accept [pull requests][pull-request]! + Handlebars in the Wild ---------------------- @@ -371,6 +379,8 @@ Handlebars in the Wild with 100+ handlebars helpers. * [hbs](http://github.com/donpark/hbs): An Express.js view engine adapter for Handlebars.js, from Don Park. +* [koa-hbs](https://github.com/jwilm/koa-hbs): [koa](https://github.com/koajs/koa) generator based + renderer for Handlebars.js. * [jblotus](http://github.com/jblotus) created [http://tryhandlebarsjs.com](http://tryhandlebarsjs.com) for anyone who would like to try out Handlebars.js in their browser. * [jQuery plugin](http://71104.github.io/jquery-handlebars/): allows you to use @@ -435,8 +445,7 @@ Handlebars utilizes the [release yeoman generator][generator-release] to perform A full release may be completed with the following: ``` -yo release:notes patch -yo release:release patch +yo release npm publish yo release:publish cdnjs handlebars.js dist/cdnjs/ yo release:publish components handlebars.js dist/components/ @@ -446,7 +455,7 @@ gem build handlebars-source.gemspec gem push handlebars-source-*.gem ``` -After this point the handlebars site needs to be updated to point to the new version numbers. +After this point the handlebars site needs to be updated to point to the new version numbers. The jsfiddle bitly link should be updated to point to the most recent distribution. License ------- @@ -456,3 +465,5 @@ Handlebars.js is released under the MIT license. [builds-page]: http://builds.handlebarsjs.com.s3.amazonaws.com/bucket-listing.html?sort=lastmod&sortdir=desc [generator-release]: https://github.com/walmartlabs/generator-release [pull-request]: https://github.com/wycats/handlebars.js/pull/new/master +[issue]: https://github.com/wycats/handlebars.js/issues/new +[jsfiddle]: http://l.kde.cc/hbs-bug diff --git a/bench/templates/depth-1.js b/bench/templates/depth-1.js new file mode 100644 index 000000000..74809bca8 --- /dev/null +++ b/bench/templates/depth-1.js @@ -0,0 +1,6 @@ +module.exports = { + context: { names: [{name: "Moe"}, {name: "Larry"}, {name: "Curly"}, {name: "Shemp"}], foo: 'bar' }, + handlebars: "{{#each names}}{{../foo}}{{/each}}", + mustache: "{{#names}}{{foo}}{{/names}}", + eco: "<% for item in @names: %><%= @foo %><% end %>" +}; diff --git a/bench/templates/depth-2.js b/bench/templates/depth-2.js new file mode 100644 index 000000000..1d38baa4e --- /dev/null +++ b/bench/templates/depth-2.js @@ -0,0 +1,6 @@ +module.exports = { + context: { names: [{bat: 'foo', name: ["Moe"]}, {bat: 'foo', name: ["Larry"]}, {bat: 'foo', name: ["Curly"]}, {bat: 'foo', name: ["Shemp"]}], foo: 'bar' }, + handlebars: "{{#each names}}{{#each name}}{{../bat}}{{../../foo}}{{/each}}{{/each}}", + mustache: "{{#names}}{{#name}}{{bat}}{{foo}}{{/name}}{{/names}}", + eco: "<% for item in @names: %><% for child in item.name: %><%= item.bat %><%= @foo %><% end %><% end %>" +}; diff --git a/bin/handlebars b/bin/handlebars index 247ddd540..d7c489b0d 100755 --- a/bin/handlebars +++ b/bin/handlebars @@ -139,7 +139,7 @@ if (!argv.simple) { output.push(argv.namespace); output.push(' || {};\n'); } -function processTemplate(template, root) { +function processTemplate(template, root, explicit) { var path = template, stat = fs.statSync(path); if (stat.isDirectory()) { @@ -150,7 +150,7 @@ function processTemplate(template, root) { processTemplate(path, root || template); } }); - } else if (extension.test(path)) { + } else if (explicit || extension.test(path)) { var data = fs.readFileSync(path, 'utf8'); if (argv.bom && data.indexOf('\uFEFF') === 0) { @@ -191,7 +191,7 @@ function processTemplate(template, root) { } argv._.forEach(function(template) { - processTemplate(template, argv.root); + processTemplate(template, argv.root, true); }); // Output the content diff --git a/components/bower.json b/components/bower.json index 25923a475..571a7b87b 100644 --- a/components/bower.json +++ b/components/bower.json @@ -1,6 +1,6 @@ { "name": "handlebars", - "version": "1.3.0", + "version": "v2.0.0-alpha.1", "main": "handlebars.js", "dependencies": {} } diff --git a/components/handlebars-source.gemspec b/components/handlebars-source.gemspec index 4313e9581..9123fddcd 100644 --- a/components/handlebars-source.gemspec +++ b/components/handlebars-source.gemspec @@ -12,6 +12,7 @@ Gem::Specification.new do |gem| gem.summary = %q{Handlebars.js source code wrapper} gem.homepage = "https://github.com/wycats/handlebars.js/" gem.version = package["version"] + gem.license = "MIT" gem.files = [ 'handlebars.js', diff --git a/components/handlebars.js.nuspec b/components/handlebars.js.nuspec index e4a169103..586a211a2 100644 --- a/components/handlebars.js.nuspec +++ b/components/handlebars.js.nuspec @@ -2,7 +2,7 @@ handlebars.js - 1.3.0 + v2.0.0-alpha.1 handlebars.js Authors https://github.com/wycats/handlebars.js/blob/master/LICENSE https://github.com/wycats/handlebars.js/ diff --git a/lib/handlebars/base.js b/lib/handlebars/base.js index 00c8bf1ff..827bcb7c2 100644 --- a/lib/handlebars/base.js +++ b/lib/handlebars/base.js @@ -1,14 +1,15 @@ module Utils from "./utils"; import Exception from "./exception"; -export var VERSION = "1.3.0"; -export var COMPILER_REVISION = 4; +export var VERSION = "v2.0.0-alpha.1"; +export var COMPILER_REVISION = 5; export var REVISION_CHANGES = { 1: '<= 1.0.rc.2', // 1.0.rc.2 is actually rev2 but doesn't report it 2: '== 1.0.0-rc.3', 3: '== 1.0.0-rc.4', - 4: '>= 1.0.0' + 4: '== 1.x.x', + 5: '>= 2.0.0' }; var isArray = Utils.isArray, @@ -38,6 +39,9 @@ HandlebarsEnvironment.prototype = { this.helpers[name] = fn; } }, + unregisterHelper: function(name) { + delete this.helpers[name]; + }, registerPartial: function(name, str) { if (toString.call(name) === objectType) { @@ -45,15 +49,20 @@ HandlebarsEnvironment.prototype = { } else { this.partials[name] = str; } + }, + unregisterPartial: function(name) { + delete this.partials[name]; } }; function registerDefaultHelpers(instance) { - instance.registerHelper('helperMissing', function(arg) { - if(arguments.length === 2) { + instance.registerHelper('helperMissing', function(/* [args, ]options */) { + if(arguments.length === 1) { + // A missing field in a {{foo}} constuct. return undefined; } else { - throw new Exception("Missing helper: '" + arg + "'"); + // Someone is actually trying to call something, blow up. + throw new Exception("Missing helper: '" + arguments[arguments.length-1].name + "'"); } }); @@ -68,19 +77,40 @@ function registerDefaultHelpers(instance) { return inverse(this); } else if (isArray(context)) { if(context.length > 0) { + if (options.ids) { + options.ids = [options.name]; + } + return instance.helpers.each(context, options); } else { return inverse(this); } } else { - return fn(context); + if (options.data && options.ids) { + var data = createFrame(options.data); + data.contextPath = Utils.appendContextPath(options.data.contextPath, options.name); + options = {data: data}; + } + + return fn(context, options); } }); instance.registerHelper('each', function(context, options) { + // Allow for {{#each}} + if (!options) { + options = context; + context = this; + } + var fn = options.fn, inverse = options.inverse; var i = 0, ret = "", data; + var contextPath; + if (options.data && options.ids) { + contextPath = Utils.appendContextPath(options.data.contextPath, options.ids[0]) + '.'; + } + if (isFunction(context)) { context = context.call(this); } if (options.data) { @@ -94,16 +124,24 @@ function registerDefaultHelpers(instance) { data.index = i; data.first = (i === 0); data.last = (i === (context.length-1)); + + if (contextPath) { + data.contextPath = contextPath + i; + } } ret = ret + fn(context[i], { data: data }); } } else { for(var key in context) { if(context.hasOwnProperty(key)) { - if(data) { - data.key = key; + if(data) { + data.key = key; data.index = i; data.first = (i === 0); + + if (contextPath) { + data.contextPath = contextPath + key; + } } ret = ret + fn(context[key], {data: data}); i++; @@ -139,13 +177,27 @@ function registerDefaultHelpers(instance) { instance.registerHelper('with', function(context, options) { if (isFunction(context)) { context = context.call(this); } - if (!Utils.isEmpty(context)) return options.fn(context); + var fn = options.fn; + + if (!Utils.isEmpty(context)) { + if (options.data && options.ids) { + var data = createFrame(options.data); + data.contextPath = Utils.appendContextPath(options.data.contextPath, options.ids[0]); + options = {data:data}; + } + + return fn(context, options); + } }); instance.registerHelper('log', function(context, options) { var level = options.data && options.data.level != null ? parseInt(options.data.level, 10) : 1; instance.log(level, context); }); + + instance.registerHelper('lookup', function(obj, field, options) { + return obj && obj[field]; + }); } export var logger = { @@ -172,7 +224,7 @@ export var logger = { export function log(level, obj) { logger.log(level, obj); } export var createFrame = function(object) { - var obj = {}; - Utils.extend(obj, object); - return obj; + var frame = Utils.extend({}, object); + frame._parent = object; + return frame; }; diff --git a/lib/handlebars/compiler/ast.js b/lib/handlebars/compiler/ast.js index 002fd3be6..286917dbe 100644 --- a/lib/handlebars/compiler/ast.js +++ b/lib/handlebars/compiler/ast.js @@ -83,25 +83,26 @@ var AST = { var id = this.id = rawParams[0]; var params = this.params = rawParams.slice(1); - // a mustache is an eligible helper if: - // * its id is simple (a single part, not `this` or `..`) - var eligibleHelper = this.eligibleHelper = id.isSimple; - // a mustache is definitely a helper if: // * it is an eligible helper, and // * it has at least one parameter or hash segment - this.isHelper = eligibleHelper && (params.length || hash); + this.isHelper = params.length || hash; + + // a mustache is an eligible helper if: + // * its id is simple (a single part, not `this` or `..`) + this.eligibleHelper = this.isHelper || id.isSimple; // if a mustache is an eligible helper but not a definite // helper, it is ambiguous, and will be resolved in a later // pass or at runtime. }, - PartialNode: function(partialName, context, strip, locInfo) { + PartialNode: function(partialName, context, hash, strip, locInfo) { LocationInfo.call(this, locInfo); this.type = "partial"; this.partialName = partialName; this.context = context; + this.hash = hash; this.strip = strip; }, @@ -130,6 +131,20 @@ var AST = { } }, + RawBlockNode: function(mustache, content, close, locInfo) { + LocationInfo.call(this, locInfo); + + if (mustache.sexpr.id.original !== close) { + throw new Exception(mustache.sexpr.id.original + " doesn't match " + close, this); + } + + content = new AST.ContentNode(content, locInfo); + + this.type = 'block'; + this.mustache = mustache; + this.program = new AST.ProgramNode([content], locInfo); + }, + ContentNode: function(string, locInfo) { LocationInfo.call(this, locInfo); this.type = "content"; @@ -148,7 +163,8 @@ var AST = { var original = "", dig = [], - depth = 0; + depth = 0, + depthString = ''; for(var i=0,l=parts.length; i 0) { - this.source[1] = this.source[1] + ", " + locals.join(", "); + varDeclarations += ", " + locals.join(", "); } // Generate minimizer alias mappings - if (!this.isChild) { - for (var alias in this.context.aliases) { - if (this.context.aliases.hasOwnProperty(alias)) { - this.source[1] = this.source[1] + ', ' + alias + '=' + this.context.aliases[alias]; - } + for (var alias in this.aliases) { + if (this.aliases.hasOwnProperty(alias)) { + varDeclarations += ', ' + alias + '=' + this.aliases[alias]; } } - if (this.source[1]) { - this.source[1] = "var " + this.source[1].substring(2) + ";"; - } - - // Merge children - if (!this.isChild) { - this.source[1] += '\n' + this.context.programs.join('\n') + '\n'; - } - - if (!this.environment.isSimple) { - this.pushSource("return buffer;"); - } - - var params = this.isChild ? ["depth0", "data"] : ["Handlebars", "depth0", "helpers", "partials", "data"]; + var params = ["depth0", "helpers", "partials", "data"]; for(var i=0, l=this.environment.depths.list.length; i " + content + " }}"); }; @@ -103,8 +108,8 @@ PrintVisitor.prototype.STRING = function(string) { return '"' + string.string + '"'; }; -PrintVisitor.prototype.INTEGER = function(integer) { - return "INTEGER{" + integer.integer + "}"; +PrintVisitor.prototype.NUMBER = function(number) { + return "NUMBER{" + number.number + "}"; }; PrintVisitor.prototype.BOOLEAN = function(bool) { diff --git a/lib/handlebars/runtime.js b/lib/handlebars/runtime.js index 5ae8d8a78..b179620c9 100644 --- a/lib/handlebars/runtime.js +++ b/lib/handlebars/runtime.js @@ -1,6 +1,6 @@ module Utils from "./utils"; import Exception from "./exception"; -import { COMPILER_REVISION, REVISION_CHANGES } from "./base"; +import { COMPILER_REVISION, REVISION_CHANGES, createFrame } from "./base"; export function checkRevision(compilerInfo) { var compilerRevision = compilerInfo && compilerInfo[0] || 1, @@ -29,8 +29,14 @@ export function template(templateSpec, env) { // Note: Using env.VM references rather than local var references throughout this section to allow // for external users to override these as psuedo-supported APIs. - var invokePartialWrapper = function(partial, name, context, helpers, partials, data) { - var result = env.VM.invokePartial.apply(this, arguments); + env.VM.checkRevision(templateSpec.compiler); + + var invokePartialWrapper = function(partial, name, context, hash, helpers, partials, data) { + if (hash) { + context = Utils.extend({}, context, hash); + } + + var result = env.VM.invokePartial.call(this, partial, name, context, helpers, partials, data); if (result != null) { return result; } if (env.compile) { @@ -46,74 +52,101 @@ export function template(templateSpec, env) { var container = { escapeExpression: Utils.escapeExpression, invokePartial: invokePartialWrapper, + + fn: function(i) { + return templateSpec[i]; + }, + programs: [], - program: function(i, fn, data) { - var programWrapper = this.programs[i]; + program: function(i, data) { + var programWrapper = this.programs[i], + fn = this.fn(i); if(data) { - programWrapper = program(i, fn, data); + programWrapper = program(this, i, fn, data); } else if (!programWrapper) { - programWrapper = this.programs[i] = program(i, fn); + programWrapper = this.programs[i] = program(this, i, fn); } return programWrapper; }, + programWithDepth: env.VM.programWithDepth, + + initData: function(context, data) { + if (!data || !('root' in data)) { + data = data ? createFrame(data) : {}; + data.root = context; + } + return data; + }, + data: function(data, depth) { + while (data && depth--) { + data = data._parent; + } + return data; + }, merge: function(param, common) { var ret = param || common; if (param && common && (param !== common)) { - ret = {}; - Utils.extend(ret, common); - Utils.extend(ret, param); + ret = Utils.extend({}, common, param); } + return ret; }, - programWithDepth: env.VM.programWithDepth, + noop: env.VM.noop, - compilerInfo: null + compilerInfo: templateSpec.compiler }; - return function(context, options) { + var ret = function(context, options) { options = options || {}; var namespace = options.partial ? options : env, helpers, - partials; + partials, + data = options.data; if (!options.partial) { - helpers = options.helpers; - partials = options.partials; - } - var result = templateSpec.call( - container, - namespace, context, - helpers, - partials, - options.data); + helpers = container.helpers = container.merge(options.helpers, namespace.helpers); - if (!options.partial) { - env.VM.checkRevision(container.compilerInfo); + if (templateSpec.usePartial) { + partials = container.partials = container.merge(options.partials, namespace.partials); + } + if (templateSpec.useData) { + data = container.initData(context, data); + } + } else { + helpers = container.helpers = options.helpers; + partials = container.partials = options.partials; } + return templateSpec.main.call(container, context, helpers, partials, data); + }; - return result; + ret.child = function(i) { + return container.programWithDepth(i); }; + return ret; } -export function programWithDepth(i, fn, data /*, $depth */) { - var args = Array.prototype.slice.call(arguments, 3); +export function programWithDepth(i, data /*, $depth */) { + /*jshint -W040 */ + var args = Array.prototype.slice.call(arguments, 2), + container = this, + fn = container.fn(i); var prog = function(context, options) { options = options || {}; - return fn.apply(this, [context, options.data || data].concat(args)); + return fn.apply(container, [context, container.helpers, container.partials, options.data || data].concat(args)); }; prog.program = i; prog.depth = args.length; return prog; } -export function program(i, fn, data) { +export function program(container, i, fn, data) { var prog = function(context, options) { options = options || {}; - return fn(context, options.data || data); + return fn.call(container, context, container.helpers, container.partials, options.data || data); }; prog.program = i; prog.depth = 0; diff --git a/lib/handlebars/utils.js b/lib/handlebars/utils.js index ed2e1d8ff..f2f1a5470 100644 --- a/lib/handlebars/utils.js +++ b/lib/handlebars/utils.js @@ -17,12 +17,16 @@ function escapeChar(chr) { return escape[chr] || "&"; } -export function extend(obj, value) { - for(var key in value) { - if(Object.prototype.hasOwnProperty.call(value, key)) { - obj[key] = value[key]; +export function extend(obj /* , ...source */) { + for (var i = 1; i < arguments.length; i++) { + for (var key in arguments[i]) { + if (Object.prototype.hasOwnProperty.call(arguments[i], key)) { + obj[key] = arguments[i][key]; + } } } + + return obj; } export var toString = Object.prototype.toString; @@ -71,3 +75,7 @@ export function isEmpty(value) { return false; } } + +export function appendContextPath(contextPath, id) { + return (contextPath ? contextPath + '.' : '') + id; +} diff --git a/package.json b/package.json index 6403c5d46..9bfc9575b 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "handlebars", "barename": "handlebars", - "version": "1.3.0", + "version": "v2.0.0-alpha.1", "description": "Handlebars provides the power necessary to let you build semantic templates effectively with no frustration", "homepage": "http://www.handlebarsjs.com/", "keywords": [ @@ -43,7 +43,7 @@ "grunt-contrib-uglify": "~0.2.2", "grunt-contrib-watch": "~0.5.3", "grunt-saucelabs": "~4.1.2", - "es6-module-packager": "0.x", + "es6-module-packager": "1.x", "jison": "~0.3.0", "keen.io": "0.0.3", "mocha": "*", diff --git a/release-notes.md b/release-notes.md index 3f0fac995..43f97d709 100644 --- a/release-notes.md +++ b/release-notes.md @@ -2,7 +2,41 @@ ## Development -[Commits](https://github.com/wycats/handlebars.js/compare/v1.3.0...master) +[Commits](https://github.com/wycats/handlebars.js/compare/v2.0.0-alpha.1...master) + +## v2.0.0-alpha.1 - February 10th, 2014 +- [#182](https://github.com/wycats/handlebars.js/pull/182) - Allow passing hash parameters to partials ([@kpdecker](https://api.github.com/users/kpdecker)) +- [#392](https://github.com/wycats/handlebars.js/pull/392) - Access to root context in partials and helpers ([@kpdecker](https://api.github.com/users/kpdecker)) +- [#472](https://github.com/wycats/handlebars.js/issues/472) - Helpers cannot have decimal parameters ([@kayleg](https://api.github.com/users/kayleg)) +- [#569](https://github.com/wycats/handlebars.js/pull/569) - Unable to lookup array values using @index ([@kpdecker](https://api.github.com/users/kpdecker)) +- [#491](https://github.com/wycats/handlebars.js/pull/491) - For nested helpers: get the @ variables of the outer helper from the inner one ([@kpdecker](https://api.github.com/users/kpdecker)) +- [#669](https://github.com/wycats/handlebars.js/issues/669) - Ability to unregister a helper ([@dbachrach](https://api.github.com/users/dbachrach)) +- [#730](https://github.com/wycats/handlebars.js/pull/730) - Raw block helpers ([@kpdecker](https://api.github.com/users/kpdecker)) +- [#634](https://github.com/wycats/handlebars.js/pull/634) - It would be great to have the helper name passed to `blockHelperMissing` ([@kpdecker](https://api.github.com/users/kpdecker)) +- [#729](https://github.com/wycats/handlebars.js/pull/729) - Convert template spec to object literal ([@kpdecker](https://api.github.com/users/kpdecker)) + +- [#658](https://github.com/wycats/handlebars.js/issues/658) - Depthed helpers do not work after an upgrade from 1.0.0 ([@xibxor](https://api.github.com/users/xibxor)) +- [#671](https://github.com/wycats/handlebars.js/issues/671) - Crashes on no-parameter {{#each}} ([@stepancheg](https://api.github.com/users/stepancheg)) +- [#689](https://github.com/wycats/handlebars.js/issues/689) - broken template precompilation ([@AAS](https://api.github.com/users/AAS)) +- [#698](https://github.com/wycats/handlebars.js/pull/698) - Fix parser generation under windows ([@osiris43](https://api.github.com/users/osiris43)) +- [#699](https://github.com/wycats/handlebars.js/issues/699) - @DATA not compiles to invalid JS in stringParams mode ([@kpdecker](https://api.github.com/users/kpdecker)) +- [#705](https://github.com/wycats/handlebars.js/issues/705) - 1.3.0 can not be wrapped in an IIFE ([@craigteegarden](https://api.github.com/users/craigteegarden)) +- [#706](https://github.com/wycats/handlebars.js/pull/706) - README: Use with helper instead of relying on blockHelperMissing ([@scottgonzalez](https://api.github.com/users/scottgonzalez)) + +- [#700](https://github.com/wycats/handlebars.js/pull/700) - Remove redundant conditions ([@blakeembrey](https://api.github.com/users/blakeembrey)) +- [#704](https://github.com/wycats/handlebars.js/pull/704) - JavaScript Compiler Cleanup ([@blakeembrey](https://api.github.com/users/blakeembrey)) + +Compatibility notes: +- `helperMissing` helper no longer has the indexed name argument. Helper name is now available via `options.name`. +- Precompiler output has changed, which breaks compatibility with prior versions of the runtime and precompiled output. +- `JavaScriptCompiler.compilerInfo` now returns generic objects rather than javascript source. +- AST changes + - INTEGER -> NUMBER + - Additional PartialNode hash parameter + - New RawBlockNode type +- Data frames now have a `_parent` field. This is internal but is enumerable for performance/compatability reasons. + +[Commits](https://github.com/wycats/handlebars.js/compare/v1.3.0...v2.0.0-alpha.1) ## v1.3.0 - January 1st, 2014 - [#690](https://github.com/wycats/handlebars.js/pull/690) - Added support for subexpressions ([@machty](https://api.github.com/users/machty)) diff --git a/spec/amd.html b/spec/amd.html index 544b57611..a095650f4 100644 --- a/spec/amd.html +++ b/spec/amd.html @@ -45,9 +45,14 @@ }; function safeEval(templateSpec) { - var ret; - eval('ret = ' + templateSpec); - return ret; + try { + var ret; + eval('ret = ' + templateSpec); + return ret; + } catch (err) { + console.error(templateSpec); + throw err; + } } diff --git a/spec/javascript-compiler.js b/spec/javascript-compiler.js index 884abf1c9..16058680b 100644 --- a/spec/javascript-compiler.js +++ b/spec/javascript-compiler.js @@ -32,7 +32,7 @@ describe('javascript-compiler api', function() { }); it('should allow compilerInfo override', function() { handlebarsEnv.JavaScriptCompiler.prototype.compilerInfo = function() { - return 'this.compilerInfo = "crazy";'; + return 'crazy'; }; handlebarsEnv.VM.checkRevision = function(compilerInfo) { if (compilerInfo !== 'crazy') { @@ -45,10 +45,12 @@ describe('javascript-compiler api', function() { describe('buffer', function() { var $superAppend, $superCreate; beforeEach(function() { + handlebarsEnv.JavaScriptCompiler.prototype.forceBuffer = true; $superAppend = handlebarsEnv.JavaScriptCompiler.prototype.appendToBuffer; $superCreate = handlebarsEnv.JavaScriptCompiler.prototype.initializeBuffer; }); afterEach(function() { + handlebarsEnv.JavaScriptCompiler.prototype.forceBuffer = false; handlebarsEnv.JavaScriptCompiler.prototype.appendToBuffer = $superAppend; handlebarsEnv.JavaScriptCompiler.prototype.initializeBuffer = $superCreate; }); diff --git a/spec/parser.js b/spec/parser.js index 70c16359e..ebde17196 100644 --- a/spec/parser.js +++ b/spec/parser.js @@ -21,6 +21,10 @@ describe('parser', function() { equals(ast_for("{{@foo}}"), "{{ @ID:foo [] }}\n"); }); + it('parses simple mustaches with data paths', function() { + equals(ast_for("{{@../foo}}"), "{{ @ID:foo [] }}\n"); + }); + it('parses mustaches with paths', function() { equals(ast_for("{{foo/bar}}"), "{{ PATH:foo/bar [] }}\n"); }); @@ -41,8 +45,8 @@ describe('parser', function() { equals(ast_for("{{foo bar \"baz\" }}"), '{{ ID:foo [ID:bar, "baz"] }}\n'); }); - it('parses mustaches with INTEGER parameters', function() { - equals(ast_for("{{foo 1}}"), "{{ ID:foo [INTEGER{1}] }}\n"); + it('parses mustaches with NUMBER parameters', function() { + equals(ast_for("{{foo 1}}"), "{{ ID:foo [NUMBER{1}] }}\n"); }); it('parses mustaches with BOOLEAN parameters', function() { @@ -56,7 +60,7 @@ describe('parser', function() { it('parses mustaches with hash arguments', function() { equals(ast_for("{{foo bar=baz}}"), "{{ ID:foo [] HASH{bar=ID:baz} }}\n"); - equals(ast_for("{{foo bar=1}}"), "{{ ID:foo [] HASH{bar=INTEGER{1}} }}\n"); + equals(ast_for("{{foo bar=1}}"), "{{ ID:foo [] HASH{bar=NUMBER{1}} }}\n"); equals(ast_for("{{foo bar=true}}"), "{{ ID:foo [] HASH{bar=BOOLEAN{true}} }}\n"); equals(ast_for("{{foo bar=false}}"), "{{ ID:foo [] HASH{bar=BOOLEAN{false}} }}\n"); equals(ast_for("{{foo bar=@baz}}"), "{{ ID:foo [] HASH{bar=@ID:baz} }}\n"); @@ -67,7 +71,7 @@ describe('parser', function() { equals(ast_for("{{foo bat='bam'}}"), '{{ ID:foo [] HASH{bat="bam"} }}\n'); equals(ast_for("{{foo omg bar=baz bat=\"bam\"}}"), '{{ ID:foo [ID:omg] HASH{bar=ID:baz, bat="bam"} }}\n'); - equals(ast_for("{{foo omg bar=baz bat=\"bam\" baz=1}}"), '{{ ID:foo [ID:omg] HASH{bar=ID:baz, bat="bam", baz=INTEGER{1}} }}\n'); + equals(ast_for("{{foo omg bar=baz bat=\"bam\" baz=1}}"), '{{ ID:foo [ID:omg] HASH{bar=ID:baz, bat="bam", baz=NUMBER{1}} }}\n'); equals(ast_for("{{foo omg bar=baz bat=\"bam\" baz=true}}"), '{{ ID:foo [ID:omg] HASH{bar=ID:baz, bat="bam", baz=BOOLEAN{true}} }}\n'); equals(ast_for("{{foo omg bar=baz bat=\"bam\" baz=false}}"), '{{ ID:foo [ID:omg] HASH{bar=ID:baz, bat="bam", baz=BOOLEAN{false}} }}\n'); }); @@ -84,6 +88,14 @@ describe('parser', function() { equals(ast_for("{{> foo bar}}"), "{{> PARTIAL:foo ID:bar }}\n"); }); + it('parses a partial with hash', function() { + equals(ast_for("{{> foo bar=bat}}"), "{{> PARTIAL:foo HASH{bar=ID:bat} }}\n"); + }); + + it('parses a partial with context and hash', function() { + equals(ast_for("{{> foo bar bat=baz}}"), "{{> PARTIAL:foo ID:bar HASH{bat=ID:baz} }}\n"); + }); + it('parses a partial with a complex name', function() { equals(ast_for("{{> shared/partial?.bar}}"), "{{> PARTIAL:shared/partial?.bar }}\n"); }); @@ -149,6 +161,10 @@ describe('parser', function() { shouldThrow(function() { ast_for("{{#goodbyes}}{{/hellos}}"); }, Error, /goodbyes doesn't match hellos/); + + shouldThrow(function() { + ast_for("{{{{goodbyes}}}} {{{{/hellos}}}}"); + }, Error, /goodbyes doesn't match hellos/); }); it('knows how to report the correct line number in errors', function() { diff --git a/spec/partials.js b/spec/partials.js index bea72f5b7..732436af0 100644 --- a/spec/partials.js +++ b/spec/partials.js @@ -23,6 +23,14 @@ describe('partials', function() { shouldCompileToWithPartials(string, [hash, {}, {dude: partial}], true, "Dudes: Empty"); }); + it("partials with parameters", function() { + var string = "Dudes: {{#dudes}}{{> dude others=..}}{{/dudes}}"; + var partial = "{{others.foo}}{{name}} ({{url}}) "; + var hash = {foo: 'bar', dudes: [{name: "Yehuda", url: "http://yehuda"}, {name: "Alan", url: "http://alan"}]}; + shouldCompileToWithPartials(string, [hash, {}, {dude: partial}], true, "Dudes: barYehuda (http://yehuda) barAlan (http://alan) ", + "Basic partials output based on current context."); + }); + it("partial in a partial", function() { var string = "Dudes: {{#dudes}}{{>dude}}{{/dudes}}"; var dude = "{{name}} {{> url}} "; diff --git a/spec/string-params.js b/spec/string-params.js index 920b85592..1cebc6f08 100644 --- a/spec/string-params.js +++ b/spec/string-params.js @@ -158,4 +158,19 @@ describe('string params mode', function() { var result = template({}, {helpers: helpers}); equals(result, "WITH"); }); + + it('should handle DATA', function() { + var template = CompilerContext.compile('{{foo @bar}}', { stringParams: true }); + + var helpers = { + foo: function(bar, options) { + equal(bar, 'bar'); + equal(options.types[0], 'DATA'); + return 'Foo!'; + } + }; + + var result = template({}, { helpers: helpers }); + equal(result, 'Foo!'); + }); }); diff --git a/spec/tokenizer.js b/spec/tokenizer.js index 841a5abc1..36a632b8a 100644 --- a/spec/tokenizer.js +++ b/spec/tokenizer.js @@ -295,12 +295,20 @@ describe('Tokenizer', function() { it('tokenizes numbers', function() { var result = tokenize('{{ foo 1 }}'); - shouldMatchTokens(result, ['OPEN', 'ID', 'INTEGER', 'CLOSE']); - shouldBeToken(result[2], "INTEGER", "1"); + shouldMatchTokens(result, ['OPEN', 'ID', 'NUMBER', 'CLOSE']); + shouldBeToken(result[2], "NUMBER", "1"); + + result = tokenize('{{ foo 1.1 }}'); + shouldMatchTokens(result, ['OPEN', 'ID', 'NUMBER', 'CLOSE']); + shouldBeToken(result[2], "NUMBER", "1.1"); result = tokenize('{{ foo -1 }}'); - shouldMatchTokens(result, ['OPEN', 'ID', 'INTEGER', 'CLOSE']); - shouldBeToken(result[2], "INTEGER", "-1"); + shouldMatchTokens(result, ['OPEN', 'ID', 'NUMBER', 'CLOSE']); + shouldBeToken(result[2], "NUMBER", "-1"); + + result = tokenize('{{ foo -1.1 }}'); + shouldMatchTokens(result, ['OPEN', 'ID', 'NUMBER', 'CLOSE']); + shouldBeToken(result[2], "NUMBER", "-1.1"); }); it('tokenizes booleans', function() { @@ -321,7 +329,7 @@ describe('Tokenizer', function() { shouldMatchTokens(result, ['OPEN', 'ID', 'ID', 'ID', 'EQUALS', 'ID', 'CLOSE']); result = tokenize("{{ foo bar baz=1 }}"); - shouldMatchTokens(result, ['OPEN', 'ID', 'ID', 'ID', 'EQUALS', 'INTEGER', 'CLOSE']); + shouldMatchTokens(result, ['OPEN', 'ID', 'ID', 'ID', 'EQUALS', 'NUMBER', 'CLOSE']); result = tokenize("{{ foo bar baz=true }}"); shouldMatchTokens(result, ['OPEN', 'ID', 'ID', 'ID', 'EQUALS', 'BOOLEAN', 'CLOSE']); @@ -389,6 +397,6 @@ describe('Tokenizer', function() { it('tokenizes nested subexpressions: literals', function() { var result = tokenize("{{foo (bar (lol true) false) (baz 1) (blah 'b') (blorg \"c\")}}"); - shouldMatchTokens(result, ['OPEN', 'ID', 'OPEN_SEXPR', 'ID', 'OPEN_SEXPR', 'ID', 'BOOLEAN', 'CLOSE_SEXPR', 'BOOLEAN', 'CLOSE_SEXPR', 'OPEN_SEXPR', 'ID', 'INTEGER', 'CLOSE_SEXPR', 'OPEN_SEXPR', 'ID', 'STRING', 'CLOSE_SEXPR', 'OPEN_SEXPR', 'ID', 'STRING', 'CLOSE_SEXPR', 'CLOSE']); + shouldMatchTokens(result, ['OPEN', 'ID', 'OPEN_SEXPR', 'ID', 'OPEN_SEXPR', 'ID', 'BOOLEAN', 'CLOSE_SEXPR', 'BOOLEAN', 'CLOSE_SEXPR', 'OPEN_SEXPR', 'ID', 'NUMBER', 'CLOSE_SEXPR', 'OPEN_SEXPR', 'ID', 'STRING', 'CLOSE_SEXPR', 'OPEN_SEXPR', 'ID', 'STRING', 'CLOSE_SEXPR', 'CLOSE']); }); }); diff --git a/spec/track-ids.js b/spec/track-ids.js new file mode 100644 index 000000000..938f98bb5 --- /dev/null +++ b/spec/track-ids.js @@ -0,0 +1,163 @@ +/*global CompilerContext */ +describe('track ids', function() { + var context; + beforeEach(function() { + context = {is: {a: 'foo'}, slave: {driver: 'bar'}}; + }); + + it('should not include anything without the flag', function() { + var template = CompilerContext.compile('{{wycats is.a slave.driver}}'); + + var helpers = { + wycats: function(passiveVoice, noun, options) { + equal(options.ids, undefined); + equal(options.hashIds, undefined); + + return 'success'; + } + }; + + equals(template({}, {helpers: helpers}), 'success'); + }); + it('should include argument ids', function() { + var template = CompilerContext.compile('{{wycats is.a slave.driver}}', {trackIds: true}); + + var helpers = { + wycats: function(passiveVoice, noun, options) { + equal(options.ids[0], 'is.a'); + equal(options.ids[1], 'slave.driver'); + + return "HELP ME MY BOSS " + options.ids[0] + ':' + passiveVoice + ' ' + options.ids[1] + ':' + noun; + } + }; + + equals(template(context, {helpers: helpers}), 'HELP ME MY BOSS is.a:foo slave.driver:bar'); + }); + it('should include hash ids', function() { + var template = CompilerContext.compile('{{wycats bat=is.a baz=slave.driver}}', {trackIds: true}); + + var helpers = { + wycats: function(options) { + equal(options.hashIds.bat, 'is.a'); + equal(options.hashIds.baz, 'slave.driver'); + + return "HELP ME MY BOSS " + options.hashIds.bat + ':' + options.hash.bat + ' ' + options.hashIds.baz + ':' + options.hash.baz; + } + }; + + equals(template(context, {helpers: helpers}), 'HELP ME MY BOSS is.a:foo slave.driver:bar'); + }); + it('should note ../ and ./ references', function() { + var template = CompilerContext.compile('{{wycats ./is.a ../slave.driver}}', {trackIds: true}); + + var helpers = { + wycats: function(passiveVoice, noun, options) { + equal(options.ids[0], 'is.a'); + equal(options.ids[1], '../slave.driver'); + + return "HELP ME MY BOSS " + options.ids[0] + ':' + passiveVoice + ' ' + options.ids[1] + ':' + noun; + } + }; + + equals(template(context, {helpers: helpers}), 'HELP ME MY BOSS is.a:foo ../slave.driver:undefined'); + }); + it('should note @data references', function() { + var template = CompilerContext.compile('{{wycats @is.a @slave.driver}}', {trackIds: true}); + + var helpers = { + wycats: function(passiveVoice, noun, options) { + equal(options.ids[0], '@is.a'); + equal(options.ids[1], '@slave.driver'); + + return "HELP ME MY BOSS " + options.ids[0] + ':' + passiveVoice + ' ' + options.ids[1] + ':' + noun; + } + }; + + equals(template({}, {helpers: helpers, data:context}), 'HELP ME MY BOSS @is.a:foo @slave.driver:bar'); + }); + + it('should return null for constants', function() { + var template = CompilerContext.compile('{{wycats 1 "foo" key=false}}', {trackIds: true}); + + var helpers = { + wycats: function(passiveVoice, noun, options) { + equal(options.ids[0], null); + equal(options.ids[1], null); + equal(options.hashIds.key, null); + + return "HELP ME MY BOSS " + passiveVoice + ' ' + noun + ' ' + options.hash.key; + } + }; + + equals(template(context, {helpers: helpers}), 'HELP ME MY BOSS 1 foo false'); + }); + it('should return true for subexpressions', function() { + var template = CompilerContext.compile('{{wycats (sub)}}', {trackIds: true}); + + var helpers = { + sub: function() { return 1; }, + wycats: function(passiveVoice, options) { + equal(options.ids[0], true); + + return "HELP ME MY BOSS " + passiveVoice; + } + }; + + equals(template(context, {helpers: helpers}), 'HELP ME MY BOSS 1'); + }); + + describe('builtin helpers', function() { + var helpers = { + wycats: function(name, options) { + return name + ':' + options.data.contextPath + '\n'; + } + }; + + describe('#each', function() { + it('should track contextPath for arrays', function() { + var template = CompilerContext.compile('{{#each array}}{{wycats name}}{{/each}}', {trackIds: true}); + + equals(template({array: [{name: 'foo'}, {name: 'bar'}]}, {helpers: helpers}), 'foo:array.0\nbar:array.1\n'); + }); + it('should track contextPath for keys', function() { + var template = CompilerContext.compile('{{#each object}}{{wycats name}}{{/each}}', {trackIds: true}); + + equals(template({object: {foo: {name: 'foo'}, bar: {name: 'bar'}}}, {helpers: helpers}), 'foo:object.foo\nbar:object.bar\n'); + }); + it('should handle nesting', function() { + var template = CompilerContext.compile('{{#each .}}{{#each .}}{{wycats name}}{{/each}}{{/each}}', {trackIds: true}); + + equals(template({array: [{name: 'foo'}, {name: 'bar'}]}, {helpers: helpers}), 'foo:.array..0\nbar:.array..1\n'); + }); + }); + describe('#with', function() { + it('should track contextPath', function() { + var template = CompilerContext.compile('{{#with field}}{{wycats name}}{{/with}}', {trackIds: true}); + + equals(template({field: {name: 'foo'}}, {helpers: helpers}), 'foo:field\n'); + }); + it('should handle nesting', function() { + var template = CompilerContext.compile('{{#with bat}}{{#with field}}{{wycats name}}{{/with}}{{/with}}', {trackIds: true}); + + equals(template({bat: {field: {name: 'foo'}}}, {helpers: helpers}), 'foo:bat.field\n'); + }); + }); + describe('#blockHelperMissing', function() { + it('should track contextPath for arrays', function() { + var template = CompilerContext.compile('{{#field}}{{wycats name}}{{/field}}', {trackIds: true}); + + equals(template({field: [{name: 'foo'}]}, {helpers: helpers}), 'foo:field.0\n'); + }); + it('should track contextPath for keys', function() { + var template = CompilerContext.compile('{{#field}}{{wycats name}}{{/field}}', {trackIds: true}); + + equals(template({field: {name: 'foo'}}, {helpers: helpers}), 'foo:field\n'); + }); + it('should handle nesting', function() { + var template = CompilerContext.compile('{{#bat}}{{#field}}{{wycats name}}{{/field}}{{/bat}}', {trackIds: true}); + + equals(template({bat: {field: {name: 'foo'}}}, {helpers: helpers}), 'foo:bat.field\n'); + }); + }); + }); +}); diff --git a/src/handlebars.l b/src/handlebars.l index 996badb22..cafdd72c7 100644 --- a/src/handlebars.l +++ b/src/handlebars.l @@ -1,5 +1,5 @@ -%x mu emu com +%x mu emu com raw %{ @@ -49,11 +49,29 @@ ID [^\s!"#%-,\.\/;->@\[-\^`\{-~]+/{LOOKAHEAD} return 'CONTENT'; } +"{{{{/"[^\s!"#%-,\.\/;->@\[-\^`\{-~]+/[=}\s\/.]"}}}}" { + yytext = yytext.substr(5, yyleng-9); + this.popState(); + return 'END_RAW_BLOCK'; + } +[^\x00]*?/("{{{{/") { return 'CONTENT'; } + [\s\S]*?"--}}" strip(0,4); this.popState(); return 'COMMENT'; "(" return 'OPEN_SEXPR'; ")" return 'CLOSE_SEXPR'; +"{{{{" { return 'OPEN_RAW_BLOCK'; } +"}}}}" { + this.popState(); + this.begin('raw'); + return 'CLOSE_RAW_BLOCK'; + } +"{{{{"[^\x00]*"}}}}" { + yytext = yytext.substr(4, yyleng-8); + this.popState(); + return 'RAW_BLOCK'; + } "{{"{LEFT_STRIP}?">" return 'OPEN_PARTIAL'; "{{"{LEFT_STRIP}?"#" return 'OPEN_BLOCK'; "{{"{LEFT_STRIP}?"/" return 'OPEN_ENDBLOCK'; @@ -77,7 +95,7 @@ ID [^\s!"#%-,\.\/;->@\[-\^`\{-~]+/{LOOKAHEAD} "@" return 'DATA'; "true"/{LITERAL_LOOKAHEAD} return 'BOOLEAN'; "false"/{LITERAL_LOOKAHEAD} return 'BOOLEAN'; -\-?[0-9]+/{LITERAL_LOOKAHEAD} return 'INTEGER'; +\-?[0-9]+(?:\.[0-9]+)?/{LITERAL_LOOKAHEAD} return 'NUMBER'; {ID} return 'ID'; diff --git a/src/handlebars.yy b/src/handlebars.yy index 7bff5125a..51796ec6d 100644 --- a/src/handlebars.yy +++ b/src/handlebars.yy @@ -35,7 +35,8 @@ statements ; statement - : openInverse program closeBlock -> new yy.BlockNode($1, $2.inverse, $2, $3, @$) + : openRawBlock CONTENT END_RAW_BLOCK -> new yy.RawBlockNode($1, $2, $3, @$) + | openInverse program closeBlock -> new yy.BlockNode($1, $2.inverse, $2, $3, @$) | openBlock program closeBlock -> new yy.BlockNode($1, $2, $2.inverse, $3, @$) | mustache -> $1 | partial -> $1 @@ -43,6 +44,10 @@ statement | COMMENT -> new yy.CommentNode($1, @$) ; +openRawBlock + : OPEN_RAW_BLOCK sexpr CLOSE_RAW_BLOCK -> new yy.MustacheNode($2, null, '', '', @$) + ; + openBlock : OPEN_BLOCK sexpr CLOSE -> new yy.MustacheNode($2, null, $1, stripFlags($1, $3), @$) ; @@ -63,7 +68,8 @@ mustache ; partial - : OPEN_PARTIAL partialName path? CLOSE -> new yy.PartialNode($2, $3, stripFlags($1, $4), @$) + : OPEN_PARTIAL partialName param hash? CLOSE -> new yy.PartialNode($2, $3, $4, stripFlags($1, $5), @$) + | OPEN_PARTIAL partialName hash? CLOSE -> new yy.PartialNode($2, undefined, $3, stripFlags($1, $4), @$) ; simpleInverse @@ -78,7 +84,7 @@ sexpr param : path -> $1 | STRING -> new yy.StringNode($1, @$) - | INTEGER -> new yy.IntegerNode($1, @$) + | NUMBER -> new yy.NumberNode($1, @$) | BOOLEAN -> new yy.BooleanNode($1, @$) | dataName -> $1 | OPEN_SEXPR sexpr CLOSE_SEXPR {$2.isHelper = true; $$ = $2;} @@ -95,7 +101,7 @@ hashSegment partialName : path -> new yy.PartialNameNode($1, @$) | STRING -> new yy.PartialNameNode(new yy.StringNode($1, @$), @$) - | INTEGER -> new yy.PartialNameNode(new yy.IntegerNode($1, @$)) + | NUMBER -> new yy.PartialNameNode(new yy.NumberNode($1, @$)) ; dataName diff --git a/tasks/parser.js b/tasks/parser.js index f6a72c617..47533cedf 100644 --- a/tasks/parser.js +++ b/tasks/parser.js @@ -4,7 +4,13 @@ module.exports = function(grunt) { grunt.registerTask('parser', 'Generate jison parser.', function() { var done = this.async(); - var child = childProcess.spawn('./node_modules/.bin/jison', ['-m', 'js', 'src/handlebars.yy', 'src/handlebars.l'], {stdio: 'inherit'}); + var cmd = './node_modules/.bin/jison'; + + if(process.platform === 'win32'){ + cmd = 'node_modules\\.bin\\jison.cmd'; + } + + var child = childProcess.spawn(cmd, ['-m', 'js', 'src/handlebars.yy', 'src/handlebars.l'], {stdio: 'inherit'}); child.on('exit', function(code) { if (code != 0) { grunt.fatal('Jison failure: ' + code); diff --git a/tasks/test.js b/tasks/test.js index cd5f00e80..664af60d8 100644 --- a/tasks/test.js +++ b/tasks/test.js @@ -10,8 +10,9 @@ module.exports = function(grunt) { throw err; } - if (stdout.toString() !== fs.readFileSync('./spec/expected/empty.amd.js').toString()) { - throw new Error('Expected binary output differed'); + var expected = fs.readFileSync('./spec/expected/empty.amd.js'); + if (stdout.toString() !== expected.toString()) { + throw new Error('Expected binary output differed:\n\n"' + stdout + '"\n\n"' + expected + '"'); } done(); diff --git a/tasks/util/git.js b/tasks/util/git.js index dc57c91b6..a6c9ec1dc 100644 --- a/tasks/util/git.js +++ b/tasks/util/git.js @@ -80,20 +80,27 @@ module.exports = { childProcess.exec('git tag -a --message=' + name + ' ' + name, {}, function(err, stdout, stderr) { if (err) { throw new Error('git.tag: ' + err.message); - throw err; } callback(); }); }, tagName: function(callback) { - childProcess.exec('git tag -l --points-at HEAD', {}, function(err, stdout) { + childProcess.exec('git describe --tags', {}, function(err, stdout) { if (err) { throw new Error('git.tagName: ' + err.message); } - var tags = stdout.trim().split(/\n/), - versionTags = tags.filter(function(tag) { return /^v/.test(tag); }); + var tags = stdout.trim().split(/\n/); + tags = tags.filter(function(info) { + info = info.split('-'); + return info.length == 1; + }); + + var versionTags = tags.filter(function(info) { + return /^v/.test(info[0]); + }); + callback(undefined, versionTags[0] || tags[0]); }); }