/** * Lo-Dash 2.4.1 (Custom Build) <http://lodash.com/> * Build: `lodash modularize modern exports="npm" -o ./npm/` * Copyright 2012-2013 The Dojo Foundation <http://dojofoundation.org/> * Based on Underscore.js 1.5.2 <http://underscorejs.org/LICENSE> * Copyright 2009-2013 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors * Available under MIT license <http://lodash.com/license> */ var defaults = require('lodash.defaults'), escape = require('lodash.escape'), escapeStringChar = require('lodash._escapestringchar'), keys = require('lodash.keys'), reInterpolate = require('lodash._reinterpolate'), templateSettings = require('lodash.templatesettings'), values = require('lodash.values'); /** Used to match empty string literals in compiled template source */ var reEmptyStringLeading = /\b__p \+= '';/g, reEmptyStringMiddle = /\b(__p \+=) '' \+/g, reEmptyStringTrailing = /(__e\(.*?\)|\b__t\)) \+\n'';/g; /** * Used to match ES6 template delimiters * http://people.mozilla.org/~jorendorff/es6-draft.html#sec-literals-string-literals */ var reEsTemplate = /\$\{([^\\}]*(?:\\.[^\\}]*)*)\}/g; /** Used to ensure capturing order of template delimiters */ var reNoMatch = /($^)/; /** Used to match unescaped characters in compiled string literals */ var reUnescapedString = /['\n\r\t\u2028\u2029\\]/g; /** * A micro-templating method that handles arbitrary delimiters, preserves * whitespace, and correctly escapes quotes within interpolated code. * * Note: In the development build, `_.template` utilizes sourceURLs for easier * debugging. See http://www.html5rocks.com/en/tutorials/developertools/sourcemaps/#toc-sourceurl * * For more information on precompiling templates see: * http://lodash.com/custom-builds * * For more information on Chrome extension sandboxes see: * http://developer.chrome.com/stable/extensions/sandboxingEval.html * * @static * @memberOf _ * @category Utilities * @param {string} text The template text. * @param {Object} data The data object used to populate the text. * @param {Object} [options] The options object. * @param {RegExp} [options.escape] The "escape" delimiter. * @param {RegExp} [options.evaluate] The "evaluate" delimiter. * @param {Object} [options.imports] An object to import into the template as local variables. * @param {RegExp} [options.interpolate] The "interpolate" delimiter. * @param {string} [sourceURL] The sourceURL of the template's compiled source. * @param {string} [variable] The data object variable name. * @returns {Function|string} Returns a compiled function when no `data` object * is given, else it returns the interpolated text. * @example * * // using the "interpolate" delimiter to create a compiled template * var compiled = _.template('hello <%= name %>'); * compiled({ 'name': 'fred' }); * // => 'hello fred' * * // using the "escape" delimiter to escape HTML in data property values * _.template('<b><%- value %></b>', { 'value': '<script>' }); * // => '<b><script></b>' * * // using the "evaluate" delimiter to generate HTML * var list = '<% _.forEach(people, function(name) { %><li><%- name %></li><% }); %>'; * _.template(list, { 'people': ['fred', 'barney'] }); * // => '<li>fred</li><li>barney</li>' * * // using the ES6 delimiter as an alternative to the default "interpolate" delimiter * _.template('hello ${ name }', { 'name': 'pebbles' }); * // => 'hello pebbles' * * // using the internal `print` function in "evaluate" delimiters * _.template('<% print("hello " + name); %>!', { 'name': 'barney' }); * // => 'hello barney!' * * // using a custom template delimiters * _.templateSettings = { * 'interpolate': /{{([\s\S]+?)}}/g * }; * * _.template('hello {{ name }}!', { 'name': 'mustache' }); * // => 'hello mustache!' * * // using the `imports` option to import jQuery * var list = '<% jq.each(people, function(name) { %><li><%- name %></li><% }); %>'; * _.template(list, { 'people': ['fred', 'barney'] }, { 'imports': { 'jq': jQuery } }); * // => '<li>fred</li><li>barney</li>' * * // using the `sourceURL` option to specify a custom sourceURL for the template * var compiled = _.template('hello <%= name %>', null, { 'sourceURL': '/basic/greeting.jst' }); * compiled(data); * // => find the source of "greeting.jst" under the Sources tab or Resources panel of the web inspector * * // using the `variable` option to ensure a with-statement isn't used in the compiled template * var compiled = _.template('hi <%= data.name %>!', null, { 'variable': 'data' }); * compiled.source; * // => function(data) { * var __t, __p = '', __e = _.escape; * __p += 'hi ' + ((__t = ( data.name )) == null ? '' : __t) + '!'; * return __p; * } * * // using the `source` property to inline compiled templates for meaningful * // line numbers in error messages and a stack trace * fs.writeFileSync(path.join(cwd, 'jst.js'), '\ * var JST = {\ * "main": ' + _.template(mainText).source + '\ * };\ * '); */ function template(text, data, options) { // based on John Resig's `tmpl` implementation // http://ejohn.org/blog/javascript-micro-templating/ // and Laura Doktorova's doT.js // https://github.com/olado/doT var settings = templateSettings.imports._.templateSettings || templateSettings; text = String(text || ''); // avoid missing dependencies when `iteratorTemplate` is not defined options = defaults({}, options, settings); var imports = defaults({}, options.imports, settings.imports), importsKeys = keys(imports), importsValues = values(imports); var isEvaluating, index = 0, interpolate = options.interpolate || reNoMatch, source = "__p += '"; // compile the regexp to match each delimiter var reDelimiters = RegExp( (options.escape || reNoMatch).source + '|' + interpolate.source + '|' + (interpolate === reInterpolate ? reEsTemplate : reNoMatch).source + '|' + (options.evaluate || reNoMatch).source + '|$' , 'g'); text.replace(reDelimiters, function(match, escapeValue, interpolateValue, esTemplateValue, evaluateValue, offset) { interpolateValue || (interpolateValue = esTemplateValue); // escape characters that cannot be included in string literals source += text.slice(index, offset).replace(reUnescapedString, escapeStringChar); // replace delimiters with snippets if (escapeValue) { source += "' +\n__e(" + escapeValue + ") +\n'"; } if (evaluateValue) { isEvaluating = true; source += "';\n" + evaluateValue + ";\n__p += '"; } if (interpolateValue) { source += "' +\n((__t = (" + interpolateValue + ")) == null ? '' : __t) +\n'"; } index = offset + match.length; // the JS engine embedded in Adobe products requires returning the `match` // string in order to produce the correct `offset` value return match; }); source += "';\n"; // if `variable` is not specified, wrap a with-statement around the generated // code to add the data object to the top of the scope chain var variable = options.variable, hasVariable = variable; if (!hasVariable) { variable = 'obj'; source = 'with (' + variable + ') {\n' + source + '\n}\n'; } // cleanup code by stripping empty strings source = (isEvaluating ? source.replace(reEmptyStringLeading, '') : source) .replace(reEmptyStringMiddle, '$1') .replace(reEmptyStringTrailing, '$1;'); // frame code as the function body source = 'function(' + variable + ') {\n' + (hasVariable ? '' : variable + ' || (' + variable + ' = {});\n') + "var __t, __p = '', __e = _.escape" + (isEvaluating ? ', __j = Array.prototype.join;\n' + "function print() { __p += __j.call(arguments, '') }\n" : ';\n' ) + source + 'return __p\n}'; try { var result = Function(importsKeys, 'return ' + source ).apply(undefined, importsValues); } catch(e) { e.source = source; throw e; } if (data) { return result(data); } // provide the compiled function's source by its `toString` method, in // supported environments, or the `source` property as a convenience for // inlining compiled templates during the build process result.source = source; return result; } module.exports = template;