| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785 |
- var utils = require('./utils')
- var _tags = require('./tags')
- var _filters = require('./filters')
- var parser = require('./parser')
- var dateformatter = require('./dateformatter')
- var loaders = require('./loaders')
- /**
- * Swig version number as a string.
- * @example
- * if (swig.version === "1.4.2") { ... }
- *
- * @type {String}
- */
- exports.version = '1.4.2'
- /**
- * Swig Options Object. This object can be passed to many of the API-level Swig methods to control various aspects of the engine. All keys are optional.
- * @typedef {Object} SwigOpts
- * @property {boolean} autoescape Controls whether or not variable output will automatically be escaped for safe HTML output. Defaults to <code data-language="js">true</code>. Functions executed in variable statements will not be auto-escaped. Your application/functions should take care of their own auto-escaping.
- * @property {array} varControls Open and close controls for variables. Defaults to <code data-language="js">['{{', '}}']</code>.
- * @property {array} tagControls Open and close controls for tags. Defaults to <code data-language="js">['{%', '%}']</code>.
- * @property {array} cmtControls Open and close controls for comments. Defaults to <code data-language="js">['{#', '#}']</code>.
- * @property {object} locals Default variable context to be passed to <strong>all</strong> templates.
- * @property {CacheOptions} cache Cache control for templates. Defaults to saving in <code data-language="js">'memory'</code>. Send <code data-language="js">false</code> to disable. Send an object with <code data-language="js">get</code> and <code data-language="js">set</code> functions to customize.
- * @property {TemplateLoader} loader The method that Swig will use to load templates. Defaults to <var>swig.loaders.fs</var>.
- */
- var defaultOptions = {
- autoescape: true,
- varControls: ['{{', '}}'],
- tagControls: ['{%', '%}'],
- cmtControls: ['{#', '#}'],
- locals: {},
- /**
- * Cache control for templates. Defaults to saving all templates into memory.
- * @typedef {boolean|string|object} CacheOptions
- * @example
- * // Default
- * swig.setDefaults({ cache: 'memory' });
- * @example
- * // Disables caching in Swig.
- * swig.setDefaults({ cache: false });
- * @example
- * // Custom cache storage and retrieval
- * swig.setDefaults({
- * cache: {
- * get: function (key) { ... },
- * set: function (key, val) { ... }
- * }
- * });
- */
- cache: 'memory',
- /**
- * Configure Swig to use either the <var>swig.loaders.fs</var> or <var>swig.loaders.memory</var> template loader. Or, you can write your own!
- * For more information, please see the <a href="../loaders/">Template Loaders documentation</a>.
- * @typedef {class} TemplateLoader
- * @example
- * // Default, FileSystem loader
- * swig.setDefaults({ loader: swig.loaders.fs() });
- * @example
- * // FileSystem loader allowing a base path
- * // With this, you don't use relative URLs in your template references
- * swig.setDefaults({ loader: swig.loaders.fs(__dirname + '/templates') });
- * @example
- * // Memory Loader
- * swig.setDefaults({ loader: swig.loaders.memory({
- * layout: '{% block foo %}{% endblock %}',
- * page1: '{% extends "layout" %}{% block foo %}Tacos!{% endblock %}'
- * })});
- */
- loader: loaders.fs()
- }
- var defaultInstance
- /**
- * Empty function, used in templates.
- * @return {string} Empty string
- * @private
- */
- function efn () {
- return ''
- }
- /**
- * Validate the Swig options object.
- * @param {?SwigOpts} options Swig options object.
- * @return {undefined} This method will throw errors if anything is wrong.
- * @private
- */
- function validateOptions (options) {
- if (!options) {
- return
- }
- utils.each(['varControls', 'tagControls', 'cmtControls'], function (key) {
- if (!options.hasOwnProperty(key)) {
- return
- }
- if (!utils.isArray(options[key]) || options[key].length !== 2) {
- throw new Error(
- 'Option "' +
- key +
- '" must be an array containing 2 different control strings.'
- )
- }
- if (options[key][0] === options[key][1]) {
- throw new Error(
- 'Option "' + key + '" open and close controls must not be the same.'
- )
- }
- utils.each(options[key], function (a, i) {
- if (a.length < 2) {
- throw new Error(
- 'Option "' +
- key +
- '" ' +
- (i ? 'open ' : 'close ') +
- 'control must be at least 2 characters. Saw "' +
- a +
- '" instead.'
- )
- }
- })
- })
- if (options.hasOwnProperty('cache')) {
- if (options.cache && options.cache !== 'memory') {
- if (!options.cache.get || !options.cache.set) {
- throw new Error(
- 'Invalid cache option ' +
- JSON.stringify(options.cache) +
- ' found. Expected "memory" or { get: function (key) { ... }, set: function (key, value) { ... } }.'
- )
- }
- }
- }
- if (options.hasOwnProperty('loader')) {
- if (options.loader) {
- if (!options.loader.load || !options.loader.resolve) {
- throw new Error(
- 'Invalid loader option ' +
- JSON.stringify(options.loader) +
- ' found. Expected { load: function (pathname, cb) { ... }, resolve: function (to, from) { ... } }.'
- )
- }
- }
- }
- }
- /**
- * Set defaults for the base and all new Swig environments.
- *
- * @example
- * swig.setDefaults({ cache: false });
- * // => Disables Cache
- *
- * @example
- * swig.setDefaults({ locals: { now: function () { return new Date(); } }});
- * // => sets a globally accessible method for all template
- * // contexts, allowing you to print the current date
- * // => {{ now()|date('F jS, Y') }}
- *
- * @param {SwigOpts} [options={}] Swig options object.
- * @return {undefined}
- */
- exports.setDefaults = function (options) {
- validateOptions(options)
- defaultInstance.options = utils.extend(defaultInstance.options, options)
- }
- /**
- * Set the default TimeZone offset for date formatting via the date filter. This is a global setting and will affect all Swig environments, old or new.
- * @param {number} offset Offset from GMT, in minutes.
- * @return {undefined}
- */
- exports.setDefaultTZOffset = function (offset) {
- dateformatter.tzOffset = offset
- }
- /**
- * Create a new, separate Swig compile/render environment.
- *
- * @example
- * var swig = require('swig');
- * var myswig = new swig.Swig({varControls: ['<%=', '%>']});
- * myswig.render('Tacos are <%= tacos =>!', { locals: { tacos: 'delicious' }});
- * // => Tacos are delicious!
- * swig.render('Tacos are <%= tacos =>!', { locals: { tacos: 'delicious' }});
- * // => 'Tacos are <%= tacos =>!'
- *
- * @param {SwigOpts} [opts={}] Swig options object.
- * @return {object} New Swig environment.
- */
- exports.Swig = function (opts) {
- validateOptions(opts)
- this.options = utils.extend({}, defaultOptions, opts || {})
- this.cache = {}
- this.extensions = {}
- var self = this
- var tags = _tags
- var filters = _filters
- /**
- * Get combined locals context.
- * @param {?SwigOpts} [options] Swig options object.
- * @return {object} Locals context.
- * @private
- */
- function getLocals (options) {
- if (!options || !options.locals) {
- return self.options.locals
- }
- return utils.extend({}, self.options.locals, options.locals)
- }
- /**
- * Determine whether caching is enabled via the options provided and/or defaults
- * @param {SwigOpts} [options={}] Swig Options Object
- * @return {boolean}
- * @private
- */
- function shouldCache (options) {
- options = options || {}
- return (
- (options.hasOwnProperty('cache') && !options.cache) || !self.options.cache
- )
- }
- /**
- * Get compiled template from the cache.
- * @param {string} key Name of template.
- * @return {object|undefined} Template function and tokens.
- * @private
- */
- function cacheGet (key, options) {
- if (shouldCache(options)) {
- return
- }
- if (self.options.cache === 'memory') {
- return self.cache[key]
- }
- return self.options.cache.get(key)
- }
- /**
- * Store a template in the cache.
- * @param {string} key Name of template.
- * @param {object} val Template function and tokens.
- * @return {undefined}
- * @private
- */
- function cacheSet (key, options, val) {
- if (shouldCache(options)) {
- return
- }
- if (self.options.cache === 'memory') {
- self.cache[key] = val
- return
- }
- self.options.cache.set(key, val)
- }
- /**
- * Clears the in-memory template cache.
- *
- * @example
- * swig.invalidateCache();
- *
- * @return {undefined}
- */
- this.invalidateCache = function () {
- if (self.options.cache === 'memory') {
- self.cache = {}
- }
- }
- /**
- * Add a custom filter for swig variables.
- *
- * @example
- * function replaceMs(input) { return input.replace(/m/g, 'f'); }
- * swig.setFilter('replaceMs', replaceMs);
- * // => {{ "onomatopoeia"|replaceMs }}
- * // => onofatopeia
- *
- * @param {string} name Name of filter, used in templates. <strong>Will</strong> overwrite previously defined filters, if using the same name.
- * @param {function} method Function that acts against the input. See <a href="/docs/filters/#custom">Custom Filters</a> for more information.
- * @return {undefined}
- */
- this.setFilter = function (name, method) {
- if (typeof method !== 'function') {
- throw new Error('Filter "' + name + '" is not a valid function.')
- }
- filters[name] = method
- }
- /**
- * Add a custom tag. To expose your own extensions to compiled template code, see <code data-language="js">swig.setExtension</code>.
- *
- * For a more in-depth explanation of writing custom tags, see <a href="../extending/#tags">Custom Tags</a>.
- *
- * @example
- * var tacotag = require('./tacotag');
- * swig.setTag('tacos', tacotag.parse, tacotag.compile, tacotag.ends, tacotag.blockLevel);
- * // => {% tacos %}Make this be tacos.{% endtacos %}
- * // => Tacos tacos tacos tacos.
- *
- * @param {string} name Tag name.
- * @param {function} parse Method for parsing tokens.
- * @param {function} compile Method for compiling renderable output.
- * @param {boolean} [ends=false] Whether or not this tag requires an <i>end</i> tag.
- * @param {boolean} [blockLevel=false] If false, this tag will not be compiled outside of <code>block</code> tags when extending a parent template.
- * @return {undefined}
- */
- this.setTag = function (name, parse, compile, ends, blockLevel) {
- if (typeof parse !== 'function') {
- throw new Error(
- 'Tag "' + name + '" parse method is not a valid function.'
- )
- }
- if (typeof compile !== 'function') {
- throw new Error(
- 'Tag "' + name + '" compile method is not a valid function.'
- )
- }
- tags[name] = {
- parse: parse,
- compile: compile,
- ends: ends || false,
- block: !!blockLevel
- }
- }
- /**
- * Add extensions for custom tags. This allows any custom tag to access a globally available methods via a special globally available object, <var>_ext</var>, in templates.
- *
- * @example
- * swig.setExtension('trans', function (v) { return translate(v); });
- * function compileTrans(compiler, args, content, parent, options) {
- * return '_output += _ext.trans(' + args[0] + ');'
- * };
- * swig.setTag('trans', parseTrans, compileTrans, true);
- *
- * @param {string} name Key name of the extension. Accessed via <code data-language="js">_ext[name]</code>.
- * @param {*} object The method, value, or object that should be available via the given name.
- * @return {undefined}
- */
- this.setExtension = function (name, object) {
- self.extensions[name] = object
- }
- /**
- * Parse a given source string into tokens.
- *
- * @param {string} source Swig template source.
- * @param {SwigOpts} [options={}] Swig options object.
- * @return {object} parsed Template tokens object.
- * @private
- */
- this.parse = function (source, options) {
- validateOptions(options)
- var locals = getLocals(options)
- var opt = {}
- var k
- for (k in options) {
- if (options.hasOwnProperty(k) && k !== 'locals') {
- opt[k] = options[k]
- }
- }
- options = utils.extend({}, self.options, opt)
- options.locals = locals
- return parser.parse(this, source, options, tags, filters)
- }
- /**
- * Parse a given file into tokens.
- *
- * @param {string} pathname Full path to file to parse.
- * @param {SwigOpts} [options={}] Swig options object.
- * @return {object} parsed Template tokens object.
- * @private
- */
- this.parseFile = function (pathname, options) {
- var src
- if (!options) {
- options = {}
- }
- pathname = self.options.loader.resolve(pathname, options.resolveFrom)
- src = self.options.loader.load(pathname)
- if (!options.filename) {
- options = utils.extend({ filename: pathname }, options)
- }
- return self.parse(src, options)
- }
- /**
- * Re-Map blocks within a list of tokens to the template's block objects.
- * @param {array} tokens List of tokens for the parent object.
- * @param {object} template Current template that needs to be mapped to the parent's block and token list.
- * @return {array}
- * @private
- */
- function remapBlocks (blocks, tokens) {
- return utils.map(tokens, function (token) {
- var args = token.args ? token.args.join('') : ''
- if (token.name === 'block' && blocks[args]) {
- token = blocks[args]
- }
- if (token.content && token.content.length) {
- token.content = remapBlocks(blocks, token.content)
- }
- return token
- })
- }
- /**
- * Import block-level tags to the token list that are not actual block tags.
- * @param {array} blocks List of block-level tags.
- * @param {array} tokens List of tokens to render.
- * @return {undefined}
- * @private
- */
- function importNonBlocks (blocks, tokens) {
- var temp = []
- utils.each(blocks, function (block) {
- temp.push(block)
- })
- utils.each(temp.reverse(), function (block) {
- if (block.name !== 'block') {
- tokens.unshift(block)
- }
- })
- }
- /**
- * Recursively compile and get parents of given parsed token object.
- *
- * @param {object} tokens Parsed tokens from template.
- * @param {SwigOpts} [options={}] Swig options object.
- * @return {object} Parsed tokens from parent templates.
- * @private
- */
- function getParents (tokens, options) {
- var parentName = tokens.parent
- var parentFiles = []
- var parents = []
- var parentFile
- var parent
- var l
- while (parentName) {
- if (!options || !options.filename) {
- throw new Error(
- 'Cannot extend "' +
- parentName +
- '" because current template has no filename.'
- )
- }
- parentFile = parentFile || options.filename
- parentFile = self.options.loader.resolve(parentName, parentFile)
- parent =
- cacheGet(parentFile, options) ||
- self.parseFile(
- parentFile,
- utils.extend({}, options, { filename: parentFile })
- )
- parentName = parent.parent
- if (parentFiles.indexOf(parentFile) !== -1) {
- throw new Error('Illegal circular extends of "' + parentFile + '".')
- }
- parentFiles.push(parentFile)
- parents.push(parent)
- }
- // Remap each parents'(1) blocks onto its own parent(2), receiving the full token list for rendering the original parent(1) on its own.
- l = parents.length
- for (l = parents.length - 2; l >= 0; l -= 1) {
- parents[l].tokens = remapBlocks(parents[l].blocks, parents[l + 1].tokens)
- importNonBlocks(parents[l].blocks, parents[l].tokens)
- }
- return parents
- }
- /**
- * Pre-compile a source string into a cache-able template function.
- *
- * @example
- * swig.precompile('{{ tacos }}');
- * // => {
- * // tpl: function (_swig, _locals, _filters, _utils, _fn) { ... },
- * // tokens: {
- * // name: undefined,
- * // parent: null,
- * // tokens: [...],
- * // blocks: {}
- * // }
- * // }
- *
- * In order to render a pre-compiled template, you must have access to filters and utils from Swig. <var>efn</var> is simply an empty function that does nothing.
- *
- * @param {string} source Swig template source string.
- * @param {SwigOpts} [options={}] Swig options object.
- * @return {object} Renderable function and tokens object.
- */
- this.precompile = function (source, options) {
- var tokens = self.parse(source, options)
- var parents = getParents(tokens, options)
- var tpl
- if (parents.length) {
- // Remap the templates first-parent's tokens using this template's blocks.
- tokens.tokens = remapBlocks(tokens.blocks, parents[0].tokens)
- importNonBlocks(tokens.blocks, tokens.tokens)
- }
- try {
- tpl = new Function( // eslint-disable-line
- '_swig',
- '_ctx',
- '_filters',
- '_utils',
- '_fn',
- ' var _ext = _swig.extensions,\n' +
- ' _output = "";\n' +
- parser.compile(tokens, parents, options) +
- '\n' +
- ' return _output;\n'
- )
- } catch (e) {
- utils.throwError(e, null, options.filename)
- }
- return { tpl: tpl, tokens: tokens }
- }
- /**
- * Compile and render a template string for final output.
- *
- * When rendering a source string, a file path should be specified in the options object in order for <var>extends</var>, <var>include</var>, and <var>import</var> to work properly. Do this by adding <code data-language="js">{ filename: '/absolute/path/to/mytpl.html' }</code> to the options argument.
- *
- * @example
- * swig.render('{{ tacos }}', { locals: { tacos: 'Tacos!!!!' }});
- * // => Tacos!!!!
- *
- * @param {string} source Swig template source string.
- * @param {SwigOpts} [options={}] Swig options object.
- * @return {string} Rendered output.
- */
- this.render = function (source, options) {
- return self.compile(source, options)()
- }
- /**
- * Compile and render a template file for final output. This is most useful for libraries like Express.js.
- *
- * @example
- * swig.renderFile('./template.html', {}, function (err, output) {
- * if (err) {
- * throw err;
- * }
- * console.log(output);
- * });
- *
- * @example
- * swig.renderFile('./template.html', {});
- * // => output
- *
- * @param {string} pathName File location.
- * @param {object} [locals={}] Template variable context.
- * @param {Function} [cb] Asyncronous callback function. If not provided, <var>compileFile</var> will run syncronously.
- * @return {string} Rendered output.
- */
- this.renderFile = function (pathName, locals, cb) {
- if (cb) {
- self.compileFile(pathName, {}, function (err, fn) {
- var result
- if (err) {
- cb(err)
- return
- }
- try {
- result = fn(locals)
- } catch (err2) {
- cb(err2)
- return
- }
- cb(null, result)
- })
- return
- }
- return self.compileFile(pathName)(locals)
- }
- /**
- * Compile string source into a renderable template function.
- *
- * @example
- * var tpl = swig.compile('{{ tacos }}');
- * // => {
- * // [Function: compiled]
- * // parent: null,
- * // tokens: [{ compile: [Function] }],
- * // blocks: {}
- * // }
- * tpl({ tacos: 'Tacos!!!!' });
- * // => Tacos!!!!
- *
- * When compiling a source string, a file path should be specified in the options object in order for <var>extends</var>, <var>include</var>, and <var>import</var> to work properly. Do this by adding <code data-language="js">{ filename: '/absolute/path/to/mytpl.html' }</code> to the options argument.
- *
- * @param {string} source Swig template source string.
- * @param {SwigOpts} [options={}] Swig options object.
- * @return {function} Renderable function with keys for parent, blocks, and tokens.
- */
- this.compile = function (source, options) {
- var key = options ? options.filename : null
- var cached = key ? cacheGet(key, options) : null
- var context
- var contextLength
- var pre
- if (cached) {
- return cached
- }
- context = getLocals(options)
- contextLength = utils.keys(context).length
- pre = self.precompile(source, options)
- function compiled (locals) {
- var lcls
- if (locals && contextLength) {
- lcls = utils.extend({}, context, locals)
- } else if (locals && !contextLength) {
- lcls = locals
- } else if (!locals && contextLength) {
- lcls = context
- } else {
- lcls = {}
- }
- return pre.tpl(self, lcls, filters, utils, efn)
- }
- utils.extend(compiled, pre.tokens)
- if (key) {
- cacheSet(key, options, compiled)
- }
- return compiled
- }
- /**
- * Compile a source file into a renderable template function.
- *
- * @example
- * var tpl = swig.compileFile('./mytpl.html');
- * // => {
- * // [Function: compiled]
- * // parent: null,
- * // tokens: [{ compile: [Function] }],
- * // blocks: {}
- * // }
- * tpl({ tacos: 'Tacos!!!!' });
- * // => Tacos!!!!
- *
- * @example
- * swig.compileFile('/myfile.txt', { varControls: ['<%=', '=%>'], tagControls: ['<%', '%>']});
- * // => will compile 'myfile.txt' using the var and tag controls as specified.
- *
- * @param {string} pathname File location.
- * @param {SwigOpts} [options={}] Swig options object.
- * @param {Function} [cb] Asyncronous callback function. If not provided, <var>compileFile</var> will run syncronously.
- * @return {function} Renderable function with keys for parent, blocks, and tokens.
- */
- this.compileFile = function (pathname, options, cb) {
- var src, cached
- if (!options) {
- options = {}
- }
- pathname = self.options.loader.resolve(pathname, options.resolveFrom)
- if (!options.filename) {
- options = utils.extend({ filename: pathname }, options)
- }
- cached = cacheGet(pathname, options)
- if (cached) {
- if (cb) {
- cb(null, cached)
- return
- }
- return cached
- }
- if (cb) {
- self.options.loader.load(pathname, function (err, src) {
- if (err) {
- cb(err)
- return
- }
- var compiled
- try {
- compiled = self.compile(src, options)
- } catch (err2) {
- cb(err2)
- return
- }
- cb(err, compiled)
- })
- return
- }
- src = self.options.loader.load(pathname)
- return self.compile(src, options)
- }
- /**
- * Run a pre-compiled template function. This is most useful in the browser when you've pre-compiled your templates with the Swig command-line tool.
- *
- * @example
- * $ swig compile ./mytpl.html --wrap-start="var mytpl = " > mytpl.js
- * @example
- * <script src="mytpl.js"></script>
- * <script>
- * swig.run(mytpl, {});
- * // => "rendered template..."
- * </script>
- *
- * @param {function} tpl Pre-compiled Swig template function. Use the Swig CLI to compile your templates.
- * @param {object} [locals={}] Template variable context.
- * @param {string} [filepath] Filename used for caching the template.
- * @return {string} Rendered output.
- */
- this.run = function (tpl, locals, filepath) {
- var context = getLocals({ locals: locals })
- if (filepath) {
- cacheSet(filepath, {}, tpl)
- }
- return tpl(self, context, filters, utils, efn)
- }
- }
- /*!
- * Export methods publicly
- */
- defaultInstance = new exports.Swig()
- exports.setFilter = defaultInstance.setFilter
- exports.setTag = defaultInstance.setTag
- exports.setExtension = defaultInstance.setExtension
- exports.parseFile = defaultInstance.parseFile
- exports.precompile = defaultInstance.precompile
- exports.compile = defaultInstance.compile
- exports.compileFile = defaultInstance.compileFile
- exports.render = defaultInstance.render
- exports.renderFile = defaultInstance.renderFile
- exports.run = defaultInstance.run
- exports.invalidateCache = defaultInstance.invalidateCache
- exports.loaders = loaders
|