From fbc66a81400418983c2d7ccf0457aeea7af0fb1a Mon Sep 17 00:00:00 2001 From: Michael Jackson Date: Sat, 17 Mar 2012 00:06:19 -0600 Subject: [PATCH] Another rewrite - Cleaner separation of responsibilities in the code between scanning, parsing, compiling, and rendering functions. - Much faster --- Rakefile | 20 +- mustache.js | 844 +++++++++--------- spec/_files/ampersand_escape.js | 3 - spec/_files/apostrophe.js | 1 - spec/_files/array_of_strings.js | 1 - spec/_files/backslashes.js | 3 - spec/_files/bug_11_eating_whitespace.js | 3 - spec/_files/changing_delimiters.js | 4 - spec/_files/comments.js | 5 - spec/_files/disappearing_whitespace.js | 4 - spec/_files/empty_list.js | 3 - spec/_files/empty_sections.js | 1 - spec/_files/empty_template.js | 1 - spec/_files/error_not_found.js | 1 - spec/_files/higher_order_sections.js | 9 - spec/_files/inverted_section.js | 3 - spec/_files/multiline_comment.js | 1 - spec/_files/partial_array_of_partials.js | 3 - .../partial_array_of_partials_implicit.js | 3 - spec/_files/partial_empty.js | 3 - spec/_files/partial_template.js | 6 - spec/_files/string_as_context.js | 4 - spec/_files/two_sections.js | 1 - spec/_files/unescaped.js | 5 - spec/mustache_spec.rb | 218 ----- test/_files/ampersand_escape.js | 3 + .../_files/ampersand_escape.mustache | 0 {spec => test}/_files/ampersand_escape.txt | 0 test/_files/apostrophe.js | 4 + {spec => test}/_files/apostrophe.mustache | 0 {spec => test}/_files/apostrophe.txt | 0 test/_files/array_of_strings.js | 3 + .../_files/array_of_strings.mustache | 0 {spec => test}/_files/array_of_strings.txt | 0 test/_files/backslashes.js | 3 + {spec => test}/_files/backslashes.mustache | 0 {spec => test}/_files/backslashes.txt | 0 test/_files/bug_11_eating_whitespace.js | 3 + .../_files/bug_11_eating_whitespace.mustache | 0 .../_files/bug_11_eating_whitespace.txt | 0 test/_files/changing_delimiters.js | 4 + .../_files/changing_delimiters.mustache | 0 {spec => test}/_files/changing_delimiters.txt | 0 test/_files/comments.js | 5 + {spec => test}/_files/comments.mustache | 0 {spec => test}/_files/comments.txt | 0 {spec => test}/_files/complex.js | 12 +- {spec => test}/_files/complex.mustache | 0 {spec => test}/_files/complex.txt | 0 {spec => test}/_files/context_lookup.js | 4 +- {spec => test}/_files/context_lookup.mustache | 0 {spec => test}/_files/context_lookup.txt | 0 {spec => test}/_files/delimiters.js | 4 +- {spec => test}/_files/delimiters.mustache | 0 {spec => test}/_files/delimiters.txt | 0 test/_files/disappearing_whitespace.js | 4 + .../_files/disappearing_whitespace.mustache | 0 .../_files/disappearing_whitespace.txt | 0 {spec => test}/_files/dot_notation.js | 10 +- {spec => test}/_files/dot_notation.mustache | 2 +- {spec => test}/_files/dot_notation.txt | 0 {spec => test}/_files/double_render.js | 4 +- {spec => test}/_files/double_render.mustache | 0 {spec => test}/_files/double_render.txt | 0 test/_files/empty_list.js | 3 + {spec => test}/_files/empty_list.mustache | 0 {spec => test}/_files/empty_list.txt | 0 test/_files/empty_sections.js | 1 + {spec => test}/_files/empty_sections.mustache | 0 {spec => test}/_files/empty_sections.txt | 0 {spec => test}/_files/empty_string.js | 4 +- {spec => test}/_files/empty_string.mustache | 0 {spec => test}/_files/empty_string.txt | 0 test/_files/empty_template.js | 1 + {spec => test}/_files/empty_template.mustache | 0 {spec => test}/_files/empty_template.txt | 0 test/_files/error_not_found.js | 3 + .../_files/error_not_found.mustache | 0 {spec => test}/_files/error_not_found.txt | 0 {spec => test}/_files/escaped.js | 6 +- {spec => test}/_files/escaped.mustache | 0 {spec => test}/_files/escaped.txt | 0 test/_files/higher_order_sections.js | 9 + .../_files/higher_order_sections.mustache | 0 .../_files/higher_order_sections.txt | 0 {spec => test}/_files/included_tag.js | 4 +- {spec => test}/_files/included_tag.mustache | 0 {spec => test}/_files/included_tag.txt | 0 test/_files/inverted_section.js | 3 + .../_files/inverted_section.mustache | 0 {spec => test}/_files/inverted_section.txt | 0 .../_files/keys_with_questionmarks.js | 4 +- .../_files/keys_with_questionmarks.mustache | 0 .../_files/keys_with_questionmarks.txt | 0 test/_files/multiline_comment.js | 1 + .../_files/multiline_comment.mustache | 0 {spec => test}/_files/multiline_comment.txt | 0 {spec => test}/_files/nested_iterating.js | 4 +- .../_files/nested_iterating.mustache | 0 {spec => test}/_files/nested_iterating.txt | 0 {spec => test}/_files/nesting.js | 4 +- {spec => test}/_files/nesting.mustache | 0 {spec => test}/_files/nesting.txt | 0 {spec => test}/_files/nesting_same_name.js | 4 +- .../_files/nesting_same_name.mustache | 0 {spec => test}/_files/nesting_same_name.txt | 0 {spec => test}/_files/null_string.js | 4 +- {spec => test}/_files/null_string.mustache | 0 {spec => test}/_files/null_string.txt | 0 {spec => test}/_files/partial_array.js | 4 +- {spec => test}/_files/partial_array.mustache | 0 {spec => test}/_files/partial_array.partial | 0 {spec => test}/_files/partial_array.txt | 0 test/_files/partial_array_of_partials.js | 8 + .../_files/partial_array_of_partials.mustache | 0 .../_files/partial_array_of_partials.partial | 0 .../_files/partial_array_of_partials.txt | 0 .../partial_array_of_partials_implicit.js | 3 + ...artial_array_of_partials_implicit.mustache | 0 ...partial_array_of_partials_implicit.partial | 0 .../partial_array_of_partials_implicit.txt | 0 test/_files/partial_empty.js | 3 + {spec => test}/_files/partial_empty.mustache | 0 {spec => test}/_files/partial_empty.partial | 0 {spec => test}/_files/partial_empty.txt | 0 {spec => test}/_files/partial_recursion.js | 4 +- .../_files/partial_recursion.mustache | 0 .../_files/partial_recursion.partial | 2 +- {spec => test}/_files/partial_recursion.txt | 0 test/_files/partial_template.js | 6 + .../_files/partial_template.mustache | 0 .../_files/partial_template.partial | 0 {spec => test}/_files/partial_template.txt | 0 {spec => test}/_files/partial_view.js | 12 +- {spec => test}/_files/partial_view.mustache | 0 {spec => test}/_files/partial_view.partial | 2 +- {spec => test}/_files/partial_view.txt | 0 {spec => test}/_files/partial_whitespace.js | 13 +- .../_files/partial_whitespace.mustache | 0 .../_files/partial_whitespace.partial | 2 +- {spec => test}/_files/partial_whitespace.txt | 0 .../_files/recursion_with_same_names.js | 6 +- .../_files/recursion_with_same_names.mustache | 0 .../_files/recursion_with_same_names.txt | 0 {spec => test}/_files/reuse_of_enumerables.js | 4 +- .../_files/reuse_of_enumerables.mustache | 0 .../_files/reuse_of_enumerables.txt | 0 {spec => test}/_files/section_as_context.js | 9 +- .../_files/section_as_context.mustache | 0 {spec => test}/_files/section_as_context.txt | 0 {spec => test}/_files/simple.js | 6 +- {spec => test}/_files/simple.mustache | 0 {spec => test}/_files/simple.txt | 0 test/_files/string_as_context.js | 4 + .../_files/string_as_context.mustache | 0 {spec => test}/_files/string_as_context.txt | 0 {spec => test}/_files/two_in_a_row.js | 4 +- {spec => test}/_files/two_in_a_row.mustache | 0 {spec => test}/_files/two_in_a_row.txt | 0 test/_files/two_sections.js | 1 + {spec => test}/_files/two_sections.mustache | 0 {spec => test}/_files/two_sections.txt | 0 test/_files/unescaped.js | 5 + {spec => test}/_files/unescaped.mustache | 0 {spec => test}/_files/unescaped.txt | 0 {spec => test}/_files/whitespace.js | 4 +- {spec => test}/_files/whitespace.mustache | 0 {spec => test}/_files/whitespace.txt | 0 test/context_test.js | 47 + test/helper.rb | 48 + test/integration.rb | 76 ++ test/parse_test.js | 67 ++ test/render_test.js | 63 ++ test/scanner_test.js | 117 +++ test/unit.rb | 7 + 175 files changed, 1028 insertions(+), 769 deletions(-) delete mode 100644 spec/_files/ampersand_escape.js delete mode 100644 spec/_files/apostrophe.js delete mode 100644 spec/_files/array_of_strings.js delete mode 100644 spec/_files/backslashes.js delete mode 100644 spec/_files/bug_11_eating_whitespace.js delete mode 100644 spec/_files/changing_delimiters.js delete mode 100644 spec/_files/comments.js delete mode 100644 spec/_files/disappearing_whitespace.js delete mode 100644 spec/_files/empty_list.js delete mode 100644 spec/_files/empty_sections.js delete mode 100644 spec/_files/empty_template.js delete mode 100644 spec/_files/error_not_found.js delete mode 100644 spec/_files/higher_order_sections.js delete mode 100644 spec/_files/inverted_section.js delete mode 100644 spec/_files/multiline_comment.js delete mode 100644 spec/_files/partial_array_of_partials.js delete mode 100644 spec/_files/partial_array_of_partials_implicit.js delete mode 100644 spec/_files/partial_empty.js delete mode 100644 spec/_files/partial_template.js delete mode 100644 spec/_files/string_as_context.js delete mode 100644 spec/_files/two_sections.js delete mode 100644 spec/_files/unescaped.js delete mode 100644 spec/mustache_spec.rb create mode 100644 test/_files/ampersand_escape.js rename {spec => test}/_files/ampersand_escape.mustache (100%) rename {spec => test}/_files/ampersand_escape.txt (100%) create mode 100644 test/_files/apostrophe.js rename {spec => test}/_files/apostrophe.mustache (100%) rename {spec => test}/_files/apostrophe.txt (100%) create mode 100644 test/_files/array_of_strings.js rename {spec => test}/_files/array_of_strings.mustache (100%) rename {spec => test}/_files/array_of_strings.txt (100%) create mode 100644 test/_files/backslashes.js rename {spec => test}/_files/backslashes.mustache (100%) rename {spec => test}/_files/backslashes.txt (100%) create mode 100644 test/_files/bug_11_eating_whitespace.js rename {spec => test}/_files/bug_11_eating_whitespace.mustache (100%) rename {spec => test}/_files/bug_11_eating_whitespace.txt (100%) create mode 100644 test/_files/changing_delimiters.js rename {spec => test}/_files/changing_delimiters.mustache (100%) rename {spec => test}/_files/changing_delimiters.txt (100%) create mode 100644 test/_files/comments.js rename {spec => test}/_files/comments.mustache (100%) rename {spec => test}/_files/comments.txt (100%) rename {spec => test}/_files/complex.js (74%) rename {spec => test}/_files/complex.mustache (100%) rename {spec => test}/_files/complex.txt (100%) rename {spec => test}/_files/context_lookup.js (73%) rename {spec => test}/_files/context_lookup.mustache (100%) rename {spec => test}/_files/context_lookup.txt (100%) rename {spec => test}/_files/delimiters.js (89%) rename {spec => test}/_files/delimiters.mustache (100%) rename {spec => test}/_files/delimiters.txt (100%) create mode 100644 test/_files/disappearing_whitespace.js rename {spec => test}/_files/disappearing_whitespace.mustache (100%) rename {spec => test}/_files/disappearing_whitespace.txt (100%) rename {spec => test}/_files/dot_notation.js (81%) rename {spec => test}/_files/dot_notation.mustache (84%) rename {spec => test}/_files/dot_notation.txt (100%) rename {spec => test}/_files/double_render.js (65%) rename {spec => test}/_files/double_render.mustache (100%) rename {spec => test}/_files/double_render.txt (100%) create mode 100644 test/_files/empty_list.js rename {spec => test}/_files/empty_list.mustache (100%) rename {spec => test}/_files/empty_list.txt (100%) create mode 100644 test/_files/empty_sections.js rename {spec => test}/_files/empty_sections.mustache (100%) rename {spec => test}/_files/empty_sections.txt (100%) rename {spec => test}/_files/empty_string.js (73%) rename {spec => test}/_files/empty_string.mustache (100%) rename {spec => test}/_files/empty_string.txt (100%) create mode 100644 test/_files/empty_template.js rename {spec => test}/_files/empty_template.mustache (100%) rename {spec => test}/_files/empty_template.txt (100%) create mode 100644 test/_files/error_not_found.js rename {spec => test}/_files/error_not_found.mustache (100%) rename {spec => test}/_files/error_not_found.txt (100%) rename {spec => test}/_files/escaped.js (56%) rename {spec => test}/_files/escaped.mustache (100%) rename {spec => test}/_files/escaped.txt (100%) create mode 100644 test/_files/higher_order_sections.js rename {spec => test}/_files/higher_order_sections.mustache (100%) rename {spec => test}/_files/higher_order_sections.txt (100%) rename {spec => test}/_files/included_tag.js (55%) rename {spec => test}/_files/included_tag.mustache (100%) rename {spec => test}/_files/included_tag.txt (100%) create mode 100644 test/_files/inverted_section.js rename {spec => test}/_files/inverted_section.mustache (100%) rename {spec => test}/_files/inverted_section.txt (100%) rename {spec => test}/_files/keys_with_questionmarks.js (50%) rename {spec => test}/_files/keys_with_questionmarks.mustache (100%) rename {spec => test}/_files/keys_with_questionmarks.txt (100%) create mode 100644 test/_files/multiline_comment.js rename {spec => test}/_files/multiline_comment.mustache (100%) rename {spec => test}/_files/multiline_comment.txt (100%) rename {spec => test}/_files/nested_iterating.js (71%) rename {spec => test}/_files/nested_iterating.mustache (100%) rename {spec => test}/_files/nested_iterating.txt (100%) rename {spec => test}/_files/nesting.js (76%) rename {spec => test}/_files/nesting.mustache (100%) rename {spec => test}/_files/nesting.txt (100%) rename {spec => test}/_files/nesting_same_name.js (71%) rename {spec => test}/_files/nesting_same_name.mustache (100%) rename {spec => test}/_files/nesting_same_name.txt (100%) rename {spec => test}/_files/null_string.js (84%) rename {spec => test}/_files/null_string.mustache (100%) rename {spec => test}/_files/null_string.txt (100%) rename {spec => test}/_files/partial_array.js (55%) rename {spec => test}/_files/partial_array.mustache (100%) rename {spec => test}/_files/partial_array.partial (100%) rename {spec => test}/_files/partial_array.txt (100%) create mode 100644 test/_files/partial_array_of_partials.js rename {spec => test}/_files/partial_array_of_partials.mustache (100%) rename {spec => test}/_files/partial_array_of_partials.partial (100%) rename {spec => test}/_files/partial_array_of_partials.txt (100%) create mode 100644 test/_files/partial_array_of_partials_implicit.js rename {spec => test}/_files/partial_array_of_partials_implicit.mustache (100%) rename {spec => test}/_files/partial_array_of_partials_implicit.partial (100%) rename {spec => test}/_files/partial_array_of_partials_implicit.txt (100%) create mode 100644 test/_files/partial_empty.js rename {spec => test}/_files/partial_empty.mustache (100%) rename {spec => test}/_files/partial_empty.partial (100%) rename {spec => test}/_files/partial_empty.txt (100%) rename {spec => test}/_files/partial_recursion.js (78%) rename {spec => test}/_files/partial_recursion.mustache (100%) rename {spec => test}/_files/partial_recursion.partial (72%) rename {spec => test}/_files/partial_recursion.txt (100%) create mode 100644 test/_files/partial_template.js rename {spec => test}/_files/partial_template.mustache (100%) rename {spec => test}/_files/partial_template.partial (100%) rename {spec => test}/_files/partial_template.txt (100%) rename {spec => test}/_files/partial_view.js (60%) rename {spec => test}/_files/partial_view.mustache (100%) rename {spec => test}/_files/partial_view.partial (89%) rename {spec => test}/_files/partial_view.txt (100%) rename {spec => test}/_files/partial_whitespace.js (59%) rename {spec => test}/_files/partial_whitespace.mustache (100%) rename {spec => test}/_files/partial_whitespace.partial (87%) rename {spec => test}/_files/partial_whitespace.txt (100%) rename {spec => test}/_files/recursion_with_same_names.js (56%) rename {spec => test}/_files/recursion_with_same_names.mustache (100%) rename {spec => test}/_files/recursion_with_same_names.txt (100%) rename {spec => test}/_files/reuse_of_enumerables.js (69%) rename {spec => test}/_files/reuse_of_enumerables.mustache (100%) rename {spec => test}/_files/reuse_of_enumerables.txt (100%) rename {spec => test}/_files/section_as_context.js (53%) rename {spec => test}/_files/section_as_context.mustache (100%) rename {spec => test}/_files/section_as_context.txt (100%) rename {spec => test}/_files/simple.js (67%) rename {spec => test}/_files/simple.mustache (100%) rename {spec => test}/_files/simple.txt (100%) create mode 100644 test/_files/string_as_context.js rename {spec => test}/_files/string_as_context.mustache (100%) rename {spec => test}/_files/string_as_context.txt (100%) rename {spec => test}/_files/two_in_a_row.js (60%) rename {spec => test}/_files/two_in_a_row.mustache (100%) rename {spec => test}/_files/two_in_a_row.txt (100%) create mode 100644 test/_files/two_sections.js rename {spec => test}/_files/two_sections.mustache (100%) rename {spec => test}/_files/two_sections.txt (100%) create mode 100644 test/_files/unescaped.js rename {spec => test}/_files/unescaped.mustache (100%) rename {spec => test}/_files/unescaped.txt (100%) rename {spec => test}/_files/whitespace.js (60%) rename {spec => test}/_files/whitespace.mustache (100%) rename {spec => test}/_files/whitespace.txt (100%) create mode 100644 test/context_test.js create mode 100644 test/helper.rb create mode 100644 test/integration.rb create mode 100644 test/parse_test.js create mode 100644 test/render_test.js create mode 100644 test/scanner_test.js create mode 100644 test/unit.rb diff --git a/Rakefile b/Rakefile index 37cb9b3..7240f8b 100644 --- a/Rakefile +++ b/Rakefile @@ -1,20 +1,18 @@ require 'rake' require 'rake/clean' -task :default => :spec +task :default => 'test:integration' -desc "Run all specs" -task :spec do - require 'rspec/core/rake_task' - RSpec::Core::RakeTask.new(:spec) do |t| - #t.spec_opts = ['--options', "\"#{File.dirname(__FILE__)}/spec/spec.opts\""] - t.pattern = 'spec/*_spec.rb' +namespace :test do + desc "Run all integration tests" + task :integration do + require File.expand_path('../test/integration', __FILE__) end -end -desc "Run all unit tests" -task :test do - exec "vows test/*_test.js" + desc "Run all unit tests" + task :unit do + require File.expand_path('../test/unit', __FILE__) + end end # Creates a task that uses the various template wrappers to make a wrapped diff --git a/mustache.js b/mustache.js index 641cebd..7b34bbb 100644 --- a/mustache.js +++ b/mustache.js @@ -9,12 +9,18 @@ var Mustache = (typeof module !== "undefined" && module.exports) || {}; exports.name = "mustache.js"; exports.version = "0.5.0-dev"; exports.tags = ["{{", "}}"]; + exports.parse = parse; + exports.clearCache = clearCache; exports.compile = compile; + exports.compilePartial = compilePartial; exports.render = render; - exports.clearCache = clearCache; - // This is here for backwards compatibility with 0.4.x. + exports.Scanner = Scanner; + exports.Context = Context; + exports.Renderer = Renderer; + + // // This is here for backwards compatibility with 0.4.x. exports.to_html = function (template, view, partials, send) { var result = render(template, view, partials); @@ -25,63 +31,30 @@ var Mustache = (typeof module !== "undefined" && module.exports) || {}; } }; - var _toString = Object.prototype.toString; - var _isArray = Array.isArray; - var _forEach = Array.prototype.forEach; - var _trim = String.prototype.trim; - - 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 spaceRe = /^\s*$/; + var whiteRe = /\s*/; + var spaceRe = /\s+/; + var nonSpaceRe = /\S/; + var eqRe = /\s*=/; + var curlyRe = /\s*\}/; + var tagRe = /#|\^|\/|>|\{|&|=|!/; function isWhitespace(string) { - return spaceRe.test(string); + return !nonSpaceRe.test(string); } - var trim; - if (_trim) { - trim = function (string) { - return string == null ? "" : _trim.call(string); - }; - } else { - var trimLeft, trimRight; + var isArray = Array.isArray || function (obj) { + return Object.prototype.toString.call(obj) === "[object Array]"; + }; - 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]+$/; - } + var quote = (typeof JSON !== "undefined" && JSON.stringify) || function (string) { + return '"' + String(string).replace(/(^|[^\\])"/g, '\\"') + '"'; + }; - trim = function (string) { - return string == null ? "" : - String(string).replace(trimLeft, "").replace(trimRight, ""); - }; + function escapeRe(string) { + return string.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, "\\$&"); } - var escapeMap = { + var entityMap = { "&": "&", "<": "<", ">": ">", @@ -89,448 +62,519 @@ var Mustache = (typeof module !== "undefined" && module.exports) || {}; "'": ''' }; - function escapeHTML(string) { + function escapeHtml(string) { return String(string).replace(/&(?!\w+;)|[<>"']/g, function (s) { - return escapeMap[s] || s; + return entityMap[s]; }); } + // Export these utility functions. + exports.isWhitespace = isWhitespace; + exports.isArray = isArray; + exports.quote = quote; + exports.escapeRe = escapeRe; + exports.escapeHtml = escapeHtml; + + function Scanner(string) { + this.string = string; + this.tail = string; + this.pos = 0; + } + /** - * Adds the `template`, `line`, and `file` properties to the given error - * object and alters the message to provide more useful debugging information. + * Returns `true` if the tail is empty (end of string). */ - function debug(e, template, line, file) { - file = file || "