diff --git a/README.markdown b/README.markdown index 56042f4d3..0b338e829 100644 --- a/README.markdown +++ b/README.markdown @@ -1,4 +1,4 @@ -[![Build Status](https://secure.travis-ci.org/wycats/handlebars.js.png)](http://travis-ci.org/wycats/handlebars.js) +[![Build Status](https://travis-ci.org/wycats/handlebars.js.png?branch=master)](https://travis-ci.org/wycats/handlebars.js) Handlebars.js ============= diff --git a/Rakefile b/Rakefile index 9ecf913d4..6fe5d849e 100644 --- a/Rakefile +++ b/Rakefile @@ -45,11 +45,11 @@ def remove_exports(string) match ? match[1] : string end -minimal_deps = %w(base compiler/parser compiler/base compiler/ast utils compiler/compiler runtime).map do |file| +minimal_deps = %w(browser-prefix base compiler/parser compiler/base compiler/ast utils compiler/compiler runtime browser-suffix).map do |file| "lib/handlebars/#{file}.js" end -runtime_deps = %w(base utils runtime).map do |file| +runtime_deps = %w(browser-prefix base utils runtime browser-suffix).map do |file| "lib/handlebars/#{file}.js" end @@ -81,8 +81,14 @@ file "dist/handlebars.runtime.js" => runtime_deps do |task| build_for_task(task) end -task :build => [:compile, "dist/handlebars.js"] -task :runtime => [:compile, "dist/handlebars.runtime.js"] +task :build => [:compile] do |task| + # Since the tests are dependent on this always rebuild. + Rake::Task["dist/handlebars.js"].execute +end +task :runtime => [:compile] do |task| + # Since the tests are dependent on this always rebuild. + Rake::Task["dist/handlebars.runtime.js"].execute +end desc "build the build and runtime version of handlebars" task :release => [:build, :runtime] diff --git a/dist/handlebars.js b/dist/handlebars.js index 26d920778..39afa6419 100644 --- a/dist/handlebars.js +++ b/dist/handlebars.js @@ -22,11 +22,12 @@ THE SOFTWARE. */ -// lib/handlebars/base.js - +// lib/handlebars/browser-prefix.js var Handlebars = {}; -(function(Handlebars) { +(function(Handlebars, undefined) { +; +// lib/handlebars/base.js Handlebars.VERSION = "1.0.0-rc.3"; Handlebars.COMPILER_REVISION = 2; @@ -151,11 +152,7 @@ Handlebars.registerHelper('if', function(context, options) { }); Handlebars.registerHelper('unless', function(context, options) { - var fn = options.fn, inverse = options.inverse; - options.fn = inverse; - options.inverse = fn; - - return Handlebars.helpers['if'].call(this, context, options); + return Handlebars.helpers['if'].call(this, context, {fn: options.inverse, inverse: options.fn}); }); Handlebars.registerHelper('with', function(context, options) { @@ -166,8 +163,6 @@ Handlebars.registerHelper('log', function(context, options) { var level = options.data && options.data.level != null ? parseInt(options.data.level, 10) : 1; Handlebars.log(level, context); }); - -}(Handlebars)); ; // lib/handlebars/compiler/parser.js /* Jison generated parser */ @@ -635,7 +630,7 @@ case 32: return 5; break; } }; -lexer.rules = [/^(?:[^\x00]*?(?=(\{\{)))/,/^(?:[^\x00]+)/,/^(?:[^\x00]{2,}?(?=(\{\{|$)))/,/^(?:[\s\S]*?--\}\})/,/^(?:\{\{>)/,/^(?:\{\{#)/,/^(?:\{\{\/)/,/^(?:\{\{\^)/,/^(?:\{\{\s*else\b)/,/^(?:\{\{\{)/,/^(?:\{\{&)/,/^(?:\{\{!--)/,/^(?:\{\{![\s\S]*?\}\})/,/^(?:\{\{)/,/^(?:=)/,/^(?:\.(?=[} ]))/,/^(?:\.\.)/,/^(?:[\/.])/,/^(?:\s+)/,/^(?:\}\}\})/,/^(?:\}\})/,/^(?:"(\\["]|[^"])*")/,/^(?:'(\\[']|[^'])*')/,/^(?:@[a-zA-Z]+)/,/^(?:true(?=[}\s]))/,/^(?:false(?=[}\s]))/,/^(?:[0-9]+(?=[}\s]))/,/^(?:[a-zA-Z0-9_$-]+(?=[=}\s\/.]))/,/^(?:\[[^\]]*\])/,/^(?:.)/,/^(?:\s+)/,/^(?:[a-zA-Z0-9_$-/]+)/,/^(?:$)/]; +lexer.rules = [/^(?:[^\x00]*?(?=(\{\{)))/,/^(?:[^\x00]+)/,/^(?:[^\x00]{2,}?(?=(\{\{|$)))/,/^(?:[\s\S]*?--\}\})/,/^(?:\{\{>)/,/^(?:\{\{#)/,/^(?:\{\{\/)/,/^(?:\{\{\^)/,/^(?:\{\{\s*else\b)/,/^(?:\{\{\{)/,/^(?:\{\{&)/,/^(?:\{\{!--)/,/^(?:\{\{![\s\S]*?\}\})/,/^(?:\{\{)/,/^(?:=)/,/^(?:\.(?=[} ]))/,/^(?:\.\.)/,/^(?:[\/.])/,/^(?:\s+)/,/^(?:\}\}\})/,/^(?:\}\})/,/^(?:"(\\["]|[^"])*")/,/^(?:'(\\[']|[^'])*')/,/^(?:@[a-zA-Z]+)/,/^(?:true(?=[}\s]))/,/^(?:false(?=[}\s]))/,/^(?:-?[0-9]+(?=[}\s]))/,/^(?:[a-zA-Z0-9_$-]+(?=[=}\s\/.]))/,/^(?:\[[^\]]*\])/,/^(?:.)/,/^(?:\s+)/,/^(?:[a-zA-Z0-9_$-/]+)/,/^(?:$)/]; lexer.conditions = {"mu":{"rules":[4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,32],"inclusive":false},"emu":{"rules":[2],"inclusive":false},"com":{"rules":[3],"inclusive":false},"par":{"rules":[30,31],"inclusive":false},"INITIAL":{"rules":[0,1,32],"inclusive":true}}; return lexer;})() parser.lexer = lexer; @@ -654,139 +649,133 @@ Handlebars.parse = function(input) { Handlebars.Parser.yy = Handlebars.AST; return Handlebars.Parser.parse(input); }; - -Handlebars.print = function(ast) { - return new Handlebars.PrintVisitor().accept(ast); -};; +; // lib/handlebars/compiler/ast.js -(function() { +Handlebars.AST = {}; - Handlebars.AST = {}; +Handlebars.AST.ProgramNode = function(statements, inverse) { + this.type = "program"; + this.statements = statements; + if(inverse) { this.inverse = new Handlebars.AST.ProgramNode(inverse); } +}; - Handlebars.AST.ProgramNode = function(statements, inverse) { - this.type = "program"; - this.statements = statements; - if(inverse) { this.inverse = new Handlebars.AST.ProgramNode(inverse); } - }; +Handlebars.AST.MustacheNode = function(rawParams, hash, unescaped) { + this.type = "mustache"; + this.escaped = !unescaped; + this.hash = hash; - Handlebars.AST.MustacheNode = function(rawParams, hash, unescaped) { - this.type = "mustache"; - this.escaped = !unescaped; - this.hash = hash; + var id = this.id = rawParams[0]; + var params = this.params = rawParams.slice(1); - 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 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); - // 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); + // 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. +}; - // 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. - }; +Handlebars.AST.PartialNode = function(partialName, context) { + this.type = "partial"; + this.partialName = partialName; + this.context = context; +}; - Handlebars.AST.PartialNode = function(partialName, context) { - this.type = "partial"; - this.partialName = partialName; - this.context = context; +Handlebars.AST.BlockNode = function(mustache, program, inverse, close) { + var verifyMatch = function(open, close) { + if(open.original !== close.original) { + throw new Handlebars.Exception(open.original + " doesn't match " + close.original); + } }; - Handlebars.AST.BlockNode = function(mustache, program, inverse, close) { - var verifyMatch = function(open, close) { - if(open.original !== close.original) { - throw new Handlebars.Exception(open.original + " doesn't match " + close.original); - } - }; + verifyMatch(mustache.id, close); + this.type = "block"; + this.mustache = mustache; + this.program = program; + this.inverse = inverse; - verifyMatch(mustache.id, close); - this.type = "block"; - this.mustache = mustache; - this.program = program; - this.inverse = inverse; - - if (this.inverse && !this.program) { - this.isInverse = true; - } - }; + if (this.inverse && !this.program) { + this.isInverse = true; + } +}; - Handlebars.AST.ContentNode = function(string) { - this.type = "content"; - this.string = string; - }; +Handlebars.AST.ContentNode = function(string) { + this.type = "content"; + this.string = string; +}; - Handlebars.AST.HashNode = function(pairs) { - this.type = "hash"; - this.pairs = pairs; - }; +Handlebars.AST.HashNode = function(pairs) { + this.type = "hash"; + this.pairs = pairs; +}; - Handlebars.AST.IdNode = function(parts) { - this.type = "ID"; - this.original = parts.join("."); +Handlebars.AST.IdNode = function(parts) { + this.type = "ID"; + this.original = parts.join("."); - var dig = [], depth = 0; + var dig = [], depth = 0; - for(var i=0,l=parts.length; i 0) { throw new Handlebars.Exception("Invalid path: " + this.original); } - else if (part === "..") { depth++; } - else { this.isScoped = true; } - } - else { dig.push(part); } + if (part === ".." || part === "." || part === "this") { + if (dig.length > 0) { throw new Handlebars.Exception("Invalid path: " + this.original); } + else if (part === "..") { depth++; } + else { this.isScoped = true; } } + else { dig.push(part); } + } - this.parts = dig; - this.string = dig.join('.'); - this.depth = depth; - - // an ID is simple if it only has one part, and that part is not - // `..` or `this`. - this.isSimple = parts.length === 1 && !this.isScoped && depth === 0; + this.parts = dig; + this.string = dig.join('.'); + this.depth = depth; - this.stringModeValue = this.string; - }; + // an ID is simple if it only has one part, and that part is not + // `..` or `this`. + this.isSimple = parts.length === 1 && !this.isScoped && depth === 0; - Handlebars.AST.PartialNameNode = function(name) { - this.type = "PARTIAL_NAME"; - this.name = name; - }; + this.stringModeValue = this.string; +}; - Handlebars.AST.DataNode = function(id) { - this.type = "DATA"; - this.id = id; - }; +Handlebars.AST.PartialNameNode = function(name) { + this.type = "PARTIAL_NAME"; + this.name = name; +}; - Handlebars.AST.StringNode = function(string) { - this.type = "STRING"; - this.string = string; - this.stringModeValue = string; - }; +Handlebars.AST.DataNode = function(id) { + this.type = "DATA"; + this.id = id; +}; - Handlebars.AST.IntegerNode = function(integer) { - this.type = "INTEGER"; - this.integer = integer; - this.stringModeValue = Number(integer); - }; +Handlebars.AST.StringNode = function(string) { + this.type = "STRING"; + this.string = string; + this.stringModeValue = string; +}; - Handlebars.AST.BooleanNode = function(bool) { - this.type = "BOOLEAN"; - this.bool = bool; - this.stringModeValue = bool === "true"; - }; +Handlebars.AST.IntegerNode = function(integer) { + this.type = "INTEGER"; + this.integer = integer; + this.stringModeValue = Number(integer); +}; - Handlebars.AST.CommentNode = function(comment) { - this.type = "comment"; - this.comment = comment; - }; +Handlebars.AST.BooleanNode = function(bool) { + this.type = "BOOLEAN"; + this.bool = bool; + this.stringModeValue = bool === "true"; +}; -})();; +Handlebars.AST.CommentNode = function(comment) { + this.type = "comment"; + this.comment = comment; +}; +; // lib/handlebars/utils.js var errorProps = ['description', 'fileName', 'lineNumber', 'message', 'name', 'number', 'stack']; @@ -809,1274 +798,1280 @@ Handlebars.SafeString.prototype.toString = function() { return this.string.toString(); }; -(function() { - var escape = { - "&": "&", - "<": "<", - ">": ">", - '"': """, - "'": "'", - "`": "`" - }; +var escape = { + "&": "&", + "<": "<", + ">": ">", + '"': """, + "'": "'", + "`": "`" +}; - var badChars = /[&<>"'`]/g; - var possible = /[&<>"'`]/; +var badChars = /[&<>"'`]/g; +var possible = /[&<>"'`]/; - var escapeChar = function(chr) { - return escape[chr] || "&"; - }; +var escapeChar = function(chr) { + return escape[chr] || "&"; +}; - Handlebars.Utils = { - escapeExpression: function(string) { - // don't escape SafeStrings, since they're already safe - if (string instanceof Handlebars.SafeString) { - return string.toString(); - } else if (string == null || string === false) { - return ""; - } +Handlebars.Utils = { + escapeExpression: function(string) { + // don't escape SafeStrings, since they're already safe + if (string instanceof Handlebars.SafeString) { + return string.toString(); + } else if (string == null || string === false) { + return ""; + } - if(!possible.test(string)) { return string; } - return string.replace(badChars, escapeChar); - }, + if(!possible.test(string)) { return string; } + return string.replace(badChars, escapeChar); + }, - isEmpty: function(value) { - if (!value && value !== 0) { - return true; - } else if(Object.prototype.toString.call(value) === "[object Array]" && value.length === 0) { - return true; - } else { - return false; - } + isEmpty: function(value) { + if (!value && value !== 0) { + return true; + } else if(toString.call(value) === "[object Array]" && value.length === 0) { + return true; + } else { + return false; } - }; -})(); + } +}; ; // lib/handlebars/compiler/compiler.js /*jshint eqnull:true*/ -Handlebars.Compiler = function() {}; -Handlebars.JavaScriptCompiler = function() {}; +var Compiler = Handlebars.Compiler = function() {}; +var JavaScriptCompiler = Handlebars.JavaScriptCompiler = function() {}; -(function(Compiler, JavaScriptCompiler) { - // the foundHelper register will disambiguate helper lookup from finding a - // function in a context. This is necessary for mustache compatibility, which - // requires that context functions in blocks are evaluated by blockHelperMissing, - // and then proceed as if the resulting value was provided to blockHelperMissing. +// the foundHelper register will disambiguate helper lookup from finding a +// function in a context. This is necessary for mustache compatibility, which +// requires that context functions in blocks are evaluated by blockHelperMissing, +// and then proceed as if the resulting value was provided to blockHelperMissing. - Compiler.prototype = { - compiler: Compiler, +Compiler.prototype = { + compiler: Compiler, - disassemble: function() { - var opcodes = this.opcodes, opcode, out = [], params, param; + disassemble: function() { + var opcodes = this.opcodes, opcode, out = [], params, param; - for (var i=0, l=opcodes.length; i 0) { - this.source[1] = this.source[1] + ", " + locals.join(", "); - } + preamble: function() { + var out = []; - // Generate minimizer alias mappings - if (!this.isChild) { - for (var alias in this.context.aliases) { - this.source[1] = this.source[1] + ', ' + alias + '=' + this.context.aliases[alias]; - } - } + if (!this.isChild) { + var namespace = this.namespace; + var copies = "helpers = helpers || " + namespace + ".helpers;"; + if (this.environment.usePartial) { copies = copies + " partials = partials || " + namespace + ".partials;"; } + if (this.options.data) { copies = copies + " data = data || {};"; } + out.push(copies); + } else { + out.push(''); + } - if (this.source[1]) { - this.source[1] = "var " + this.source[1].substring(2) + ";"; - } + if (!this.environment.isSimple) { + out.push(", buffer = " + this.initializeBuffer()); + } else { + out.push(""); + } - // Merge children - if (!this.isChild) { - this.source[1] += '\n' + this.context.programs.join('\n') + '\n'; - } + // track the last context pushed into place to allow skipping the + // getContext opcode when it would be a noop + this.lastContext = 0; + this.source = out; + }, - if (!this.environment.isSimple) { - this.source.push("return buffer;"); - } + createFunctionContext: function(asObject) { + var locals = this.stackVars.concat(this.registers.list); - var params = this.isChild ? ["depth0", "data"] : ["Handlebars", "depth0", "helpers", "partials", "data"]; + if(locals.length > 0) { + this.source[1] = this.source[1] + ", " + locals.join(", "); + } - for(var i=0, l=this.environment.depths.list.length; i this.stackVars.length) { this.stackVars.push("stack" + this.stackSlot); } - return this.topStackName(); - }, - topStackName: function() { - return "stack" + this.stackSlot; - }, - flushInline: function() { - var inlineStack = this.inlineStack; - if (inlineStack.length) { - this.inlineStack = []; - for (var i = 0, len = inlineStack.length; i < len; i++) { - var entry = inlineStack[i]; - if (entry instanceof Literal) { - this.compileStack.push(entry); - } else { - this.pushStack(entry); - } - } + this.push('(' + prefix + item + ')'); + } else { + // Prevent modification of the context depth variable. Through replaceStack + if (!/^stack/.test(stack)) { + stack = this.nextStack(); } - }, - isInline: function() { - return this.inlineStack.length; - }, - popStack: function(wrapped) { - var inline = this.isInline(), - item = (inline ? this.inlineStack : this.compileStack).pop(); + this.source.push(stack + " = (" + prefix + item + ");"); + } + return stack; + }, - if (!wrapped && (item instanceof Literal)) { - return item.value; - } else { - if (!inline) { - this.stackSlot--; + nextStack: function() { + return this.pushStack(); + }, + + incrStack: function() { + this.stackSlot++; + if(this.stackSlot > this.stackVars.length) { this.stackVars.push("stack" + this.stackSlot); } + return this.topStackName(); + }, + topStackName: function() { + return "stack" + this.stackSlot; + }, + flushInline: function() { + var inlineStack = this.inlineStack; + if (inlineStack.length) { + this.inlineStack = []; + for (var i = 0, len = inlineStack.length; i < len; i++) { + var entry = inlineStack[i]; + if (entry instanceof Literal) { + this.compileStack.push(entry); + } else { + this.pushStack(entry); } - return item; } - }, + } + }, + isInline: function() { + return this.inlineStack.length; + }, - topStack: function(wrapped) { - var stack = (this.isInline() ? this.inlineStack : this.compileStack), - item = stack[stack.length - 1]; + popStack: function(wrapped) { + var inline = this.isInline(), + item = (inline ? this.inlineStack : this.compileStack).pop(); - if (!wrapped && (item instanceof Literal)) { - return item.value; - } else { - return item; + if (!wrapped && (item instanceof Literal)) { + return item.value; + } else { + if (!inline) { + this.stackSlot--; } - }, + return item; + } + }, - quotedString: function(str) { - return '"' + str - .replace(/\\/g, '\\\\') - .replace(/"/g, '\\"') - .replace(/\n/g, '\\n') - .replace(/\r/g, '\\r') + '"'; - }, + topStack: function(wrapped) { + var stack = (this.isInline() ? this.inlineStack : this.compileStack), + item = stack[stack.length - 1]; - setupHelper: function(paramSize, name, missingParams) { - var params = []; - this.setupParams(paramSize, params, missingParams); - var foundHelper = this.nameLookup('helpers', name, 'helper'); + if (!wrapped && (item instanceof Literal)) { + return item.value; + } else { + return item; + } + }, - return { - params: params, - name: foundHelper, - callParams: ["depth0"].concat(params).join(", "), - helperMissingParams: missingParams && ["depth0", this.quotedString(name)].concat(params).join(", ") - }; - }, + quotedString: function(str) { + return '"' + str + .replace(/\\/g, '\\\\') + .replace(/"/g, '\\"') + .replace(/\n/g, '\\n') + .replace(/\r/g, '\\r') + '"'; + }, - // the params and contexts arguments are passed in arrays - // to fill in - setupParams: function(paramSize, params, useRegister) { - var options = [], contexts = [], types = [], param, inverse, program; + setupHelper: function(paramSize, name, missingParams) { + var params = []; + this.setupParams(paramSize, params, missingParams); + var foundHelper = this.nameLookup('helpers', name, 'helper'); - options.push("hash:" + this.popStack()); + return { + params: params, + name: foundHelper, + callParams: ["depth0"].concat(params).join(", "), + helperMissingParams: missingParams && ["depth0", this.quotedString(name)].concat(params).join(", ") + }; + }, - inverse = this.popStack(); - program = this.popStack(); + // the params and contexts arguments are passed in arrays + // to fill in + setupParams: function(paramSize, params, useRegister) { + var options = [], contexts = [], types = [], param, inverse, program; - // Avoid setting fn and inverse if neither are set. This allows - // helpers to do a check for `if (options.fn)` - if (program || inverse) { - if (!program) { - this.context.aliases.self = "this"; - program = "self.noop"; - } + options.push("hash:" + this.popStack()); - if (!inverse) { - this.context.aliases.self = "this"; - inverse = "self.noop"; - } + inverse = this.popStack(); + program = this.popStack(); - options.push("inverse:" + inverse); - options.push("fn:" + program); + // Avoid setting fn and inverse if neither are set. This allows + // helpers to do a check for `if (options.fn)` + if (program || inverse) { + if (!program) { + this.context.aliases.self = "this"; + program = "self.noop"; } - for(var i=0; i": ">", - '"': """, - "'": "'", - "`": "`" - }; - - var badChars = /[&<>"'`]/g; - var possible = /[&<>"'`]/; - - var escapeChar = function(chr) { - return escape[chr] || "&"; - }; - - Handlebars.Utils = { - escapeExpression: function(string) { - // don't escape SafeStrings, since they're already safe - if (string instanceof Handlebars.SafeString) { - return string.toString(); - } else if (string == null || string === false) { - return ""; - } +var escape = { + "&": "&", + "<": "<", + ">": ">", + '"': """, + "'": "'", + "`": "`" +}; - if(!possible.test(string)) { return string; } - return string.replace(badChars, escapeChar); - }, - - isEmpty: function(value) { - if (!value && value !== 0) { - return true; - } else if(Object.prototype.toString.call(value) === "[object Array]" && value.length === 0) { - return true; - } else { - return false; - } +var badChars = /[&<>"'`]/g; +var possible = /[&<>"'`]/; + +var escapeChar = function(chr) { + return escape[chr] || "&"; +}; + +Handlebars.Utils = { + escapeExpression: function(string) { + // don't escape SafeStrings, since they're already safe + if (string instanceof Handlebars.SafeString) { + return string.toString(); + } else if (string == null || string === false) { + return ""; + } + + if(!possible.test(string)) { return string; } + return string.replace(badChars, escapeChar); + }, + + isEmpty: function(value) { + if (!value && value !== 0) { + return true; + } else if(toString.call(value) === "[object Array]" && value.length === 0) { + return true; + } else { + return false; } - }; -})(); + } +}; ; // lib/handlebars/runtime.js @@ -318,3 +311,6 @@ Handlebars.VM = { Handlebars.template = Handlebars.VM.template; ; +// lib/handlebars/browser-suffix.js +})(Handlebars); +; diff --git a/handlebars-source.gemspec b/handlebars-source.gemspec new file mode 100644 index 000000000..514e2b1e4 --- /dev/null +++ b/handlebars-source.gemspec @@ -0,0 +1,21 @@ +# -*- encoding: utf-8 -*- +require 'json' + +package = JSON.parse(File.read('package.json')) + +Gem::Specification.new do |gem| + gem.name = "handlebars-source" + gem.authors = ["Yehuda Katz"] + gem.email = ["wycats@gmail.com"] + gem.date = Time.now.strftime("%Y-%m-%d") + gem.description = %q{Handlebars.js source code wrapper for (pre)compilation gems.} + gem.summary = %q{Handlebars.js source code wrapper} + gem.homepage = "https://github.com/wycats/handlebars.js/" + gem.version = package["version"] + + gem.files = [ + 'dist/handlebars.js', + 'dist/handlebars.runtime.js', + 'lib/handlebars/source.rb' + ] +end diff --git a/lib/handlebars/base.js b/lib/handlebars/base.js index 1a25b5cc8..dcbd95889 100644 --- a/lib/handlebars/base.js +++ b/lib/handlebars/base.js @@ -2,11 +2,9 @@ module.exports.create = function() { -// BEGIN(BROWSER) - var Handlebars = {}; -(function(Handlebars) { +// BEGIN(BROWSER) Handlebars.VERSION = "1.0.0-rc.3"; Handlebars.COMPILER_REVISION = 2; @@ -131,11 +129,7 @@ Handlebars.registerHelper('if', function(context, options) { }); Handlebars.registerHelper('unless', function(context, options) { - var fn = options.fn, inverse = options.inverse; - options.fn = inverse; - options.inverse = fn; - - return Handlebars.helpers['if'].call(this, context, options); + return Handlebars.helpers['if'].call(this, context, {fn: options.inverse, inverse: options.fn}); }); Handlebars.registerHelper('with', function(context, options) { @@ -147,8 +141,6 @@ Handlebars.registerHelper('log', function(context, options) { Handlebars.log(level, context); }); -}(Handlebars)); - // END(BROWSER) return Handlebars; diff --git a/lib/handlebars/browser-prefix.js b/lib/handlebars/browser-prefix.js new file mode 100644 index 000000000..138467d68 --- /dev/null +++ b/lib/handlebars/browser-prefix.js @@ -0,0 +1,3 @@ +var Handlebars = {}; + +(function(Handlebars, undefined) { diff --git a/lib/handlebars/browser-suffix.js b/lib/handlebars/browser-suffix.js new file mode 100644 index 000000000..36c52c091 --- /dev/null +++ b/lib/handlebars/browser-suffix.js @@ -0,0 +1 @@ +})(Handlebars); diff --git a/lib/handlebars/compiler/ast.js b/lib/handlebars/compiler/ast.js index 1ba1fcf73..850c60554 100644 --- a/lib/handlebars/compiler/ast.js +++ b/lib/handlebars/compiler/ast.js @@ -1,134 +1,131 @@ exports.attach = function(Handlebars) { // BEGIN(BROWSER) -(function() { +Handlebars.AST = {}; - Handlebars.AST = {}; - - Handlebars.AST.ProgramNode = function(statements, inverse) { - this.type = "program"; - this.statements = statements; - if(inverse) { this.inverse = new Handlebars.AST.ProgramNode(inverse); } - }; +Handlebars.AST.ProgramNode = function(statements, inverse) { + this.type = "program"; + this.statements = statements; + if(inverse) { this.inverse = new Handlebars.AST.ProgramNode(inverse); } +}; - Handlebars.AST.MustacheNode = function(rawParams, hash, unescaped) { - this.type = "mustache"; - this.escaped = !unescaped; - this.hash = hash; +Handlebars.AST.MustacheNode = function(rawParams, hash, unescaped) { + this.type = "mustache"; + this.escaped = !unescaped; + this.hash = hash; - var id = this.id = rawParams[0]; - var params = this.params = rawParams.slice(1); + 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 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); + // 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); - // 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. - }; + // 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. +}; - Handlebars.AST.PartialNode = function(partialName, context) { - this.type = "partial"; - this.partialName = partialName; - this.context = context; - }; +Handlebars.AST.PartialNode = function(partialName, context) { + this.type = "partial"; + this.partialName = partialName; + this.context = context; +}; - Handlebars.AST.BlockNode = function(mustache, program, inverse, close) { - var verifyMatch = function(open, close) { - if(open.original !== close.original) { - throw new Handlebars.Exception(open.original + " doesn't match " + close.original); - } - }; - - verifyMatch(mustache.id, close); - this.type = "block"; - this.mustache = mustache; - this.program = program; - this.inverse = inverse; - - if (this.inverse && !this.program) { - this.isInverse = true; +Handlebars.AST.BlockNode = function(mustache, program, inverse, close) { + var verifyMatch = function(open, close) { + if(open.original !== close.original) { + throw new Handlebars.Exception(open.original + " doesn't match " + close.original); } }; - Handlebars.AST.ContentNode = function(string) { - this.type = "content"; - this.string = string; - }; + verifyMatch(mustache.id, close); + this.type = "block"; + this.mustache = mustache; + this.program = program; + this.inverse = inverse; - Handlebars.AST.HashNode = function(pairs) { - this.type = "hash"; - this.pairs = pairs; - }; + if (this.inverse && !this.program) { + this.isInverse = true; + } +}; + +Handlebars.AST.ContentNode = function(string) { + this.type = "content"; + this.string = string; +}; + +Handlebars.AST.HashNode = function(pairs) { + this.type = "hash"; + this.pairs = pairs; +}; - Handlebars.AST.IdNode = function(parts) { - this.type = "ID"; - this.original = parts.join("."); +Handlebars.AST.IdNode = function(parts) { + this.type = "ID"; + this.original = parts.join("."); - var dig = [], depth = 0; + var dig = [], depth = 0; - for(var i=0,l=parts.length; i 0) { throw new Handlebars.Exception("Invalid path: " + this.original); } - else if (part === "..") { depth++; } - else { this.isScoped = true; } - } - else { dig.push(part); } + if (part === ".." || part === "." || part === "this") { + if (dig.length > 0) { throw new Handlebars.Exception("Invalid path: " + this.original); } + else if (part === "..") { depth++; } + else { this.isScoped = true; } } + else { dig.push(part); } + } - this.parts = dig; - this.string = dig.join('.'); - this.depth = depth; + this.parts = dig; + this.string = dig.join('.'); + this.depth = depth; - // an ID is simple if it only has one part, and that part is not - // `..` or `this`. - this.isSimple = parts.length === 1 && !this.isScoped && depth === 0; + // an ID is simple if it only has one part, and that part is not + // `..` or `this`. + this.isSimple = parts.length === 1 && !this.isScoped && depth === 0; - this.stringModeValue = this.string; - }; + this.stringModeValue = this.string; +}; - Handlebars.AST.PartialNameNode = function(name) { - this.type = "PARTIAL_NAME"; - this.name = name; - }; +Handlebars.AST.PartialNameNode = function(name) { + this.type = "PARTIAL_NAME"; + this.name = name; +}; - Handlebars.AST.DataNode = function(id) { - this.type = "DATA"; - this.id = id; - }; +Handlebars.AST.DataNode = function(id) { + this.type = "DATA"; + this.id = id; +}; - Handlebars.AST.StringNode = function(string) { - this.type = "STRING"; - this.string = string; - this.stringModeValue = string; - }; +Handlebars.AST.StringNode = function(string) { + this.type = "STRING"; + this.string = string; + this.stringModeValue = string; +}; - Handlebars.AST.IntegerNode = function(integer) { - this.type = "INTEGER"; - this.integer = integer; - this.stringModeValue = Number(integer); - }; +Handlebars.AST.IntegerNode = function(integer) { + this.type = "INTEGER"; + this.integer = integer; + this.stringModeValue = Number(integer); +}; - Handlebars.AST.BooleanNode = function(bool) { - this.type = "BOOLEAN"; - this.bool = bool; - this.stringModeValue = bool === "true"; - }; +Handlebars.AST.BooleanNode = function(bool) { + this.type = "BOOLEAN"; + this.bool = bool; + this.stringModeValue = bool === "true"; +}; - Handlebars.AST.CommentNode = function(comment) { - this.type = "comment"; - this.comment = comment; - }; +Handlebars.AST.CommentNode = function(comment) { + this.type = "comment"; + this.comment = comment; +}; -})(); // END(BROWSER) return Handlebars; diff --git a/lib/handlebars/compiler/base.js b/lib/handlebars/compiler/base.js index 8ff1101d5..759445154 100644 --- a/lib/handlebars/compiler/base.js +++ b/lib/handlebars/compiler/base.js @@ -15,9 +15,6 @@ Handlebars.parse = function(input) { return Handlebars.Parser.parse(input); }; -Handlebars.print = function(ast) { - return new Handlebars.PrintVisitor().accept(ast); -}; // END(BROWSER) return Handlebars; diff --git a/lib/handlebars/compiler/compiler.js b/lib/handlebars/compiler/compiler.js index 84008043c..196ba18bb 100644 --- a/lib/handlebars/compiler/compiler.js +++ b/lib/handlebars/compiler/compiler.js @@ -7,1229 +7,1237 @@ compilerbase.attach(Handlebars); // BEGIN(BROWSER) /*jshint eqnull:true*/ -Handlebars.Compiler = function() {}; -Handlebars.JavaScriptCompiler = function() {}; +var Compiler = Handlebars.Compiler = function() {}; +var JavaScriptCompiler = Handlebars.JavaScriptCompiler = function() {}; -(function(Compiler, JavaScriptCompiler) { - // the foundHelper register will disambiguate helper lookup from finding a - // function in a context. This is necessary for mustache compatibility, which - // requires that context functions in blocks are evaluated by blockHelperMissing, - // and then proceed as if the resulting value was provided to blockHelperMissing. +// the foundHelper register will disambiguate helper lookup from finding a +// function in a context. This is necessary for mustache compatibility, which +// requires that context functions in blocks are evaluated by blockHelperMissing, +// and then proceed as if the resulting value was provided to blockHelperMissing. - Compiler.prototype = { - compiler: Compiler, +Compiler.prototype = { + compiler: Compiler, - disassemble: function() { - var opcodes = this.opcodes, opcode, out = [], params, param; + disassemble: function() { + var opcodes = this.opcodes, opcode, out = [], params, param; - for (var i=0, l=opcodes.length; i 0) { - this.source[1] = this.source[1] + ", " + locals.join(", "); - } + createFunctionContext: function(asObject) { + var locals = this.stackVars.concat(this.registers.list); - // Generate minimizer alias mappings - if (!this.isChild) { - for (var alias in this.context.aliases) { - this.source[1] = this.source[1] + ', ' + alias + '=' + this.context.aliases[alias]; - } - } + if(locals.length > 0) { + this.source[1] = this.source[1] + ", " + locals.join(", "); + } - if (this.source[1]) { - this.source[1] = "var " + this.source[1].substring(2) + ";"; + // Generate minimizer alias mappings + if (!this.isChild) { + for (var alias in this.context.aliases) { + this.source[1] = this.source[1] + ', ' + alias + '=' + this.context.aliases[alias]; } + } - // Merge children - if (!this.isChild) { - this.source[1] += '\n' + this.context.programs.join('\n') + '\n'; - } + if (this.source[1]) { + this.source[1] = "var " + this.source[1].substring(2) + ";"; + } - if (!this.environment.isSimple) { - this.source.push("return buffer;"); - } + // Merge children + if (!this.isChild) { + this.source[1] += '\n' + this.context.programs.join('\n') + '\n'; + } - var params = this.isChild ? ["depth0", "data"] : ["Handlebars", "depth0", "helpers", "partials", "data"]; + if (!this.environment.isSimple) { + this.source.push("return buffer;"); + } - for(var i=0, l=this.environment.depths.list.length; i this.stackVars.length) { this.stackVars.push("stack" + this.stackSlot); } - return this.topStackName(); - }, - topStackName: function() { - return "stack" + this.stackSlot; - }, - flushInline: function() { - var inlineStack = this.inlineStack; - if (inlineStack.length) { - this.inlineStack = []; - for (var i = 0, len = inlineStack.length; i < len; i++) { - var entry = inlineStack[i]; - if (entry instanceof Literal) { - this.compileStack.push(entry); - } else { - this.pushStack(entry); - } - } + this.push('(' + prefix + item + ')'); + } else { + // Prevent modification of the context depth variable. Through replaceStack + if (!/^stack/.test(stack)) { + stack = this.nextStack(); } - }, - isInline: function() { - return this.inlineStack.length; - }, - - popStack: function(wrapped) { - var inline = this.isInline(), - item = (inline ? this.inlineStack : this.compileStack).pop(); - if (!wrapped && (item instanceof Literal)) { - return item.value; - } else { - if (!inline) { - this.stackSlot--; + this.source.push(stack + " = (" + prefix + item + ");"); + } + return stack; + }, + + nextStack: function() { + return this.pushStack(); + }, + + incrStack: function() { + this.stackSlot++; + if(this.stackSlot > this.stackVars.length) { this.stackVars.push("stack" + this.stackSlot); } + return this.topStackName(); + }, + topStackName: function() { + return "stack" + this.stackSlot; + }, + flushInline: function() { + var inlineStack = this.inlineStack; + if (inlineStack.length) { + this.inlineStack = []; + for (var i = 0, len = inlineStack.length; i < len; i++) { + var entry = inlineStack[i]; + if (entry instanceof Literal) { + this.compileStack.push(entry); + } else { + this.pushStack(entry); } - return item; } - }, - - topStack: function(wrapped) { - var stack = (this.isInline() ? this.inlineStack : this.compileStack), - item = stack[stack.length - 1]; - - if (!wrapped && (item instanceof Literal)) { - return item.value; - } else { - return item; + } + }, + isInline: function() { + return this.inlineStack.length; + }, + + popStack: function(wrapped) { + var inline = this.isInline(), + item = (inline ? this.inlineStack : this.compileStack).pop(); + + if (!wrapped && (item instanceof Literal)) { + return item.value; + } else { + if (!inline) { + this.stackSlot--; } - }, - - quotedString: function(str) { - return '"' + str - .replace(/\\/g, '\\\\') - .replace(/"/g, '\\"') - .replace(/\n/g, '\\n') - .replace(/\r/g, '\\r') + '"'; - }, - - setupHelper: function(paramSize, name, missingParams) { - var params = []; - this.setupParams(paramSize, params, missingParams); - var foundHelper = this.nameLookup('helpers', name, 'helper'); - - return { - params: params, - name: foundHelper, - callParams: ["depth0"].concat(params).join(", "), - helperMissingParams: missingParams && ["depth0", this.quotedString(name)].concat(params).join(", ") - }; - }, - - // the params and contexts arguments are passed in arrays - // to fill in - setupParams: function(paramSize, params, useRegister) { - var options = [], contexts = [], types = [], param, inverse, program; - - options.push("hash:" + this.popStack()); - - inverse = this.popStack(); - program = this.popStack(); - - // Avoid setting fn and inverse if neither are set. This allows - // helpers to do a check for `if (options.fn)` - if (program || inverse) { - if (!program) { - this.context.aliases.self = "this"; - program = "self.noop"; - } + return item; + } + }, - if (!inverse) { - this.context.aliases.self = "this"; - inverse = "self.noop"; - } + topStack: function(wrapped) { + var stack = (this.isInline() ? this.inlineStack : this.compileStack), + item = stack[stack.length - 1]; - options.push("inverse:" + inverse); - options.push("fn:" + program); + if (!wrapped && (item instanceof Literal)) { + return item.value; + } else { + return item; + } + }, + + quotedString: function(str) { + return '"' + str + .replace(/\\/g, '\\\\') + .replace(/"/g, '\\"') + .replace(/\n/g, '\\n') + .replace(/\r/g, '\\r') + '"'; + }, + + setupHelper: function(paramSize, name, missingParams) { + var params = []; + this.setupParams(paramSize, params, missingParams); + var foundHelper = this.nameLookup('helpers', name, 'helper'); + + return { + params: params, + name: foundHelper, + callParams: ["depth0"].concat(params).join(", "), + helperMissingParams: missingParams && ["depth0", this.quotedString(name)].concat(params).join(", ") + }; + }, + + // the params and contexts arguments are passed in arrays + // to fill in + setupParams: function(paramSize, params, useRegister) { + var options = [], contexts = [], types = [], param, inverse, program; + + options.push("hash:" + this.popStack()); + + inverse = this.popStack(); + program = this.popStack(); + + // Avoid setting fn and inverse if neither are set. This allows + // helpers to do a check for `if (options.fn)` + if (program || inverse) { + if (!program) { + this.context.aliases.self = "this"; + program = "self.noop"; } - for(var i=0; i": ">", - '"': """, - "'": "'", - "`": "`" - }; +var escape = { + "&": "&", + "<": "<", + ">": ">", + '"': """, + "'": "'", + "`": "`" +}; - var badChars = /[&<>"'`]/g; - var possible = /[&<>"'`]/; +var badChars = /[&<>"'`]/g; +var possible = /[&<>"'`]/; - var escapeChar = function(chr) { - return escape[chr] || "&"; - }; +var escapeChar = function(chr) { + return escape[chr] || "&"; +}; - Handlebars.Utils = { - escapeExpression: function(string) { - // don't escape SafeStrings, since they're already safe - if (string instanceof Handlebars.SafeString) { - return string.toString(); - } else if (string == null || string === false) { - return ""; - } +Handlebars.Utils = { + escapeExpression: function(string) { + // don't escape SafeStrings, since they're already safe + if (string instanceof Handlebars.SafeString) { + return string.toString(); + } else if (string == null || string === false) { + return ""; + } - if(!possible.test(string)) { return string; } - return string.replace(badChars, escapeChar); - }, + if(!possible.test(string)) { return string; } + return string.replace(badChars, escapeChar); + }, - isEmpty: function(value) { - if (!value && value !== 0) { - return true; - } else if(Object.prototype.toString.call(value) === "[object Array]" && value.length === 0) { - return true; - } else { - return false; - } + isEmpty: function(value) { + if (!value && value !== 0) { + return true; + } else if(toString.call(value) === "[object Array]" && value.length === 0) { + return true; + } else { + return false; } - }; -})(); + } +}; // END(BROWSER) diff --git a/package.json b/package.json index 7d9071643..7dda35743 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "handlebars", "description": "Extension of the Mustache logicless template language", - "version": "1.0.9", + "version": "1.0.10", "homepage": "http://www.handlebarsjs.com/", "keywords": [ "handlebars mustache template html" diff --git a/spec/qunit_spec.js b/spec/qunit_spec.js index fa15c3c05..f12e44cd4 100644 --- a/spec/qunit_spec.js +++ b/spec/qunit_spec.js @@ -571,6 +571,16 @@ test("simple literals work", function() { }}; shouldCompileTo(string, [hash, helpers], "Message: Hello world 12 times: true false", "template with a simple String literal"); }); +test("negative number literals work", function() { + var string = 'Message: {{hello -12}}'; + var hash = {}; + var helpers = {hello: function(times) { + if(typeof times !== 'number') { times = "NaN"; } + return "Hello " + times + " times"; + }}; + shouldCompileTo(string, [hash, helpers], "Message: Hello -12 times", "template with a negative integer literal"); +}); + test("using a quote in the middle of a parameter raises an error", function() { shouldThrow(function() { @@ -619,6 +629,12 @@ test("constructing a safestring from a string and checking its type", function() equal(safe, "testing 1, 2, 3", "SafeString is equivalent to its underlying string"); }); +test("it should not escape SafeString properties", function() { + var name = new Handlebars.SafeString("Sean O'Malley"); + + shouldCompileTo('{{name}}', [{ name: name }], "Sean O'Malley"); +}); + suite("helperMissing"); test("if a context is not found, helperMissing is used", function() { @@ -1354,7 +1370,7 @@ test("bug reported by @fat where lambdas weren't being properly resolved", funct test("Passing falsy values to Handlebars.compile throws an error", function() { shouldThrow(function() { CompilerContext.compile(null); - }, "You must pass a string or Handlebars AST to Handlebars.compile. You passed null"); + }, "You must pass a string or Handlebars AST to Handlebars.precompile. You passed null"); }); test('GH-408: Multiple loops fail', function() { @@ -1368,3 +1384,16 @@ test('GH-408: Multiple loops fail', function() { var result = template(context); equals(result, "John DoeJane DoeJohn DoeJane DoeJohn DoeJane Doe", 'It should output multiple times'); }); + +test('GS-428: Nested if else rendering', function() { + var succeedingTemplate = '{{#inverse}} {{#blk}} Unexpected {{/blk}} {{else}} {{#blk}} Expected {{/blk}} {{/inverse}}'; + var failingTemplate = '{{#inverse}} {{#blk}} Unexpected {{/blk}} {{else}} {{#blk}} Expected {{/blk}} {{/inverse}}'; + + var helpers = { + blk: function(block) { return block.fn(''); }, + inverse: function(block) { return block.inverse(''); } + }; + + shouldCompileTo(succeedingTemplate, [{}, helpers], ' Expected '); + shouldCompileTo(failingTemplate, [{}, helpers], ' Expected '); +}); diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index cf738012e..e79415ad2 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -76,9 +76,7 @@ def self.js_load(context, file) CONTEXT.instance_eval do |context| Handlebars::Spec.load_helpers(context); - Handlebars::Spec.js_load(context, 'lib/handlebars/base.js'); - Handlebars::Spec.js_load(context, 'lib/handlebars/utils.js'); - Handlebars::Spec.js_load(context, 'lib/handlebars/runtime.js'); + Handlebars::Spec.js_load(context, 'dist/handlebars.js'); context["CompilerContext"] = {} CompilerContext = context["CompilerContext"] @@ -93,18 +91,20 @@ def self.js_load(context, file) end end + PARSER_CONTEXT = V8::Context.new + PARSER_CONTEXT.instance_eval do |context| + Handlebars::Spec.load_helpers(context); + + Handlebars::Spec.js_load(context, 'lib/handlebars/compiler/parser.js'); + end + COMPILE_CONTEXT = V8::Context.new COMPILE_CONTEXT.instance_eval do |context| Handlebars::Spec.load_helpers(context); - Handlebars::Spec.js_load(context, 'lib/handlebars/base.js'); - Handlebars::Spec.js_load(context, 'lib/handlebars/utils.js'); - Handlebars::Spec.js_load(context, 'lib/handlebars/compiler/parser.js'); - Handlebars::Spec.js_load(context, 'lib/handlebars/compiler/base.js'); - Handlebars::Spec.js_load(context, 'lib/handlebars/compiler/ast.js'); + Handlebars::Spec.js_load(context, 'dist/handlebars.js'); Handlebars::Spec.js_load(context, 'lib/handlebars/compiler/visitor.js'); Handlebars::Spec.js_load(context, 'lib/handlebars/compiler/printer.js'); - Handlebars::Spec.js_load(context, 'lib/handlebars/compiler/compiler.js'); context["Handlebars"]["logger"]["level"] = ENV["DEBUG_JS"] ? context["Handlebars"]["logger"][ENV["DEBUG_JS"]] : 4 @@ -121,15 +121,7 @@ def self.js_load(context, file) FULL_CONTEXT.instance_eval do |context| Handlebars::Spec.load_helpers(context); - Handlebars::Spec.js_load(context, 'lib/handlebars/base.js'); - Handlebars::Spec.js_load(context, 'lib/handlebars/utils.js'); - Handlebars::Spec.js_load(context, 'lib/handlebars/compiler/parser.js'); - Handlebars::Spec.js_load(context, 'lib/handlebars/compiler/base.js'); - Handlebars::Spec.js_load(context, 'lib/handlebars/compiler/ast.js'); - Handlebars::Spec.js_load(context, 'lib/handlebars/compiler/visitor.js'); - Handlebars::Spec.js_load(context, 'lib/handlebars/compiler/printer.js'); - Handlebars::Spec.js_load(context, 'lib/handlebars/compiler/compiler.js'); - Handlebars::Spec.js_load(context, 'lib/handlebars/runtime.js'); + Handlebars::Spec.js_load(context, 'dist/handlebars.js'); context["Handlebars"]["logger"]["level"] = ENV["DEBUG_JS"] ? context["Handlebars"]["logger"][ENV["DEBUG_JS"]] : 4 diff --git a/spec/tokenizer_spec.rb b/spec/tokenizer_spec.rb index cb7f6eb09..0240e6310 100644 --- a/spec/tokenizer_spec.rb +++ b/spec/tokenizer_spec.rb @@ -2,12 +2,9 @@ require "timeout" describe "Tokenizer" do - let(:parser) { @context["handlebars"] } - let(:lexer) { @context["handlebars"]["lexer"] } + let(:parser) { Handlebars::Spec::PARSER_CONTEXT["handlebars"] } + let(:lexer) { Handlebars::Spec::PARSER_CONTEXT["handlebars"]["lexer"] } - before(:all) do - @compiles = true - end Token = Struct.new(:name, :text) def tokenize(string) @@ -235,6 +232,10 @@ def tokenize(string) result = tokenize(%|{{ foo 1 }}|) result.should match_tokens(%w(OPEN ID INTEGER CLOSE)) result[2].should be_token("INTEGER", "1") + + result = tokenize(%|{{ foo -1 }}|) + result.should match_tokens(%w(OPEN ID INTEGER CLOSE)) + result[2].should be_token("INTEGER", "-1") end it "tokenizes booleans" do diff --git a/src/handlebars.l b/src/handlebars.l index 04c7c4c2b..b32e39c64 100644 --- a/src/handlebars.l +++ b/src/handlebars.l @@ -42,7 +42,7 @@ "@"[a-zA-Z]+ { yytext = yytext.substr(1); return 'DATA'; } "true"/[}\s] { return 'BOOLEAN'; } "false"/[}\s] { return 'BOOLEAN'; } -[0-9]+/[}\s] { return 'INTEGER'; } +\-?[0-9]+/[}\s] { return 'INTEGER'; } [a-zA-Z0-9_$-]+/[=}\s\/.] { return 'ID'; } '['[^\]]*']' { yytext = yytext.substr(1, yyleng-2); return 'ID'; } . { return 'INVALID'; }