diff --git a/.gitignore b/.gitignore index 252c5ae..a7ed15c 100644 --- a/.gitignore +++ b/.gitignore @@ -2,3 +2,4 @@ runner.js jquery.mustache.js dojox yui3 +commonjs.mustache.js diff --git a/CHANGES.md b/CHANGES.md index 8f10522..f843e74 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -2,6 +2,11 @@ ## 0.3.0 (??-??-????) +* Fix Rhino compat. +* CommonJS packaging is no longer a special case. +* DRY Rakefile. +* Allow whitespace around tag names. +* Fix partial scope. * Fix Comments. * Added inverted sections. * Avoid double encoding of entities. @@ -9,7 +14,6 @@ * Added higher order sections. - ## 0.2.3 (28-03-2010) * Better error message for missing partials. diff --git a/Rakefile b/Rakefile index 3298479..4629c97 100644 --- a/Rakefile +++ b/Rakefile @@ -11,44 +11,46 @@ end desc "Run all specs" task :spec -desc "Package for CommonJS" -task :commonjs do - puts "Packaging for CommonJS" - `mkdir lib` - `cp mustache.js lib/mustache.js` - puts "Done." +def templated_build(name, opts={}) + # Create a rule that uses the .tmpl.{pre,post} stuff to make a final, + # wrapped, output file. + # There is some extra complexity because Dojo and YUI3 use different + # template files and final locations. + short = name.downcase + source = "mustache-#{short}" + dependencies = ["mustache.js"] + Dir.glob("#{source}/*.tpl.*") + + desc "Package for #{name}" + task short.to_sym => dependencies do + target_js = opts[:location] ? "mustache.js" : "#{short}.mustache.js" + + puts "Packaging for #{name}" + sh "mkdir -p #{opts[:location]}" if opts[:location] + 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 -desc "Package for jQuery" -task :jquery do - puts "Packaging for jQuery" - source = "mustache-jquery" - target_jq = "jquery.mustache.js" - `cat #{source}/#{target_jq}.tpl.pre mustache.js #{source}/#{target_jq}.tpl.post > #{target_jq}` - puts "Done, see ./#{target_jq}" -end +templated_build "CommonJS", :location => "lib", :extra => "package.json" +templated_build "jQuery" +templated_build "Dojo", :location => "dojox/string" +templated_build "YUI3", :location => "yui3/mustache" -desc "Package for dojo" -task :dojo do - puts "Packaging for dojo" - source = "mustache-dojo" - target_js = "mustache.js" - `mkdir -p dojox; mkdir -p dojox/string` - `cat #{source}/#{target_js}.tpl.pre mustache.js #{source}/#{target_js}.tpl.post > dojox/string/#{target_js}` - puts "Done, see ./dojox/string/#{target_js} Include using dojo.require('dojox.string.mustache.'); " +def version + File.read("mustache.js").match('version: "([^\"]+)",$')[1] end -desc "Package for YUI3" -task :yui3 do - puts "Packaging for YUI3" - source = "mustache-yui3" - target_js = "mustache.js" - `mkdir -p yui3; mkdir -p yui3/mustache` - `cat #{source}/#{target_js}.tpl.pre mustache.js #{source}/#{target_js}.tpl.post > yui3/mustache/#{target_js}` - puts "Done, see ./yui3/mustache/#{target_js}" -end desc "Remove temporary files." task :clean do - `git clean -fdx` + sh "git clean -fdx" end diff --git a/THANKS.md b/THANKS.md index 67cd3a0..6dac939 100644 --- a/THANKS.md +++ b/THANKS.md @@ -16,3 +16,5 @@ Mustache.js wouldn't kick ass if it weren't for these fine souls: * Will Leinweber / will * dpree * Jason Smith / jhs + * Aaron Gibralter / agibralter + * Ross Boucher / boucher diff --git a/examples/array_of_partials_implicit_partial.2.html b/examples/array_of_partials_implicit_partial.2.html new file mode 100644 index 0000000..12f7159 --- /dev/null +++ b/examples/array_of_partials_implicit_partial.2.html @@ -0,0 +1 @@ +{{.}} diff --git a/examples/array_of_partials_implicit_partial.html b/examples/array_of_partials_implicit_partial.html new file mode 100644 index 0000000..11537e9 --- /dev/null +++ b/examples/array_of_partials_implicit_partial.html @@ -0,0 +1,5 @@ +Here is some stuff! +{{%IMPLICIT-ITERATOR}} +{{#numbers}} +{{>partial}} +{{/numbers}} diff --git a/examples/array_of_partials_implicit_partial.js b/examples/array_of_partials_implicit_partial.js new file mode 100644 index 0000000..fcdf3b0 --- /dev/null +++ b/examples/array_of_partials_implicit_partial.js @@ -0,0 +1,3 @@ +var partial_context = { + numbers: ['1', '2', '3', '4'] +}; diff --git a/examples/array_of_partials_implicit_partial.txt b/examples/array_of_partials_implicit_partial.txt new file mode 100644 index 0000000..f622375 --- /dev/null +++ b/examples/array_of_partials_implicit_partial.txt @@ -0,0 +1,5 @@ +Here is some stuff! +1 +2 +3 +4 diff --git a/examples/array_of_partials_partial.2.html b/examples/array_of_partials_partial.2.html new file mode 100644 index 0000000..bdde77d --- /dev/null +++ b/examples/array_of_partials_partial.2.html @@ -0,0 +1 @@ +{{i}} diff --git a/examples/array_of_partials_partial.html b/examples/array_of_partials_partial.html new file mode 100644 index 0000000..1af6d68 --- /dev/null +++ b/examples/array_of_partials_partial.html @@ -0,0 +1,4 @@ +Here is some stuff! +{{#numbers}} +{{>partial}} +{{/numbers}} diff --git a/examples/array_of_partials_partial.js b/examples/array_of_partials_partial.js new file mode 100644 index 0000000..45611cf --- /dev/null +++ b/examples/array_of_partials_partial.js @@ -0,0 +1,3 @@ +var partial_context = { + numbers: [{i: '1'}, {i: '2'}, {i: '3'}, {i: '4'}] +}; diff --git a/examples/array_of_partials_partial.txt b/examples/array_of_partials_partial.txt new file mode 100644 index 0000000..f622375 --- /dev/null +++ b/examples/array_of_partials_partial.txt @@ -0,0 +1,5 @@ +Here is some stuff! +1 +2 +3 +4 diff --git a/examples/bug_11_eating_whitespace.html b/examples/bug_11_eating_whitespace.html new file mode 100644 index 0000000..8d5cd92 --- /dev/null +++ b/examples/bug_11_eating_whitespace.html @@ -0,0 +1 @@ +{{tag}} foo diff --git a/examples/bug_11_eating_whitespace.js b/examples/bug_11_eating_whitespace.js new file mode 100644 index 0000000..78ce975 --- /dev/null +++ b/examples/bug_11_eating_whitespace.js @@ -0,0 +1,3 @@ +var bug_11_eating_whitespace = { + tag: "yo" +}; diff --git a/examples/bug_11_eating_whitespace.txt b/examples/bug_11_eating_whitespace.txt new file mode 100644 index 0000000..f5bbc85 --- /dev/null +++ b/examples/bug_11_eating_whitespace.txt @@ -0,0 +1 @@ +yo foo diff --git a/examples/partial_recursion.2.html b/examples/partial_recursion.2.html new file mode 100644 index 0000000..c002d68 --- /dev/null +++ b/examples/partial_recursion.2.html @@ -0,0 +1,4 @@ +{{name}} +{{#children}} + {{>partial}} +{{/children}} \ No newline at end of file diff --git a/examples/partial_recursion.html b/examples/partial_recursion.html new file mode 100644 index 0000000..8d67c38 --- /dev/null +++ b/examples/partial_recursion.html @@ -0,0 +1,4 @@ +{{name}} +{{#kids}} + {{>partial}} +{{/kids}} \ No newline at end of file diff --git a/examples/partial_recursion.js b/examples/partial_recursion.js new file mode 100644 index 0000000..ad1f2eb --- /dev/null +++ b/examples/partial_recursion.js @@ -0,0 +1,11 @@ +var partial_context = { + name: '1', + kids: [ + { + name: '1.1', + children: [ + {name: '1.1.1'} + ] + } + ] +}; diff --git a/examples/partial_recursion.txt b/examples/partial_recursion.txt new file mode 100644 index 0000000..0f70515 --- /dev/null +++ b/examples/partial_recursion.txt @@ -0,0 +1,3 @@ +1 +1.1 +1.1.1 diff --git a/examples/whitespace_partial.2.html b/examples/whitespace_partial.2.html new file mode 100644 index 0000000..9c46084 --- /dev/null +++ b/examples/whitespace_partial.2.html @@ -0,0 +1,5 @@ +Hello {{ name}} +You have just won ${{value }}! +{{# in_ca }} +Well, ${{ taxed_value }}, after taxes. +{{/ in_ca }} diff --git a/examples/whitespace_partial.html b/examples/whitespace_partial.html new file mode 100644 index 0000000..ce43cb3 --- /dev/null +++ b/examples/whitespace_partial.html @@ -0,0 +1,3 @@ +

