From eb01be04702388fd3cf98e509d3d14f1f9243f09 Mon Sep 17 00:00:00 2001 From: Michael Jackson Date: Fri, 16 Dec 2011 22:42:48 -0800 Subject: [PATCH] Parser rewrite This commit is a complete rewrite of the core mustache.js file with two main goals: 1) a major performance boost and 2) better compliance with the mustache spec. In order to improve performance templates are pre-compiled to JavaScript functions. These compiled functions take a view, a partials object, and an optional callback as arguments. They are cached to prevent unnecessary re-compilation of an already compiled template. Both of these enhancements facilitate a generous boost in performance. A few other notes: - The mustache.js file is now both browser and CommonJS ready without any modification. - The API exposes two main methods: Mustache.compile and Mustache.render. The former is used to generate a function for a given template, while the latter is a higher-level function that is used to compile and render a template in one shot. Mustache.to_html is still available for backwards compatibility. - The concept of pragmas is removed to conform more closely to the original mustache spec. The dot symbol still works to reference the current item in an array. - The parser is much more strict about whitespace than it was before. The rule is simple: if a line contains only a non-variable tag (i.e. not {{tag}} or {{{tag}}}) and whitespace, that line is ignored in the output. Users may use the "space" option when compiling templates to preserve every whitespace character in the original template. - The parser is able to provide detailed information about where errors occur when parsing and rendering templates, including the line number and surrounding code context. --- .gitignore | 1 - README.md | 10 +- Rakefile | 9 +- TESTING.md | 8 +- mustache.js | 816 ++++++++++-------- package.json | 8 + .../array_of_partials_implicit_partial.js | 3 - spec/_files/array_of_strings.mustache | 2 +- spec/_files/array_of_strings_options.js | 1 - spec/_files/array_of_strings_options.mustache | 2 - spec/_files/array_of_strings_options.txt | 1 - spec/_files/array_partial.js | 5 - spec/_files/complex.mustache | 2 +- spec/_files/complex.txt | 6 +- spec/_files/delimiters.mustache | 2 +- spec/_files/empty_partial.2.mustache | 1 - spec/_files/empty_partial.js | 3 - spec/_files/empty_template.txt | 2 +- spec/_files/error_not_found.txt | 1 - spec/_files/inverted_section.txt | 1 + spec/_files/nesting.txt | 7 +- spec/_files/null_string.js | 1 + spec/_files/null_string.mustache | 3 +- spec/_files/null_string.txt | 1 + spec/_files/partial_array.js | 3 + ...artial.mustache => partial_array.mustache} | 0 ...rtial.2.mustache => partial_array.partial} | 2 +- .../{array_partial.txt => partial_array.txt} | 1 - ...artial.js => partial_array_of_partials.js} | 2 +- ...che => partial_array_of_partials.mustache} | 0 ...ache => partial_array_of_partials.partial} | 0 ...tial.txt => partial_array_of_partials.txt} | 0 .../partial_array_of_partials_implicit.js | 3 + ...rtial_array_of_partials_implicit.mustache} | 0 ...artial_array_of_partials_implicit.partial} | 0 ...=> partial_array_of_partials_implicit.txt} | 0 spec/_files/partial_empty.js | 3 + ...artial.mustache => partial_empty.mustache} | 0 spec/_files/partial_empty.partial | 1 + .../{empty_partial.txt => partial_empty.txt} | 0 spec/_files/partial_recursion.js | 2 +- spec/_files/partial_recursion.mustache | 2 +- ...n.2.mustache => partial_recursion.partial} | 2 +- spec/_files/partial_template.js | 6 + ...ial.mustache => partial_template.mustache} | 2 +- spec/_files/partial_template.partial | 1 + ...plate_partial.txt => partial_template.txt} | 0 spec/_files/partial_view.js | 16 + ...partial.mustache => partial_view.mustache} | 2 +- ...artial.2.mustache => partial_view.partial} | 0 .../{view_partial.txt => partial_view.txt} | 1 - spec/_files/partial_whitespace.js | 17 + ...l.mustache => partial_whitespace.mustache} | 2 +- ....2.mustache => partial_whitespace.partial} | 0 ...ace_partial.txt => partial_whitespace.txt} | 1 - spec/_files/reuse_of_enumerables.js | 2 +- spec/_files/section_as_context.txt | 6 +- spec/_files/simple.mustache | 2 +- spec/_files/template_partial.2.mustache | 1 - spec/_files/template_partial.js | 8 - spec/_files/two_in_a_row.mustache | 2 +- spec/_files/two_sections.txt | 1 - spec/_files/unescaped.mustache | 2 +- spec/_files/unknown_pragma.js | 1 - spec/_files/unknown_pragma.mustache | 1 - spec/_files/unknown_pragma.txt | 1 - spec/_files/view_partial.js | 19 - spec/_files/whitespace_partial.js | 19 - spec/mustache_spec.rb | 138 +-- wrappers/commonjs/mustache.js.tpl.post | 8 - wrappers/commonjs/mustache.js.tpl.pre | 6 - wrappers/commonjs/package.json | 8 - 72 files changed, 596 insertions(+), 594 deletions(-) create mode 100644 package.json delete mode 100644 spec/_files/array_of_partials_implicit_partial.js delete mode 100644 spec/_files/array_of_strings_options.js delete mode 100644 spec/_files/array_of_strings_options.mustache delete mode 100644 spec/_files/array_of_strings_options.txt delete mode 100644 spec/_files/array_partial.js delete mode 100644 spec/_files/empty_partial.2.mustache delete mode 100644 spec/_files/empty_partial.js create mode 100644 spec/_files/partial_array.js rename spec/_files/{array_partial.mustache => partial_array.mustache} (100%) rename spec/_files/{array_partial.2.mustache => partial_array.partial} (83%) rename spec/_files/{array_partial.txt => partial_array.txt} (98%) rename spec/_files/{array_of_partials_partial.js => partial_array_of_partials.js} (61%) rename spec/_files/{array_of_partials_implicit_partial.mustache => partial_array_of_partials.mustache} (100%) rename spec/_files/{array_of_partials_partial.2.mustache => partial_array_of_partials.partial} (100%) rename spec/_files/{array_of_partials_implicit_partial.txt => partial_array_of_partials.txt} (100%) create mode 100644 spec/_files/partial_array_of_partials_implicit.js rename spec/_files/{array_of_partials_partial.mustache => partial_array_of_partials_implicit.mustache} (100%) rename spec/_files/{array_of_partials_implicit_partial.2.mustache => partial_array_of_partials_implicit.partial} (100%) rename spec/_files/{array_of_partials_partial.txt => partial_array_of_partials_implicit.txt} (100%) create mode 100644 spec/_files/partial_empty.js rename spec/_files/{empty_partial.mustache => partial_empty.mustache} (100%) create mode 100644 spec/_files/partial_empty.partial rename spec/_files/{empty_partial.txt => partial_empty.txt} (100%) rename spec/_files/{partial_recursion.2.mustache => partial_recursion.partial} (72%) create mode 100644 spec/_files/partial_template.js rename spec/_files/{template_partial.mustache => partial_template.mustache} (59%) create mode 100644 spec/_files/partial_template.partial rename spec/_files/{template_partial.txt => partial_template.txt} (100%) create mode 100644 spec/_files/partial_view.js rename spec/_files/{view_partial.mustache => partial_view.mustache} (61%) rename spec/_files/{view_partial.2.mustache => partial_view.partial} (100%) rename spec/_files/{view_partial.txt => partial_view.txt} (99%) create mode 100644 spec/_files/partial_whitespace.js rename spec/_files/{whitespace_partial.mustache => partial_whitespace.mustache} (63%) rename spec/_files/{whitespace_partial.2.mustache => partial_whitespace.partial} (100%) rename spec/_files/{whitespace_partial.txt => partial_whitespace.txt} (99%) delete mode 100644 spec/_files/template_partial.2.mustache delete mode 100644 spec/_files/template_partial.js delete mode 100644 spec/_files/unknown_pragma.js delete mode 100644 spec/_files/unknown_pragma.mustache delete mode 100644 spec/_files/unknown_pragma.txt delete mode 100644 spec/_files/view_partial.js delete mode 100644 spec/_files/whitespace_partial.js delete mode 100644 wrappers/commonjs/mustache.js.tpl.post delete mode 100644 wrappers/commonjs/mustache.js.tpl.pre delete mode 100644 wrappers/commonjs/package.json diff --git a/.gitignore b/.gitignore index c44d96c..bd08399 100644 --- a/.gitignore +++ b/.gitignore @@ -1,7 +1,6 @@ .DS_Store .rvmrc runner.js -lib jquery.mustache.js qooxdoo.mustache.js dojox diff --git a/README.md b/README.md index 578df7c..7d941df 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,7 @@ > What could be more logical awesome than no logic at all? [mustache.js](http://github.com/janl/mustache.js) is an implementation of the -[Mustache](http://mustache.github.com/) templating system in JavaScript. +[Mustache](http://mustache.github.com/) template system in JavaScript. [Mustache](http://mustache.github.com/) is a logic-less template syntax. It can be used for HTML, config files, source code - anything. It works by expanding @@ -274,10 +274,11 @@ own iteration marker: ## Plugins for JavaScript Libraries -mustache.js may be built specifically for several different client libraries -and platforms, including the following: +By default mustache.js may be used in a browser or any [CommonJS](http://www.commonjs.org/) +environment, including [node](http://nodejs.org/). Additionally, mustache.js may +be built specifically for several different client libraries and platforms, +including the following: - - [node](http://nodejs.org/) (or other CommonJS platforms) - [jQuery](http://jquery.com/) - [Dojo](http://www.dojotoolkit.org/) - [YUI](http://developer.yahoo.com/yui/) @@ -287,7 +288,6 @@ and platforms, including the following: These may be built using [Rake](http://rake.rubyforge.org/) and one of the following commands: - $ rake commonjs $ rake jquery $ rake dojo $ rake yui diff --git a/Rakefile b/Rakefile index 16bc724..b2a83b9 100644 --- a/Rakefile +++ b/Rakefile @@ -13,7 +13,7 @@ task :spec do end def version - File.read("mustache.js").match('version: "([^\"]+)",$')[1] + File.read("mustache.js").match('version = "([^\"]+)";$')[1] end # Creates a rule that uses the .tmpl.{pre,post} stuff to make a final, @@ -36,17 +36,10 @@ def templated_build(name, opts={}) sh "cat #{source}/#{target_js}.tpl.pre mustache.js \ #{source}/#{target_js}.tpl.post > #{opts[:location] || '.'}/#{target_js}" - # extra - if opts[:extra] - sh "sed -e 's/{{version}}/#{version}/' #{source}/#{opts[:extra]} \ - > #{opts[:location]}/#{opts[:extra]}" - end - puts "Done, see #{opts[:location] || '.'}/#{target_js}" end end -templated_build "CommonJS", :location => "lib", :extra => "package.json" templated_build "jQuery" templated_build "Dojo", :location => "dojox/string" templated_build "YUI3", :location => "yui3/mustache" diff --git a/TESTING.md b/TESTING.md index 6abec93..47d15e4 100644 --- a/TESTING.md +++ b/TESTING.md @@ -1,4 +1,4 @@ -## Running the mustache.js Test Suite +## Running the mustache.js test suite The mustache.js test suite uses the [RSpec](http://rspec.info/) testing framework. In order to run the tests you'll need to install [Ruby](http://ruby-lang.org/) @@ -57,6 +57,6 @@ suite with the following command: All test files live in the spec/_files directory. To create a new test: - * Create a template file `somename.mustache` - * Create a javascript file with data and functions `somename.js` - * Create a file the expected result `somename.txt` + * Create a template file called `somename.mustache` + * Create a JavaScript file containing the view called `somename.js` + * Create a text file with the expected result called `somename.txt` diff --git a/mustache.js b/mustache.js index da2c410..0121abc 100644 --- a/mustache.js +++ b/mustache.js @@ -1,38 +1,75 @@ -/* - mustache.js — Logic-less templates in JavaScript +/*! + * mustache.js - Logic-less {{mustache}} templates with JavaScript + * http://github.com/janl/mustache.js + */ +var Mustache = (typeof module !== "undefined" && module.exports) || {}; - See http://mustache.github.com/ for more info. -*/ +(function (exports) { + + exports.name = "mustache.js"; + exports.version = "0.5.0-dev"; + exports.tags = ["{{", "}}"]; + exports.parse = parse; + exports.compile = compile; + exports.render = render; + exports.clearCache = clearCache; + + exports.to_html = render; // keep backwards compatibility -var Mustache = function () { var _toString = Object.prototype.toString; + var _isArray = Array.isArray; + var _forEach = Array.prototype.forEach; + var _trim = String.prototype.trim; - Array.isArray = Array.isArray || function (obj) { - return _toString.call(obj) == "[object Array]"; + var isArray; + if (_isArray) { + isArray = _isArray; + } else { + isArray = function (obj) { + return _toString.call(obj) === "[object Array]"; + }; + } + + var forEach; + if (_forEach) { + forEach = function (obj, callback, scope) { + return _forEach.call(obj, callback, scope); + }; + } else { + forEach = function (obj, callback, scope) { + for (var i = 0, len = obj.length; i < len; ++i) { + callback.call(scope, obj[i], i, obj); + } + }; } - var _trim = String.prototype.trim, trim; + var spaceRe = /^\s*$/; + function isWhitespace(string) { + return spaceRe.test(string); + } + + var trim; if (_trim) { - trim = function (text) { - return text == null ? "" : _trim.call(text); - } + trim = function (string) { + return string == null ? "" : _trim.call(string); + }; } else { var trimLeft, trimRight; - // IE doesn't match non-breaking spaces with \s. - if ((/\S/).test("\xA0")) { - trimLeft = /^[\s\xA0]+/; - trimRight = /[\s\xA0]+$/; - } else { + if (isWhitespace("\xA0")) { trimLeft = /^\s+/; trimRight = /\s+$/; + } else { + // IE doesn't match non-breaking spaces with \s, thanks jQuery. + trimLeft = /^[\s\xA0]+/; + trimRight = /[\s\xA0]+$/; } - trim = function (text) { - return text == null ? "" : - text.toString().replace(trimLeft, "").replace(trimRight, ""); - } + trim = function (string) { + return string == null ? "" : + String(string).replace(trimLeft, "").replace(trimRight, ""); + }; } var escapeMap = { @@ -49,388 +86,451 @@ var Mustache = function () { }); } - var regexCache = {}; - var Renderer = function () {}; - - Renderer.prototype = { - otag: "{{", - ctag: "}}", - pragmas: {}, - buffer: [], - pragmas_implemented: { - "IMPLICIT-ITERATOR": true - }, - context: {}, - - render: function (template, context, partials, in_recursion) { - // reset buffer & set context - if (!in_recursion) { - this.context = context; - this.buffer = []; // TODO: make this non-lazy - } + /** + * Adds the `template`, `line`, and `file` properties to the given error + * object and alters the message to provide more useful debugging information. + */ + function debug(e, template, line, file) { + file = file || "