diff --git a/CHANGELOG b/CHANGELOG index 176fbfd..90fdd7d 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,6 +1,14 @@ *SVN* -* Extended grep semantics. The first argument to Enumerable#grep is now a "filter" (an object with a match() method) so you can now e.g. filter an array of DOM nodes by CSS selector. RegExp#match is now an alias to RegExp#test, so grep can still be used to filter an array of strings with a regular expression. Closes #7596. [Christophe Porteneuve, sam] +* Template enhancements. Closes #8166. [Christophe Porteneuve] + - Added String#interpolate as a shortcut for new Template(...).evaluate(...). + - If you pass String#interpolate or Template#evaluate an object with a toTemplateReplacements() method, the return value of that method will be used as the replacement object. + - You can now substitute properties of template replacement values in template strings, using dot or bracket notation (or both). Example: + "#{name.last}, #{name.first[0]}. (#{location})".interpolate({ + name: { first: "Christophe", last: "Porteneuve" }, location: "Paris" + }) // "Porteneuve, C. (Paris)" + +* Extended grep semantics. The first argument to Enumerable#grep is now a "filter" (an object with a match() method) so you can now e.g. filter an array of DOM nodes by CSS selector. RegExp#match is now an alias to RegExp#test, so grep can still be used to filter an array of strings with a regular expression. Closes #7596. [Christophe Porteneuve, sam] * Make String#scan explicitly return a string. This prevents possible issues with methods expecting input data that is typeof == 'string'. Closes #6350. [AndrewRev, Tobie Langel] diff --git a/src/string.js b/src/string.js index 2c7ff8e..bbd8770 100644 --- a/src/string.js +++ b/src/string.js @@ -199,6 +199,10 @@ Object.extend(String.prototype, { blank: function() { return /^\s*$/.test(this); + }, + + interpolate: function(object, pattern) { + return new Template(this, pattern).evaluate(object); } }); @@ -231,14 +235,32 @@ Template.Pattern = /(^|.|\r|\n)(#\{(.*?)\})/; Template.prototype = { initialize: function(template, pattern) { this.template = template.toString(); - this.pattern = pattern || Template.Pattern; + this.pattern = pattern || Template.Pattern; }, evaluate: function(object) { + if (typeof object.toTemplateReplacements == 'function') + object = object.toTemplateReplacements(); + return this.template.gsub(this.pattern, function(match) { - var before = match[1]; + if (object == null) return ''; + + var before = match[1] || ''; if (before == '\\') return match[2]; - return before + String.interpret(object[match[3]]); - }); + + var ctx = object, expr = match[3]; + var pattern = /^([^.[]+|\[((?:.*?[^\\])?)\])(\.|\[|$)/, match = pattern.exec(expr); + if (match == null) return ''; + + while (match != null) { + var comp = match[1].startsWith('[') ? match[2].gsub('\\\\]', ']') : match[1]; + ctx = ctx[comp]; + if (null == ctx || '' == match[3]) break; + expr = expr.substring('[' == match[3] ? match[1].length : match[0].length); + match = pattern.exec(expr); + } + + return before + String.interpret(ctx); + }.bind(this)); } -} +}; diff --git a/test/unit/string.html b/test/unit/string.html index 7632754..52ff8e1 100644 --- a/test/unit/string.html +++ b/test/unit/string.html @@ -146,7 +146,7 @@ assertEnumEqual(['a','b'],'ab'.toArray()); assertEnumEqual(['f','o','o'],'foo'.toArray()); }}, - + /* Note that camelize() differs from its Rails counterpart, as it is optimized for dealing with JavaScript object @@ -313,7 +313,67 @@ assertEqual('