{{ greeting }}

+{{> partial }} +

{{ farewell }}

\ No newline at end of file diff --git a/examples/whitespace_partial.js b/examples/whitespace_partial.js new file mode 100644 index 0000000..30ade55 --- /dev/null +++ b/examples/whitespace_partial.js @@ -0,0 +1,19 @@ +var partial_context = { + greeting: function() { + return "Welcome"; + }, + + farewell: function() { + return "Fair enough, right?"; + }, + + partial: { + name: "Chris", + value: 10000, + taxed_value: function() { + return this.value - (this.value * 0.4); + }, + in_ca: true + } +}; + diff --git a/examples/whitespace_partial.txt b/examples/whitespace_partial.txt new file mode 100644 index 0000000..160b0b6 --- /dev/null +++ b/examples/whitespace_partial.txt @@ -0,0 +1,6 @@ +

Welcome

+Hello Chris +You have just won $10000! +Well, $6000, after taxes. + +

Fair enough, right?

diff --git a/lib/mustache.js b/lib/mustache.js deleted file mode 100644 index ecfefcb..0000000 --- a/lib/mustache.js +++ /dev/null @@ -1,296 +0,0 @@ -/* - mustache.js — Logic-less templates in JavaScript - - See http://mustache.github.com/ for more info. -*/ - -var Mustache = function() { - var Renderer = function() {}; - - Renderer.prototype = { - otag: "{{", - ctag: "}}", - pragmas: {}, - buffer: [], - pragmas_implemented: { - "IMPLICIT-ITERATOR": true - }, - - render: function(template, context, partials, in_recursion) { - // fail fast - if(template.indexOf(this.otag) == -1) { - if(in_recursion) { - return template; - } else { - this.send(template); - return; - } - } - - if(!in_recursion) { - this.buffer = []; - } - - template = this.render_pragmas(template); - var html = this.render_section(template, context, partials); - if(in_recursion) { - return this.render_tags(html, context, partials, in_recursion); - } - - this.render_tags(html, context, partials, in_recursion); - }, - - /* - Sends parsed lines - */ - send: function(line) { - if(line != "") { - this.buffer.push(line); - } - }, - - /* - Looks for %PRAGMAS - */ - render_pragmas: function(template) { - // no pragmas - if(template.indexOf(this.otag + "%") == -1) { - return template; - } - - var that = this; - var regex = new RegExp(this.otag + "%([\\w_-]+) ?([\\w]+=[\\w]+)?" - + this.ctag); - return template.replace(regex, function(match, pragma, options) { - if(!that.pragmas_implemented[pragma]) { - throw({message: "This implementation of mustache doesn't understand the '" - + pragma + "' pragma"}); - } - that.pragmas[pragma] = {}; - if(options) { - var opts = options.split("="); - that.pragmas[pragma][opts[0]] = opts[1]; - } - return ""; - // ignore unknown pragmas silently - }); - }, - - /* - Tries to find a partial in the global scope and render it - */ - render_partial: function(name, context, partials) { - if(!partials || !partials[name]) { - throw({message: "unknown_partial '" + name + "'"}); - } - if(typeof(context[name]) != "object") { - return partials[name]; - } - return this.render(partials[name], context[name], partials, true); - }, - - /* - Renders boolean and enumerable sections - */ - render_section: function(template, context, partials) { - if(template.indexOf(this.otag + "#") == -1) { - return template; - } - var that = this; - // CSW - Added "+?" so it finds the tighest bound, not the widest - var regex = new RegExp(this.otag + "\\#(.+)" + this.ctag + - "\\s*([\\s\\S]+?)" + this.otag + "\\/\\1" + this.ctag + "\\s*", "mg"); - - // for each {{#foo}}{{/foo}} section do... - return template.replace(regex, function(match, name, content) { - var value = that.find(name, context); - if(that.is_array(value)) { // Enumerable, Let's loop! - return that.map(value, function(row) { - return that.render(content, that.merge(context, - that.create_context(row)), partials, true); - }).join(""); - } else if(value) { // boolean section - return that.render(content, context, partials, true); - } else { - return ""; - } - }); - }, - - /* - Replace {{foo}} and friends with values from our view - */ - render_tags: function(template, context, partials, in_recursion) { - // tit for tat - var that = this; - - var new_regex = function() { - return new RegExp(that.otag + "(=|!|>|\\{|%)?([^\/#]+?)\\1?" + - that.ctag + "+", "g"); - }; - - var regex = new_regex(); - var lines = template.split("\n"); - for (var i=0; i < lines.length; i++) { - lines[i] = lines[i].replace(regex, function(match, operator, name) { - switch(operator) { - case "!": // ignore comments - return match; - case "=": // set new delimiters, rebuild the replace regexp - that.set_delimiters(name); - regex = new_regex(); - return ""; - case ">": // render partial - return that.render_partial(name, context, partials); - case "{": // the triple mustache is unescaped - return that.find(name, context); - default: // escape the value - return that.escape(that.find(name, context)); - } - }, this); - if(!in_recursion) { - this.send(lines[i]); - } - } - - if(in_recursion) { - return lines.join("\n"); - } - }, - - set_delimiters: function(delimiters) { - var dels = delimiters.split(" "); - this.otag = this.escape_regex(dels[0]); - this.ctag = this.escape_regex(dels[1]); - }, - - escape_regex: function(text) { - // thank you Simon Willison - if(!arguments.callee.sRE) { - var specials = [ - '/', '.', '*', '+', '?', '|', - '(', ')', '[', ']', '{', '}', '\\' - ]; - arguments.callee.sRE = new RegExp( - '(\\' + specials.join('|\\') + ')', 'g' - ); - } - return text.replace(arguments.callee.sRE, '\\$1'); - }, - - /* - find `name` in current `context`. That is find me a value - from the view object - */ - find: function(name, context) { - name = this.trim(name); - if(typeof context[name] === "function") { - return context[name].apply(context); - } - if(context[name] !== undefined) { - return context[name]; - } - // silently ignore unkown variables - return ""; - }, - - // Utility methods - - /* - Does away with nasty characters - */ - escape: function(s) { - return ((s == null) ? "" : s).toString().replace(/[&"<>\\]/g, function(s) { - switch(s) { - case "&": return "&"; - case "\\": return "\\\\";; - case '"': return '\"';; - case "<": return "<"; - case ">": return ">"; - default: return s; - } - }); - }, - - /* - Merges all properties of object `b` into object `a`. - `b.property` overwrites a.property` - */ - merge: function(a, b) { - var _new = {}; - for(var name in a) { - if(a.hasOwnProperty(name)) { - _new[name] = a[name]; - } - }; - for(var name in b) { - if(b.hasOwnProperty(name)) { - _new[name] = b[name]; - } - }; - return _new; - }, - - // by @langalex, support for arrays of strings - create_context: function(_context) { - if(this.is_object(_context)) { - return _context; - } else if(this.pragmas["IMPLICIT-ITERATOR"]) { - var iterator = this.pragmas["IMPLICIT-ITERATOR"].iterator || "."; - var ctx = {}; - ctx[iterator] = _context; - return ctx; - } - }, - - is_object: function(a) { - return a && typeof a == "object"; - }, - - is_array: function(a) { - return Object.prototype.toString.call(a) === '[object Array]'; - }, - - /* - Gets rid of leading and trailing whitespace - */ - trim: function(s) { - return s.replace(/^\s*|\s*$/g, ""); - }, - - /* - Why, why, why? Because IE. Cry, cry cry. - */ - map: function(array, fn) { - if (typeof array.map == "function") { - return array.map(fn); - } else { - var r = []; - var l = array.length; - for(var i=0;i|\\{|%)?([^\/#\^]+?)\\1?" + + return new RegExp(that.otag + "(=|!|>|\\{|%)?([^\\/#\\^]+?)\\1?" + that.ctag + "+", "g"); }; var regex = new_regex(); + var tag_replace_callback = function(match, operator, name) { + switch(operator) { + case "!": // ignore comments + return ""; + case "=": // set new delimiters, rebuild the replace regexp + that.set_delimiters(name); + regex = new_regex(); + return ""; + case ">": // render partial + return that.render_partial(name, context, partials); + case "{": // the triple mustache is unescaped + return that.find(name, context); + default: // escape the value + return that.escape(that.find(name, context)); + } + }; var lines = template.split("\n"); - for (var i=0; i < lines.length; i++) { - lines[i] = lines[i].replace(regex, function(match, operator, name) { - switch(operator) { - case "!": // ignore comments - return ""; - case "=": // set new delimiters, rebuild the replace regexp - that.set_delimiters(name); - regex = new_regex(); - return ""; - case ">": // render partial - return that.render_partial(name.replace(/^\s+/,""), context, partials); - case "{": // the triple mustache is unescaped - return that.find(name, context); - default: // escape the value - return that.escape(that.find(name, context)); - } - }, this); - if(!in_recursion) { - this.send(lines[i]); - } - } - - if(in_recursion) { - return lines.join("\n"); - } + for(var i = 0; i < lines.length; i++) { + lines[i] = lines[i].replace(regex, tag_replace_callback, this); + if(!in_recursion) { + this.send(lines[i]); + } + } + + if(in_recursion) { + return lines.join("\n"); + } }, set_delimiters: function(delimiters) { @@ -196,7 +200,7 @@ var Mustache = function() { '(\\' + specials.join('|\\') + ')', 'g' ); } - return text.replace(arguments.callee.sRE, '\\$1'); + return text.replace(arguments.callee.sRE, '\\$1'); }, /* @@ -205,11 +209,24 @@ var Mustache = function() { */ find: function(name, context) { name = this.trim(name); - if(typeof context[name] === "function") { - return context[name].apply(context); + + // Checks whether a value is thruthy or false or 0 + function is_kinda_truthy(bool) { + return bool === false || bool === 0 || bool; } - if(context[name] !== undefined) { - return context[name]; + + var value; + if(is_kinda_truthy(context[name])) { + value = context[name]; + } else if(is_kinda_truthy(this.context[name])) { + value = this.context[name]; + } + + if(typeof value === "function") { + return value.apply(context); + } + if(value !== undefined) { + return value; } // silently ignore unkown variables return ""; @@ -226,37 +243,19 @@ var Mustache = function() { Does away with nasty characters */ escape: function(s) { - return ((s == null) ? "" : s).toString().replace(/&(?!\w+;)|["<>\\]/g, function(s) { + s = String(s === null ? "" : s); + return s.replace(/&(?!\w+;)|["<>\\]/g, function(s) { switch(s) { - case "&": return "&"; - case "\\": return "\\\\";; - case '"': return '\"';; - case "<": return "<"; - case ">": return ">"; - default: return s; + case "&": return "&"; + case "\\": return "\\\\"; + case '"': return '\"'; + case "<": return "<"; + case ">": return ">"; + default: return s; } }); }, - /* - Merges all properties of object `b` into object `a`. - `b.property` overwrites a.property` - */ - merge: function(a, b) { - var _new = {}; - for(var name in a) { - if(a.hasOwnProperty(name)) { - _new[name] = a[name]; - } - }; - for(var name in b) { - if(b.hasOwnProperty(name)) { - _new[name] = b[name]; - } - }; - return _new; - }, - // by @langalex, support for arrays of strings create_context: function(_context) { if(this.is_object(_context)) { @@ -293,7 +292,7 @@ var Mustache = function() { } else { var r = []; var l = array.length; - for(var i=0;i