UNPKG

kiwi

Version:

Simple, modular, fast and lightweight template engine, based on jQuery templates syntax.

2,335 lines (1,816 loc) 75.4 kB
(function() { // CommonJS require() function require(p){ var path = require.resolve(p) , mod = require.modules[path]; if (!mod) throw new Error('failed to require "' + p + '"'); if (!mod.exports) { mod.exports = {}; mod.call(mod.exports, mod, mod.exports, require.relative(path)); } return mod.exports; } require.modules = {}; require.resolve = function (path){ var orig = path , reg = path + '.js' , index = path + '/index.js'; return require.modules[reg] && reg || require.modules[index] && index || orig; }; require.register = function (path, fn){ require.modules[path] = fn; }; require.relative = function (parent) { return function(p){ if ('.' != p.charAt(0)) return require(p); var path = parent.split('/') , segs = p.split('/'); path.pop(); for (var i = 0; i < segs.length; i++) { var seg = segs[i]; if ('..' == seg) path.pop(); else if ('.' != seg) path.push(seg); } return require(path.join('/')); }; }; require.register("cache.js", function(module, exports, require){ /*! * Coolony's Kiwi * Copyright ©2012 Pierre Matri <pierre.matri@coolony.com> * MIT Licensed */ /** * Module dependencies. */ /** * Basic cache for client mode. * * Constructor. * * @api public */ function Cache() { this._cache = {}; } /** * Set `key` to `value`. * * @param {String} key * @param {Mixed} value * @api public */ Cache.prototype.cache = function(key, value) { if(_.isUndefined(value)) return; this._cache[key] = value; }; /** * Get `key`. * * @param {String} key * @return {Mixed} * @api public */ Cache.prototype.get = function(key) { return this._cache[key]; }; /** * Module exports. */ module.exports = Cache; }); // module: cache.js require.register("compiler.js", function(module, exports, require){ /*! * Coolony's Kiwi * Copyright ©2012 Pierre Matri <pierre.matri@coolony.com> * MIT Licensed */ /** * Module dependencies */ var token = require('./token'); var utils = require('./utils'); /** * Constants */ var TAG_OPENING_DELIMITER = '{{'; var TAG_CLOSING_DELIMITER = '}}'; var TAG_OPENING_DELIMITER_LENGTH = TAG_OPENING_DELIMITER.length; var TAG_NAME_MATCH = /^(\/)?\s*([^\s\}\(]*)/; /** * Initialize `Compiler` with the given `Template`, from the given `source`, * with `options`. * * @param {String} source * @api private */ var Compiler = module.exports = function(template) { this.template = template; this.source = template.template; this.options = template.options; this.helpers = {}; this.__compilationEnd = []; }; /** * Compile template to a native JS function, and invoke * `callback(err, compiled)`. * * @param {Function} callback * @api private */ Compiler.prototype.compile = function(callback) { var _this = this; // Callback for utils.applyAll function onProcessed(err, processed) { if(err) return callback(err); _this._tokenize(processed, onTokenized); } // Callback for Compiler#_tokenize function onTokenized(err, tokenized) { if(err) return callback(err); tokenized.compile(_this, onCompiled); } // Callback for Compiler#_compileTokens function onCompiled(err, compiled) { if(err) return callback(err); var func; try { func = new Function("$template", "$tools", "_", "$data", "$helpers", "$callback", compiled); } catch(err) { return callback(err); } func.$helpers = _this.helpers; callback(null, func); } // Apply tag before processors utils.applyAll(this.source, token.tagBeforeProcessors, [this], onProcessed); }; /** * Tokenize template, and invoke `callback(err, tokenized)`. * * @param {Function} callback * @api private */ Compiler.prototype._tokenize = function(source, callback) { var workingSource = source; var currentToken = new token.RootToken(); var rootToken = currentToken; var inToken = false; var currentPart; // Lookup templates in document, and build parse tree while(true) { var nextInflexion = workingSource.search(inToken ? TAG_CLOSING_DELIMITER : TAG_OPENING_DELIMITER ); if(nextInflexion === -1) { this._pushLiteralToken(rootToken, currentToken, workingSource); break; } else { if(inToken) nextInflexion = nextInflexion + TAG_OPENING_DELIMITER_LENGTH; currentPart = workingSource.slice(0, nextInflexion); workingSource = workingSource.slice(nextInflexion); if(!inToken) { this._pushLiteralToken(rootToken, currentToken, currentPart); } else { currentPart = currentPart.slice(2, -2); try { currentToken = this._pushToken(rootToken, currentToken, currentPart); } catch(err) { return callback(err); } } inToken = !inToken; } } // Ensure we are at root level if(rootToken !== currentToken) { return callback(new Error('Tokenization error: unexpected end of file, ' + 'expected `' + currentToken.tag.tagName + '`.' )); } callback(null, rootToken); }; /** * Create `LiteralToken` with `literal`, and append it to `parent`. * * @param {BaseToken} parent * @param {String} literal * @api private */ Compiler.prototype._pushLiteralToken = function(root, parent, literal) { if(!literal.length) return; parent.children.push(new token.LiteralToken(literal, root)); }; /** * Create `BaseToken` from tag `tag`, and append it to `current`. * Return the new working token. * * @param {BaseToken} current * @param {String} tag * @return {BaseToken} New working token. * @api private */ Compiler.prototype._pushToken = function(root, current, tag) { var match = tag.match(TAG_NAME_MATCH); var tagName = match[2]; var openingTag = match[1] !== '/'; var tagType = current.lookupTag(tagName); var newToken; // Check whether we know this tag if(!tagType) { throw new Error('Tokenization error: Unknown tag `' + tagName + '`.'); } // Handle intermediate tags if(tagType.isIntermediate) { // If we already are in an intermediate tag, close it if(current.tagType.isIntermediate) current = current.parent; // Push the tag to the stack newToken = new token.IntermediateToken(tag, tagType, root, current, [], this.options); current.intermediate.push(newToken); return newToken; } // Handle block tags else if(tagType.isBlock) { // Handle opening tags if(openingTag) { newToken = new token.BlockToken(tag, tagType, root, current, [], this.options); current.children.push(newToken); return newToken; // Handle closing tags } else { if(!current.parent) { throw new Error('Tokenization error: unexpected `' + tagName + '` tag at root level.' ); } if(current.tagType.isIntermediate) { current = current.parent; } if(current.tagType.tagName !== tagName) { throw new Error('Tokenization error: unexpected `' + tagName + '` tag, expected `' + current.tag.tagName + '`.' ); } return current.parent; } } // Handle leaf tags else { // Ensure we don't have a closing tag if(!openingTag) { throw new Error('Tokenization error: `' + tagName + '` is not a block tag.' ); } newToken = new token.LeafToken(tag, tagType, root, current, this.options); current.children.push(newToken); return current; } }; Compiler.prototype.registerTemplateHelper = function(name, helper) { this.helpers[name] = helper; }; }); // module: compiler.js require.register("filter.js", function(module, exports, require){ /*! * Coolony's Kiwi * Copyright ©2012 Pierre Matri <pierre.matri@coolony.com> * MIT Licensed */ /* * Module dependencies */ /* * Apply `filters` to `input`, and return output. * * @param {String} input * @param {Mixed[]} filters * @return {String} * @api private */ function filter(input, filters) { var unknownFilter; filters.forEach(function(filterName) { var filterArgs = []; if(_.isArray(filterName)) { filterArgs = filterName.slice(1); filterName = filterName[0]; } var filterFunction = module.exports.filters[filterName]; if(!filterFunction) { unknownFilter = filterName; return false; } input = filterFunction.apply(this, [input].concat(filterArgs)); }); if(unknownFilter) { throw new Error('Rendering error: Unknown filter `' + unknownFilter + '`.' ); } return input; } /* * Module exports */ module.exports = filter; var filters = module.exports.filters = {}; /* * Read tags from tags directory */ function loadFilters(loadedFiles) { loadedFiles = loadedFiles || frame.files.requireDir(__dirname + '/filters/'); for(var file in loadedFiles) { var fileFilters = loadedFiles[file]; for(var filter in fileFilters) { filters[filter] = fileFilters[filter]; } } } var files = ['base', 'datetime']; var acc = {}; _.each(files, function(file) { acc[file] = require('./filters/' + file); }); loadFilters(acc); }); // module: filter.js require.register("filters/base.js", function(module, exports, require){ /*! * Coolony's Kiwi * Copyright ©2012 Pierre Matri <pierre.matri@coolony.com> * MIT Licensed * * Partly based on Swig * @see https://github.com/paularmstrong/swig */ /** * Module dependencies */ var tools = require('../tools'); var utils = require('../utils'); /** * Escape HTML in `input`. * * @param {String} input * @return {String} * @api private */ exports.escape = function(input) { if(input === null || input === undefined) return ''; if(utils.isIterable(input)) { var acc = {}; _.each(input, function (value, key) { acc[key] = exports.escape(value); }); return acc; } return tools.escape(input); }; /** * Escape HTML in `input` if not marked as safe. * * @param {String} input * @return {String} * @api private */ exports.escapeIfUnsafe = function(input) { if(input === null || input === undefined) return ''; if(utils.isIterable(input)) { var acc = {}; _.each(input, function (value, key) { acc[key] = exports.escapeIfUnsafe(value); }); return acc; } return tools.escapeIfUnsafe(input); }; /** * Capitalize `input`. * * @param {Mixed} input * @return {String} * @api private */ exports.capitalize = function(input) { if(utils.isIterable(input)) { var acc = {}; _.each(input, function (value, key) { acc[key] = exports.capitalize(value); }); return acc; } input = input.toString(); return input.charAt(0).toUpperCase() + input.slice(1); }; /** * Make `input` uppercase. * * @param {String} input * @return {String} * @api private */ exports.upper = function(input) { if(utils.isIterable(input)) { var acc = {}; _.each(input, function (value, key) { acc[key] = exports.upper(value); }); return acc; } return input.toString().toUpperCase(); }; /** * Make `input` lowercase. * * @param {String} input * @return {String} * @api private */ exports.lower = function(input) { if(utils.isIterable(input)) { var acc = {}; _.each(input, function (value, key) { acc[key] = exports.lower(value); }); return acc; } return input.toString().toLowerCase(); }; /** * Transform `input` to JSON. * * @param {Mixed} input * @return {Mixed} * @api private */ exports.json = function(input) { return JSON.stringify(input); }; /** * Add `operand` to `input` and return the result. * * @param {Mixed} input * @param {Mixed} operand * @return {Mixed} * @api private */ exports.add = function(input, operand) { if(typeof input === 'object') { var acc = {}; _.each(input, function (value, key) { acc[key] = exports.add(value, operand); }); return acc; } return parseFloat(input) + parseFloat(operand); }; /** * Subtract `operand` from `input` and return the result. * * @param {Mixed} input * @param {Mixed} operand * @return {Mixed} * @api private */ exports.subtract = function(input, operand) { return exports.add(input, -parseInt(operand, 10)); }; /** * Multiply `operand` by `input` and return the result. * * @param {Mixed} input * @param {Mixed} operand * @return {Mixed} * @api private */ exports.mul = function(input, operand) { if(typeof input === 'object') { var acc = {}; _.each(input, function (value, key) { acc[key] = exports.mul(value, operand); }); return acc; } return parseFloat(input) * parseFloat(operand); }; /** * Divide `input` by `operand` and return the result. * * @param {Mixed} input * @param {Mixed} operand * @return {Mixed} * @api private */ exports.div = function(input, operand) { if(typeof input === 'object') { var acc = {}; _.each(input, function (value, key) { acc[key] = exports.div(value, operand); }); return acc; } return parseFloat(input) / parseFloat(operand); }; /** * Add 1 to `input` and return the result. * * @param {Mixed} input * @return {Mixed} * @api private */ exports.incr = function(input, operand) { return exports.add(input, 1); }; /** * Subtract 1 from `input` and return the result. * * @param {Mixed} input * @return {Mixed} * @api private */ exports.decr = function(input, operand) { return exports.add(input, -1); }; /** * Round `input` and return the result. * * @param {Mixed} input * @return {Mixed} * @api private */ exports.round = function(input) { if(typeof input === 'object') { var acc = {}; _.each(input, function (value, key) { acc[key] = exports.round(value); }); return acc; } return Math.round(input); }; /** * Calculate floor of `input` and return the result. * * @param {Mixed} input * @return {Mixed} * @api private */ exports.floor = function(input) { if(typeof input === 'object') { var acc = {}; _.each(input, function (value, key) { acc[key] = exports.floor(value); }); return acc; } return Math.floor(input); }; /** * Calculate ceiling of `input` and return the result. * * @param {Mixed} input * @return {Mixed} * @api private */ exports.ceil = function(input) { if(typeof input === 'object') { var acc = {}; _.each(input, function (value, key) { acc[key] = exports.ceil(value); }); return acc; } return Math.ceil(input); }; /** * Remove all occurences of `needle` in `input`. * * @param {String} input * @param {String} needle * @return {String} * @api private */ exports.cut = function(input, needle) { if(typeof input === 'object') { var acc = {}; _.each(input, function (value, key) { acc[key] = exports.cut(value, needle); }); return acc; } return input.toString().replace(needle, ''); }; /** * Slash `input`. * * @param {Mixed} input * @return {Mixed} * @api private */ exports.addslashes = function(input) { if(utils.isIterable(input)) { var acc = {}; _.each(input, function (value, key) { acc[key] = exports.capitalize(value); }); return acc; } return input.toString() .replace(/\\/g, '\\\\') .replace(/\'/g, "\\'") .replace(/\"/g, '\\"') .replace(/\0/g,'\\0'); }; /** * Unslash `input`. * * @param {Mixed} input * @return {Mixed} * @api private */ exports.stripslashes = function(input) { if(utils.isIterable(input)) { var acc = {}; _.each(input, function (value, key) { acc[key] = exports.capitalize(value); }); return acc; } return input.toString() .replace(/\\'/g,'\'') .replace(/\\"/g,'"') .replace(/\\0/g,'\0') .replace(/\\\\/g,'\\'); }; /** * Return first element from `input`. * * @param {Mixed} input * @return {Mixed} * @api private */ exports.first = function (input) { if(typeof input === 'object' && !_.isArray(input)) return ''; if(_.isString(input)) return input.substr(0, 1); return _.first(input); }; /** * Return last element from `input`. * * @param {Mixed} input * @return {Mixed} * @api private */ exports.last = function (input) { if(typeof input === 'object' && !_.isArray(input)) return ''; if(_.isString(input)) return input.slice(-1); return _.last(input); }; /** * Return length of `input`. * * @param {Mixed} input * @return {Integer} * @api private */ exports.length = function (input) { if(typeof input === 'object') return _.keys(input).length; return input.length; }; /** * Reverse `input`. * * @param {Mixed} input * @return {Integer} * @api private */ exports.reverse = function (input) { if(_.isArray(input)) return input.reverse(); return input; }; /** * Join `input` with `separator`. * * @param {Mixed} input * @param {String} separator * @return {Mixed} * @api private */ exports.join = function (input, separator) { if(_.isArray(input)) return input.join(separator); if(typeof input === 'object') { var acc = []; _.each(input, function (value, key) { acc.push(value); }); return acc.join(separator); } return input; }; /** * Encode `input` to URL component. * * @param {Mixed} input * @return {Mixed} * @api private */ exports.urlencode = function(input) { return encodeURIComponent(input); }; /** * Decode `input` from URL component. * * @param {Mixed} input * @return {Mixed} * @api private */ exports.urldecode = function(input) { return decodeURIComponent(input); }; /** * Replace `search` by `replacement` in `input`, with `flags`. * * @param {String} input * @param {String} replacement * @param {String} [flags] * @return {String} * @api private */ exports.replace = function(input, search, replacement, flags) { return input.replace(new RegExp(search, flags), replacement); }; /** * Module exports */ module.exports = exports; }); // module: filters/base.js require.register("filters/datetime.js", function(module, exports, require){ /*! * Coolony's Kiwi * Copyright ©2012 Pierre Matri <pierre.matri@coolony.com> * MIT Licensed */ /** * Module dependencies */ /** * Return humanized difference between now and `input`. * * @param {Mixed} input * @return {String} * @api private */ exports.timeago = function(input) { return moment(input).fromNow(); }; /** * Return calendar time for `input`, relative to now. * * @param {Mixed} input * @return {String} * @api private */ exports.relativedate = function(input) { return moment(input).calendar(); }; /** * Return formatted `input` with `pattern`. * * @param {Mixed} input * @param {String} pattern * @return {String} * @api private */ exports.date = function(input, pattern) { return moment(input).format(pattern); }; /** * Module exports */ module.exports = exports; }); // module: filters/datetime.js require.register("kiwi.js", function(module, exports, require){ /*! * Coolony's Kiwi * Copyright ©2012 Pierre Matri <pierre.matri@coolony.com> * MIT Licensed */ /** * Module dependencies */ var Template = exports.Template = require('./template'); /** * Exports */ exports.tools = require('./tools'); /** * Version */ exports.version = '0.2.2'; /** * Module exports */ module.exports = exports; }); // module: kiwi.js require.register("tags/as.js", function(module, exports, require){ /*! * Coolony's Kiwi * Copyright ©2012 Pierre Matri <pierre.matri@coolony.com> * MIT Licensed */ /** * Constants */ var AS_PARSE_RE = /^as\s+([^\s]+)$/; /** * Global variables */ module.exports.tags = {}; var asTag = module.exports.tags.as = {}; /** * Basic tag settings */ asTag.isBlock = true; /** * Compile `token` with `compiledContents` to JavaScript, and invoke * `callback(err, compiled)`. * * @param {BlockToken} token * @param {String} compiledContents * @param {Function} callback * @api private */ asTag.compile = function(token, compiledContents, compiledIntermediate, compiler, callback) { var parsed = token.tag.match(AS_PARSE_RE); if(!parsed) { return callback(new Error('Compilation error: Unable to parse tag `' + token.tag + '`.' )); } var name = parsed[1]; var compiled = '(function(parentAcc) {' + 'var __acc = [];' + compiledContents + 'var __joined = __acc.join("");' + '$data["' + name + '"] = $tools.tools.safe(__joined);' + '})(__acc);'; callback(null, compiled); }; }); // module: tags/as.js require.register("tags/block.js", function(module, exports, require){ /*! * Coolony's Kiwi * Copyright ©2012 Pierre Matri <pierre.matri@coolony.com> * MIT Licensed */ /** * Constants */ var BLOCK_PARSE_RE = /^block\s+([^\s]+)(?:\s+(append|prepend))?$/; /** * Global variables */ module.exports.tags = {}; var blockTag = module.exports.tags.block = {}; var parentTag = module.exports.tags.parent = {}; /** * Block tag */ /** * Basic tag settings */ blockTag.isBlock = true; /** * Compile `token` with `compiledContents` to JavaScript, and invoke * `callback(err, compiled)`. * * @param {BlockToken} token * @param {String} compiledContents * @param {Function} callback * @api private */ blockTag.compile = function(token, compiledContents, compiledIntermediate, compiler, callback) { var parsed = token.tag.match(BLOCK_PARSE_RE); if(!parsed) { return callback(new Error('Compilation error: Unable to parse tag `' + token.tag + '`.' )); } var name = parsed[1]; var mode = parsed[2]; //console.log(compiledContents); var compiled = '(function(__parentAcc) {' + 'var __acc = [];' + compiledContents + 'var __currentBlock = __acc.join("");' + 'if(!_.isUndefined(__blocks["' + name + '"])) {' + '__tmp = new String(__blocks["' + name + '"].replace(' + '/\\{\\{parent\\}\\}/g, __currentBlock' + '));' + 'if(__blocks["' + name + '"].mode) {' + '__tmp.mode = __blocks["' + name + '"].mode;' + '}' + '__blocks["' + name + '"] = __tmp;' + '}' + 'var __acc = [];' + 'if(_.isUndefined(__blocks["' + name + '"]) ||' + '__blocks["' + name + '"].mode) {' + 'if(__blocks["' + name + '"] &&' + '__blocks["' + name + '"].mode == "prepend") {' + '__acc.push(__blocks["' + name + '"]);' + '}' + '__acc.push(__currentBlock);' + 'if(__blocks["' + name + '"] &&' + '__blocks["' + name + '"].mode == "append") {' + '__acc.push(__blocks["' + name + '"]);' + '}' + '} else {' + '__acc.push(__blocks["' + name + '"]);' + '}' + 'var __joined = new String(__acc.join(""));' + (mode ? ('__joined.mode = "' + mode + '";') : '') + '__parentAcc.push(__joined);' + '__blocks["' + name + '"] = __joined;' + '})(__acc);'; callback(null, compiled); }; /** * Parent tag */ /** * Compile `token` with `compiledContents` to JavaScript, and invoke * `callback(err, compiled)`. * * @param {BlockToken} token * @param {String} compiledContents * @param {Function} callback * @api private */ parentTag.compile = function(token, compiledContents, compiler, callback) { if(!token.parent.tagType || token.parent.tagType.tagName !== 'block') { return callback(new Error( 'Compilation error: `parent` must be immediate child of a `block` tag.' )); } token.parent.hasParentTag = true; callback(null, '__acc.push("{{parent}}");'); }; }); // module: tags/block.js require.register("tags/comment.js", function(module, exports, require){ /*! * Coolony's Kiwi * Copyright ©2012 Pierre Matri <pierre.matri@coolony.com> * MIT Licensed */ /** * Constants */ var COMMENT_RE = /\{\{\s*comment\s*\}\}((.|\n)*?)\{\{\s*\/\s*comment\s*\}\}/g; /** * Global variables */ module.exports.tags = {}; var commentTag = module.exports.tags['#'] = {}; /** * Basic tag settings */ commentTag.isBlock = false; /** * Compile `token` with `compiledContents` to JavaScript, and invoke * `callback(err, compiled)`. * * @param {BlockToken} token * @param {String} compiledContents * @param {Function} callback * @api private */ commentTag.compile = function(token, compiledContents, compiler, callback) { callback(null, ''); }; /** * Before Processor. * Replace "{{verbatim}}…{{/verbatim}}" with a placeholder in `source`, and * invoke `callback(err, replaced)`. * * @param {String} source * @param {Function} callback * @api private */ commentTag.beforeProcessor = function(source, compiler, callback) { source = source.replace(COMMENT_RE, ''); callback(null, source); }; }); // module: tags/comment.js require.register("tags/each.js", function(module, exports, require){ /*! * Coolony's Kiwi * Copyright ©2012 Pierre Matri <pierre.matri@coolony.com> * MIT Licensed */ /** * Constants */ var EACH_PARSE_RE = /^each(?:\(([^\)\s]+)\s*\,\s*([^\)\s]+)\s*\))?\s+(.*)$/; /** * Global variables */ module.exports.tags = {}; var eachTag = module.exports.tags.each = {}; var intermediateTags = eachTag.intermediateTags = {}; var emptyTag = intermediateTags.empty = {}; /** * Basic tag settings */ eachTag.isBlock = true; eachTag.headDeclarations = 'var $each = undefined;'; /** * Compile `token` with `compiledContents` to JavaScript, and invoke * `callback(err, compiled)`. * * @param {BlockToken} token * @param {String} compiledContents * @param {Function} callback * @api private */ eachTag.compile = function(token, compiledContents, compiledIntermediate, compiler, callback) { var parsed = token.tag.match(EACH_PARSE_RE); if(!parsed) { return callback(new Error('Compilation error: Unable to parse tag `' + token.tag + '`.' )); } var elementVariable = parsed[1] || '$value'; var indexVariable = parsed[2] || '$index'; var collection = parsed[3]; var ifEmpty; // If we have an intermediate tag if(compiledIntermediate.length) { // Ensure there is only one tag if(compiledIntermediate.length > 1) { return callback(new Error('Compilation Error: Too many intermediate ' + 'tags for `each`.' )); } // Check tag if(token.intermediate[0].tag !== 'empty') { return callback(new Error('Compilation Error: Unexpected tag `' + token.intermediate[0].tag + '`.' )); } ifEmpty = compiledIntermediate[0]; } // Actually compile tag var compiled = '(function(__parentEachLoop) {'; // Add loop collection part if(!token.options.strict) { compiled += 'try {' + '__tmp = ' + collection + '} catch(__err) {' + 'if(__err instanceof ReferenceError) {' + '__tmp = [];' + '} else {' + 'throw __err;' + '}' + '}'; } else { compiled += '__tmp = ' + collection + ';'; } // Calculate loop collection length compiled += 'var __eachLoopLength = _.size(__tmp);' + 'var _eachLoop, $each;'; // Add the empty start part if(ifEmpty) compiled += 'if(__eachLoopLength) {'; // Call each if(token.options.eachCounters) { compiled += 'var __eachLoopCounter = 0;' + '_.each(__tmp, ' + 'function(' + elementVariable + ',' + indexVariable + '){' + '$each = _eachLoop = {' + 'size: __eachLoopLength,' + 'counter0: __eachLoopCounter,' + 'counter: __eachLoopCounter + 1,' + 'revcounter0: __eachLoopLength - __eachLoopCounter - 1,' + 'revcounter: __eachLoopLength - __eachLoopCounter,' + 'first: __eachLoopCounter === 0,' + 'last: __eachLoopCounter + 1 === __eachLoopLength,' + 'parentLoop: __parentEachLoop,' + 'parent: __parentEachLoop,' + '_index: ' + indexVariable + ',' + '_value: ' + elementVariable + '};' + 'if(__parentEachLoop) {' + '$each.parentIndex = __parentEachLoop._index;' + '$each.parentValue = __parentEachLoop._value;' + '}' + compiledContents + '__eachLoopCounter++;' + '});'; } else { compiled += '_.each(__tmp, ' + 'function(' + elementVariable + ',' + indexVariable + ') {' + compiledContents + '});'; } // Add the empty remaining part, if requested if(ifEmpty) { compiled += '} else {' + ifEmpty + '}'; } compiled += '})($each);'; callback(null, compiled); }; }); // module: tags/each.js require.register("tags/extend.js", function(module, exports, require){ /*! * Coolony's Kiwi * Copyright ©2012 Pierre Matri <pierre.matri@coolony.com> * MIT Licensed */ /** * Module dependencies */ var utils = require('../utils'); /** * Constants */ var EXTEND_PARSE_RE = /^extend\s+(.+)$/; var EXTEND_ARGS_SPLIT_RE = /\s+(?=(?:[^'"]|'[^']*'|"[^"]*")*$)/g; /** * Global variables */ module.exports.tags = {}; var extendTag = module.exports.tags.extend = {}; var helpers = module.exports.helpers = {}; /** * Basic tag settings */ extendTag.isBlock = false; /** * Compile `token` with `compiledContents` to JavaScript, and invoke * `callback(err, compiled)`. * * @param {BlockToken} token * @param {String} compiledContents * @param {Function} callback * @api private */ extendTag.compile = function(token, compiledContents, compiler, callback) { if(token.root.children[0] !== token) { return callback(new Error('Compilation error: Extend tag must be defined ' + 'at the very beginning of the template.' )); } var parsed = token.tag.match(EXTEND_PARSE_RE); var parsedArgs = parsed ? parsed[1].split(EXTEND_ARGS_SPLIT_RE) : null; if(!parsed || !parsedArgs) { return callback(new Error('Compilation error: Unable to parse tag `' + token.tag + '`.' )); } var name = parsedArgs[0]; var compiledInclude = parsedArgs[1] ? ('_.extend($data, ' + parsedArgs.splice(1).join(' ') + ')') : '$data'; var compiled = 'var __originalCallback = $callback;' + '$callback = function(err, compiled) {' + '$helpers.extend(' + name + ', __compiled, $template,' + compiledInclude + ', __originalCallback);' + '};'; callback(null, compiled); }; /** * Helper function. * Make `compiled` `template` with `data` extend `name` template, and invoke * `callback(err, compiled)`. * * @param {String} name * @param {String} compiled * @param {Template} template * @param {Object} data * @param {Function} callback * @api private */ helpers.extend = function(name, compiled, template, data, callback) { function onRendered(err, rendered) { if(err) return callback(err); callback(null, rendered); } template._renderRelative(name, data, compiled, onRendered); }; }); // module: tags/extend.js require.register("tags/filter.js", function(module, exports, require){ /*! * Coolony's Kiwi * Copyright ©2012 Pierre Matri <pierre.matri@coolony.com> * MIT Licensed */ /** * Module dependencies */ var utils = require('../utils'); /** * Constants */ var FILTER_PARSE_RE = /^filter\s+(.+)$/; /** * Global variables */ module.exports.tags = {}; var filterTag = module.exports.tags.filter = {}; /** * Basic tag settings */ filterTag.isBlock = true; /** * Compile `token` with `compiledContents` to JavaScript, and invoke * `callback(err, compiled)`. * * @param {BlockToken} token * @param {String} compiledContents * @param {Function} callback * @api private */ filterTag.compile = function(token, compiledContents, compiledIntermediate, compiler, callback) { var parsed = token.tag.match(FILTER_PARSE_RE); if(!parsed) { return callback(new Error('Compilation error: Unable to parse tag `' + token.tag + '`.' )); } var filters; try { filters = utils.parseFilters(parsed[1]); } catch(err) { return callback(err); } var name = parsed[1]; var compiled = '(function(__parentAcc) {' + 'var __acc = [];' + compiledContents + '__parentAcc.push(' + '$tools.filter(' + '__acc.join(""),' + '[' + filters.join(',') + ']' + ')' + ');' + '})(__acc);'; callback(null, compiled); }; }); // module: tags/filter.js require.register("tags/if.js", function(module, exports, require){ /*! * Coolony's Kiwi * Copyright ©2012 Pierre Matri <pierre.matri@coolony.com> * MIT Licensed */ /** * Module dependencies */ /** * Constants */ var IF_PARSE_RE = /^if\s+(.*)$/; var ELSE_PARSE_RE = /^else(?:\s+(.*))?$/; /** * Global variables */ module.exports.tags = {}; var ifTag = module.exports.tags['if'] = {}; var intermediateTags = ifTag.intermediateTags = {}; var elseTag = intermediateTags['else'] = {}; /** * Global functions */ /** * Outputs `if` clause based on `condition`. If not `strict`, * actual test will be wrapped in a `try…catch` statement to catch * ReferenceErrors silently * * @param {String} condition * @param {Boolean} strict * @return {String} * @api private */ function createIfCondition(condition, strict) { var compiled; if(strict) { compiled = 'if(' + condition + ')'; } else { compiled = 'try {' + '__tmp = ' + condition + '} catch(__err) {' + 'if(__err instanceof ReferenceError) {' + '__tmp = false;' + '} else {' + 'throw __err;' + '}' + '}' + 'if(__tmp)'; } return compiled; } /** * Basic tag settings */ ifTag.isBlock = true; /** * Compile `token` with `compiledContents` to JavaScript, and invoke * `callback(err, compiled)`. * * @param {BlockToken} token * @param {String} compiledContents * @param {Function} callback * @api private */ ifTag.compile = function(token, compiledContents, compiledIntermediate, compiler, callback) { var parsed = token.tag.match(IF_PARSE_RE); if(!parsed) { return callback(new Error('Compilation error: Unable to parse tag `' + token.tag + '`.' )); } var condition = parsed[1]; var appendEnd = ['}']; // Handle basic if var compiled = createIfCondition(condition, token.options.strict); compiled += '{' + compiledContents; // Handle else var err; _.each(token.intermediate, function(intermediate, index) { var intermediateTag = intermediate.tag; var compiledIntermediateTag = compiledIntermediate[index]; var parsed = intermediateTag.match(ELSE_PARSE_RE); if(!parsed) { err = new Error('Compilation error: Unable to parse tag `' + token.tag + '`.' ); return; } var condition = parsed[1]; if(!condition) { compiled += '} else {'; } else { appendEnd.push('}'); compiled += '} else {' + createIfCondition(condition, token.options.strict) + '{'; } compiled += compiledIntermediate[index]; }); // Handle error if(err) return callback(err); // Return compiled += appendEnd.join(''); callback(null, compiled); }; }); // module: tags/if.js require.register("tags/ifblock.js", function(module, exports, require){ /*! * Coolony's Kiwi * Copyright ©2012 Pierre Matri <pierre.matri@coolony.com> * MIT Licensed */ /** * Constants */ var IFBLOCK_PARSE_RE = /^ifblock\s+([^\s]+)$/; /** * Global variables */ module.exports.tags = {}; var ifBlockTag = module.exports.tags.ifblock = {}; /** * Basic tag settings */ ifBlockTag.isBlock = true; /** * Compile `token` with `compiledContents` to JavaScript, and invoke * `callback(err, compiled)`. * * @param {BlockToken} token * @param {String} compiledContents * @param {Function} callback * @api private */ ifBlockTag.compile = function(token, compiledContents, compiledIntermediate, compiler, callback) { var parsed = token.tag.match(IFBLOCK_PARSE_RE); if(!parsed) { return callback(new Error('Compilation error: Unable to parse tag `' + token.tag + '`.' )); } var name = parsed[1]; var compiled = 'if(!_.isUndefined(__blocks["' + name + '"])) {' + compiledContents + '}'; callback(null, compiled); }; }); // module: tags/ifblock.js require.register("tags/include.js", function(module, exports, require){ /*! * Coolony's Kiwi * Copyright ©2012 Pierre Matri <pierre.matri@coolony.com> * MIT Licensed */ /** * Module dependencies */ var utils = require('../utils'); /** * Constants */ var INCLUDE_PARSE_RE = /^include\s+(.+)?$/; var INCLUDE_ARGS_SPLIT_RE = /\s+(?=(?:[^'"]|'[^']*'|"[^"]*")*$)/g; /** * Global variables */ module.exports.tags = {}; var includeTag = module.exports.tags.include = {}; var helpers = module.exports.helpers = {}; /** * Basic tag settings */ includeTag.isBlock = false; /** * Compile `token` with `compiledContents` to JavaScript, and invoke * `callback(err, compiled)`. * * @param {BlockToken} token * @param {String} compiledContents * @param {Function} callback * @api private */ includeTag.compile = function(token, compiledContents, compiler, callback) { var parsed = token.tag.match(INCLUDE_PARSE_RE); var parsedArgs = parsed ? parsed[1].split(INCLUDE_ARGS_SPLIT_RE) : null; if(!parsed || !parsedArgs) { return callback(new Error('Compilation error: Unable to parse tag `' + token.tag + '`.' )); } var name = parsedArgs[0]; var compiledInclude = parsedArgs[1] ? ('_.extend($data, ' + parsedArgs.splice(1).join(' ') + ')') : '$data'; compiler.__compilationEnd.unshift('});'); var compiled = '$helpers.include(' + name + ', $template,' + compiledInclude + ', ' + 'function(err, rendered) {' + '__acc.push(rendered);'; callback(null, compiled); }; /** * Helper function. * Make `compiled` `template` with `data` extend `name` template, and invoke * `callback(err, compiled)`. * * @param {String} name * @param {String} compiled * @param {Template} template * @param {Object} data * @param {Function} callback * @api private */ helpers.include = function(name, template, data, callback) { function onRendered(err, rendered) { if(err) return callback(err); callback(null, rendered); } template._renderRelative(name, data, null, onRendered); }; }); // module: tags/include.js require.register("tags/print.js", function(module, exports, require){ /*! * Coolony's Kiwi * Copyright ©2012 Pierre Matri <pierre.matri@coolony.com> * MIT Licensed */ /** * Module dependencies */ var filter = require('../filter'); var utils = require('../utils'); /** * Constants */ var TAG_REPLACE_RE = /\$\{([^\}]*)\}/g; var TAG_PARSE_RE = /^(?:=|html)\s+(?:\:(\d+)\s+)?([^|]+)(?:\|(.*))?$/; var DEFAULT_VARIABLE_FILTERS = ['escapeIfUnsafe'].map(JSON.stringify); var DEFAULT_HTML_FILTERS = []; /** * Global variables */ module.exports.tags = {}; var variableTag = module.exports.tags['='] = {}; var htmlTag = module.exports.tags['html'] = {}; /** * Basic tag settings */ variableTag.isBlock = false; htmlTag.isBlock = false; /** * Before Processor. * Replace "${…}" to "{{= …}}" in `source`, and invoke * `callback(err, replaced)`. * * @param {String} source * @param {Function} callback * @api private */ variableTag.beforeProcessor = function(source, compiler, callback) { compiler.__print = []; source = source.replace(TAG_REPLACE_RE, function(all, contents) { compiler.__print.push(all); return '{{= :' + compiler.__print.length + ' ' + contents + '}}'; }); callback(null, source); }; /** * Compilers. */ variableTag.compile = createPrintTagCompiler(DEFAULT_VARIABLE_FILTERS); htmlTag.compile = createPrintTagCompiler(DEFAULT_HTML_FILTERS); /** * Create print tag based on `defaultFilters`. * Return `function(token, compiledContents, compiler, callback)`. * * @param {Array} defaultFilters * @return {Function} * @api private */ function createPrintTagCompiler(defaultFilters) { var compiler = function(token, compiledContents, compiler, callback) { var parsed = token.tag.match(TAG_PARSE_RE); if(!parsed) { return callback(new Error('Compilation error: Unable to parse tag `' + token.tag + '`.' )); } // Handle decompiler if(parsed[1]) { var key = parseInt(parsed[1], 10) - 1; token.__originalTag = compiler.__print[key]; } var contents = parsed[2]; var filters; try { filters = parsed[3] ? utils.parseFilters(parsed[3], defaultFilters) : defaultFilters; } catch(err) { return callback(err); } contents = '$tools.filter(' + contents + ', ' + '[' + filters.join(',') + ']' + ')'; var ret; if(!token.options.strict) { ret = 'try {' + 'var __tmp = ' + contents + ';' + '} catch(__err) {' + 'if(__err instanceof ReferenceError) {' + '__tmp = "";' + '} else {' + 'throw __err;' + '}' + '}' + '__acc.push(__tmp);'; } else { ret = '__acc.push(' + contents + ');'; } callback(null, ret); }; compiler.untokenize = function(token, compiler) { return token.__originalTag ? token.__originalTag : ('{{' + token.tag + '}}'); }; return compiler; } }); // module: tags/print.js require.register("tags/raw.js", function(module, exports, require){ /*! * Coolony's Kiwi * Copyright ©2012 Pierre Matri <pierre.matri@coolony.com> * MIT Licensed */ /** * Module dependencies */ var utils = require('../utils'); /** * Constants * @see https://github.com/kof/node-jqtpl */ var VERBATIM_RE = new RegExp( '\\{\\{\\s*verbatim\\s*\\}\\}((?:.|\\n)*?)' + '\\{\\{\\s*\\/\\s*verbatim\\s*\\}\\}|' + '{\\{\\s*raw\\s*\\}\\}((?:.|\\n)*?)\\{\\{\\s*\\/\\s*raw\\s*\\}\\}' , 'g'); /** * Global variables */ module.exports.tags = {}; var verbatimTag = module.exports.tags.verbatim = {}; /** * Basic tag settings */ verbatimTag.isBlock = false; /** * Compile `token` with `compiledContents` to JavaScript, and invoke * `callback(err, compiled)`. * * @param {BlockToken} token * @param {String} compiledContents * @param {Function} callback * @api private */ verbatimTag.compile = function(token, compiledContents, compiler, callback) { var key = parseInt(token.tag.split(' ')[1], 10) - 1; callback(null, '__acc.push("' + utils.escapeCompiledString(compiler.__verbatim[key]) + '");' ); }; /** * Before Processor. * Replace "{{verbatim}}…{{/verbatim}}" with a placeholder in `source`, and * invoke `callback(err, replaced)`. * * @param {String} source * @param {Function} callback * @api private */ verbatimTag.beforeProcessorPrepend = true; verbatimTag.beforeProcessor = function(source, compiler, callback) { compiler.__verbatim = []; source = source.replace(VERBATIM_RE, function onMatch(all, content1, content2) { compiler.__verbatim.push(content1 || content2); return '{{verbatim ' + compiler.__verbatim.length + '}}'; }); callback(null, source); }; }); // module: tags/raw.js require.register("tags/tmpl.js", function(module, exports, require){ /*! * Coolony's Kiwi * Copyright ©2012 Pierre Matri <pierre.matri@coolony.com> * MIT Licensed */ /** * Module dependencies */ var utils = require('../utils'); /** * Constants */ var TAG_PARSE_RE = /^tmpl\s+(.*)$/; /** * Global variables */ module.exports.tags = {}; var tmplTag = module.exports.tags.tmpl = {}; /** * Basic tag settings */ tmplTag.isBlock = false; /** * Compile `token` with `compiledContents` to JavaScript, and invoke * `callback(err, compiled)`. * * @param {BlockToken} token * @param {String} compiledContents * @param {Function} callback * @api private */ tmplTag.compile = function(token, compiledContents, compiler, callback) { var parsed = token.tag.match(TAG_PARSE_RE); if(!parsed) { return callback(new Error('Compilation error: Unable to parse tag `' + token.tag + '`.' )); } var nested = parsed[1]; compiler.__compilationEnd.unshift('});'); callback(null, '$template._nest(' + nested + ', $data, ' + 'function(err, rendered){' + '__acc.push(rendered);' ); }; }); // module: tags/tmpl.js require.register("template.js", function(module, exports, require){ /*! * Coolony's Kiwi * Copyright ©2012 Pierre Matri <pierre.matri@coolony.com> * MIT Licensed */ /** * Module dependencies */ var Cache = require('./cache'); var CappedCache = Cache; var utils = require('./utils'); var token = require('./token'); var tools = require('./tools'); var Compiler = require('./compiler'); var filter = require('./filter'); /** * Constants */ var DEFAULTS = { lookup: utils.lookupTemplate, load: utils.loadTemplate, path: null, cache: true, cacheHandler: Cache, cacheOptions: [], lookupPaths: [], cacheTmplHandler: CappedCache, cacheTmplOptions: [1000], useIsolatedTmplCache: true, cacheContext: null, strict: true, eachCounters: true, _parent: null, _cacheAttachment: '_cache', _cacheTmplAttachment: '_nestCache' }; var TEMPLATE_EXPORTS = {filter: filter, utils: utils, tools: tools}; /** * Initializes `Template` with optionnally the given `str` and * `options`. * * @param {String} [str] * @param {Object} [options] * @api public */ function Template(str, options) { // Handle the case where the only argument passed is the `options` object if(_.isObject(str) && !options){ options = str; str = null; } // Create options if not provided options = options ? _.clone(options) : {}; // Set default cache behavior // Merges given `options` with `DEFAULTS` options = _.defaults(options, DEFAULTS); options.cacheContext = options.cacheContext || Template;