(function(f){if(typeof exports==="object"&&typeof module!=="undefined"){module.exports=f()}else if(typeof define==="function"&&define.amd){define([],f)}else{var g;if(typeof window!=="undefined"){g=window}else if(typeof global!=="undefined"){g=global}else if(typeof self!=="undefined"){g=self}else{g=this}g.TinyLiquid = f()}})(function(){var define,module,exports;return (function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o<r.length;o++)s(r[o]);return s})({1:[function(require,module,exports){ /** * Context Object * * @author Zongmin Lei<leizongmin@gmail.com> */ var utils = require('./utils'); var parser = require('./parser'); var filters = require('./filters'); var vm = require('./vm'); var OPCODE = require('./opcode'); var debug = utils.debug('Context'); var merge = utils.merge; /** * Context * * @param {Object} options * - {Object} filters * - {Object} asyncFilters * - {Object} locals * - {Object} syncLocals * - {Object} asyncLocals * - {Object} blocks * - {Boolean} isLayout default:false * - {Integer} timeout unit:ms, default:120000 * - {Object} parent */ var Context = module.exports = exports = function (options) { options = options || {}; this._locals = {}; this._syncLocals = {}; this._asyncLocals = {}; this._asyncLocals2 = []; this._filters = merge(filters, options.filters); this._asyncFilters = {}; this._cycles = {}; this._buffer = ''; this._forloops = []; this._isInForloop = false; this._tablerowloops = []; this._isInTablerowloop = false; this._includeFileHandler = null; this._position = {line: 0, column: 0}; this._astCache = {}; this._filenameStack = []; this._filterCache = {}; this._blocks = {}; this._isLayout = !!options.isLayout; // default configuration options = merge({ timeout: 120000 }, options); this.options = options; // parent this._parent = options.parent || null; // initialize the configuration var me = this; var set = function (name) { if (options[name] && typeof(options[name]) === 'object') { Object.keys(options[name]).forEach(function (i) { me['_' + name][i] = options[name][i]; }); } }; set('locals'); set('syncLocals'); set('asyncLocals'); set('asyncLocals2'); set('filters'); set('blocks'); if (options.asyncFilters && typeof options.asyncFilters === 'object') { Object.keys(options.asyncFilters).forEach(function (i) { me.setAsyncFilter(i, options.asyncFilters[i]); }); } }; /** * Copy the configuration from other context object * * @param {Object} from * @return {Object} */ Context.prototype.from = function (from) { var me = this; var set = function (name) { if (from[name] && typeof(from[name]) === 'object') { for (var i in from[name]) { if (i in me[name]) continue; me[name][i] = from[name][i]; } } else if (typeof(from[name] === 'function')) { if (!me[name]) { me[name] = from[name]; } } } set('_locals'); set('_syncLocals'); set('_asyncLocals'); set('_asyncLocals2'); set('_filters'); set('_asyncFilters'); set('options'); set('_onErrorHandler'); set('_includeFileHandler'); set('_filterCache'); set('_blocks'); if (Array.isArray(from._filenameStack)) { me._filenameStack = from._filenameStack.slice(); } for (var i in from) { if (i in me) continue; me[i] = from[i]; } me._isInForloop = from._isInForloop; me._forloops = from._forloops.slice(); me._isInTablerowloop = from._isInTablerowloop; me._tablerowloops = from._tablerowloops; me._isLayout = from._isLayout; return this; }; /* constants */ Context.prototype.STATIC_LOCALS = 0; // normal locals Context.prototype.SYNC_LOCALS = 1; // get value from a sync function Context.prototype.ASYNC_LOCALS = 2; // get value from a async function Context.prototype.SYNC_FILTER = 0; // normal filter Context.prototype.ASYNC_FILTER = 1; // async filter /** * Set Timeout * * @param {Integer} ms */ Context.prototype.setTimeout = function (ms) { ms = parseInt(ms, 10); if (ms > 0) this.options.timeout = ms; }; /** * Run AST * * @param {Array} astList * @param {Function} callback */ Context.prototype.run = function (astList, callback) { return vm.run(astList, this, callback); }; /** * Register normal locals * * @param {String} name * @param {Function} val */ Context.prototype.setLocals = function (name, val) { this._locals[name] = val; if (this._parent) { this._parent.setLocals(name, val); } }; /** * Register sync locals * * @param {String} name * @param {Function} val */ Context.prototype.setSyncLocals = function (name, fn) { this._syncLocals[name] = fn; }; /** * Register async locals * * @param {String} name * @param {Function} fn */ Context.prototype.setAsyncLocals = function (name, fn) { if (name instanceof RegExp) { var name2 = name.toString(); // remove the same name for (var i = 0, len = this._asyncLocals2; i < len; i++) { var item = this._asyncLocals2[i]; if (item[0].toString() === name2) { this._asyncLocals2.splice(i, 1); break; } } this._asyncLocals2.push([name, fn]); } else { this._asyncLocals[name] = fn; } }; /** * Register normal filter * * @param {String} name * @param {Function} fn */ Context.prototype.setFilter = function (name, fn) { this._filters[name.trim()] = fn; }; /** * Register async filter * * @param {String} name * @param {Function} fn */ Context.prototype.setAsyncFilter = function (name, fn) { if (fn.enableCache) fn = utils.wrapFilterCache(name, fn); this._asyncFilters[name.trim()] = fn; }; /** * Set layout file * * @param {String} filename */ Context.prototype.setLayout = function (filename) { this._layout = filename; }; /** * Set block * * @param {String} name * @param {String} buf */ Context.prototype.setBlock = function (name, buf) { this._blocks[name] = buf; if (this._parent) { this._parent.setBlock(name, buf); } }; /** * Set block if empty * * @param {String} name * @param {String} buf */ Context.prototype.setBlockIfEmpty = function (name, buf) { if (!(name in this._blocks)) { this._blocks[name] = buf; } }; /** * Get block * * @param {String} name * @return {String} */ Context.prototype.getBlock = function (name) { return this._blocks[name] || null; }; /** * Get locals * * @param {String} name * @return {Array} [type, value, isAllowCache] return null if the locals not found */ Context.prototype.getLocals = function (name) { if (name in this._locals) return [this.STATIC_LOCALS, this._locals[name]]; if (name in this._syncLocals) return [this.SYNC_LOCALS, this._syncLocals[name], true]; if (name in this._asyncLocals) return [this.ASYNC_LOCALS, this._asyncLocals[name], true]; for (var i = 0, len = this._asyncLocals2.length; i < len; i++) { var item = this._asyncLocals2[i]; if (item[0].test(name)) { return [this.ASYNC_LOCALS, item[1], true]; } } return null; }; /** * Fetch Single Locals * * @param {String} name * @param {Function} callback */ Context.prototype.fetchSingleLocals = function (name, callback) { var me = this; var info = me.getLocals(name); if (!info) return callback(null, info); switch (info[0]) { case me.STATIC_LOCALS: callback(null, info[1]); break; case me.SYNC_LOCALS: var v = info[1](name, me); if (info[2]) me.setLocals(name, v); callback(null, v); break; case me.ASYNC_LOCALS: info[1](name, function (err, v) { if (err) return callback(err); if (info[2]) me.setLocals(name, v); callback(null, v); }, me); break; default: callback(me.throwLocalsUndefinedError(name)); } }; /** * Fetch locals * * @param {Array|String} list * @param {Function} callback */ Context.prototype.fetchLocals = function (list, callback) { var me = this; if (Array.isArray(list)) { var values = []; utils.asyncEach(list, function (name, i, done) { me.fetchSingleLocals(name, function (err, val) { if (err) { values[i] = err; } else { values[i] = val; } done(); }); }, callback, null, values); } else { me.fetchSingleLocals(list, callback); } }; /** * Get filter * * @param {String} name * @return {Array} [type, function] return null if the filter not found */ Context.prototype.getFilter = function (name) { name = name.trim(); if (name in this._filters) return [this.SYNC_FILTER, this._filters[name]]; if (name in this._asyncFilters) return [this.ASYNC_FILTER, this._asyncFilters[name]]; return null; }; /** * Call filter * * @param {String} method * @param {Array} args * @param {Function} callback */ Context.prototype.callFilter = function (method, args, callback) { if (arguments.length < 3) { callback = args; args = []; } var info = this.getFilter(method); if (!info) return callback(this.throwFilterUndefinedError(method)); if (info[0] === this.ASYNC_FILTER) { args.push(callback); args.push(this); info[1].apply(null, args); } else { args.push(this); callback(null, info[1].apply(null, args)); } }; /** * Print HTML * * @param {Object} str */ Context.prototype.print = function (str) { this._buffer += (str === null || typeof(str) === 'undefined') ? '' : str; }; /** * Set buffer * * @param {String} buf */ Context.prototype.setBuffer = function (buf) { this._buffer = buf; }; /** * Get buffer * * @return {String} */ Context.prototype.getBuffer = function () { return this._buffer; }; /** * Clear buffer * * @return {String} */ Context.prototype.clearBuffer = function () { var buf = this.getBuffer(); this.setBuffer(''); return buf; }; /** * Set cycle * * @param {String} name * @param {Array} list */ Context.prototype.setCycle = function (name, list) { this._cycles[name] = {index: 0, length: list.length, list: list}; }; /** * Get the index of the cycle * * @param {String} name * @return {Integer} */ Context.prototype.getCycleIndex = function (name) { var cycle = this._cycles[name]; if (cycle) { cycle.index++; if (cycle.index >= cycle.length) cycle.index = 0; return cycle.index; } else { return null; } }; /** * Enter a forloop * * @param {Integer} length * @param {String} itemName */ Context.prototype.forloopEnter = function (length, itemName) { this._forloops.push({ length: length, itemName: itemName }); this._isInForloop = true; }; /** * Set the forloop item value * * @param {Object} item * @param {Integer} index */ Context.prototype.forloopItem = function (item, index) { var loop = this._forloops[this._forloops.length - 1]; loop.item = item; loop.index = index; }; /** * Set the forloop information * * @return {Object} */ Context.prototype.forloopInfo = function () { return this._forloops[this._forloops.length - 1]; }; /** * Exit the current forloop */ Context.prototype.forloopEnd = function () { this._forloops.pop(); if (this._forloops.length < 1) { this._isInForloop = false; } }; /** * Enter a tablerowloop * * @param {Integer} length * @param {String} itemName * @param {Integer} columns */ Context.prototype.tablerowloopEnter = function (length, itemName, columns) { this._tablerowloops.push({ length: length, itemName: itemName, columns: columns }); this._isInTablerowloop = true; }; /** * Set the tablerowloop item value * * @param {Object} item * @param {Integer} index * @param {Integer} colIndex */ Context.prototype.tablerowloopItem = function (item, index, colIndex) { var loop = this._tablerowloops[this._tablerowloops.length - 1]; loop.item = item; loop.index = index; loop.colIndex = colIndex; }; /** * Get the tablerow information * * @return {Object} */ Context.prototype.tablerowloopInfo = function () { return this._tablerowloops[this._tablerowloops.length - 1]; }; /** * Exit the current tablerowloop */ Context.prototype.tablerowloopEnd = function () { this._tablerowloops.pop(); if (this._tablerowloops.length < 1) { this._isInTablerowloop = false; } }; /** * Include a template file * * @param {String} name * @param {Array} localsAst * @param {Array} headerAst * @param {Function} callback */ Context.prototype.include = function (name, localsAst, headerAst, callback) { if (typeof headerAst === 'function') { callback = headerAst; headerAst = null; } var me = this; if (typeof(this._includeFileHandler) === 'function') { this._includeFileHandler(name, function (err, astList) { if (err) return callback(err); // all include files run on new context var c = new Context({parent: me}); c.from(me); function start () { c.run(astList, function (err) { //console.log(err, c.getBuffer(), headerAst); me.print(c.clearBuffer()); callback(err); }); } if (headerAst && headerAst.length > 0) { astList = [me._position.line, me._position.column,OPCODE.LIST, headerAst, astList]; } if (localsAst) { me.run(localsAst, function (err, locals) { if (err) locals = {}; Object.keys(locals).forEach(function (n) { c._locals[n] = locals[n]; }); start(); }); } else { start(); } }); } else { return callback(new Error('please set an include file handler')); } }; /** * Extends layout * * @param {String} name * @param {Function} callback */ Context.prototype.extends = function (name, callback) { if (typeof(this._includeFileHandler) === 'function') { this._includeFileHandler(name, callback); } else { return callback(new Error('please set an include file handler')); } }; /** * Set the include file handler * * @param {Function} fn format: function (name, callback) * callback format: function (err, astList) */ Context.prototype.onInclude = function (fn) { this._includeFileHandler = fn; }; /** * Throw locals undefined error * * @param {String} name * @return {Object} */ Context.prototype.throwLocalsUndefinedError = function (name) { debug('Locals ' + name + ' is undefined'); return null; }; /** * Throw loop item undefined error * * @param {String} name * @return {Object} */ Context.prototype.throwLoopItemUndefinedError = function (name) { debug('Loop item ' + name + ' is undefined'); return null; }; /** * Throw forloop/tablerow locals undefined error * * @param {String} name * @return {Object} */ Context.prototype.throwLoopLocalsUndefinedError = function (name) { debug('Loop locals ' + name + ' is undefined'); return null; }; /** * Throw filter undefined error * * @param {String} name * @return {Object} */ Context.prototype.throwFilterUndefinedError = function (name) { var err = new Error('Filter ' + name + ' is undefined ' + this.getCurrentPosition(true)); err.code = 'UNDEFINED_FILTER'; err = this.wrapCurrentPosition(err); return err; } /** * Throw unknown opcode error * * @param {String} code * @return {Object} */ Context.prototype.throwUnknownOpcodeError = function (code) { var err = new Error('Unknown opcode ' + code + ' ' + this.getCurrentPosition(true)); err.code = 'UNKNOWN_OPCODE'; err = this.wrapCurrentPosition(err); return err; }; /** * Throw unknown tag error * * @param {String} name * @param {String} body * @return {Object} */ Context.prototype.throwUnknownTagError = function (name, body) { var err = new Error('Unknown tag "' + (name + ' ' + body).trim() + '" ' + this.getCurrentPosition(true)); err.code = 'UNKNOWN_TAG'; err = this.wrapCurrentPosition(err); return err; }; /** * Set current position * * @param {Integer} line * @param {Integer} column */ Context.prototype.setCurrentPosition = function (line, column) { this._position.line = line; this._position.column = column; }; /** * Get current position * * @param {Boolean} getString * @return {Object} */ Context.prototype.getCurrentPosition = function (getString) { if (getString) { return 'at line ' + this._position.line + ', column ' + this._position.column; } else { return this._position; } }; /** * Wrap current position on a error object * * @param {Object} err * @return {Object} */ Context.prototype.wrapCurrentPosition = function (err) { err = err || {}; err.line = this._position.line; err.column = this._position.column; return err; }; /** * Push Filename * * @param {String} filename * @return {String} */ Context.prototype.pushFilename = function (filename) { this._filenameStack.push(filename); return filename; }; /** * Pop Filename * * @return {String} */ Context.prototype.popFilename = function () { return this._filenameStack.pop(); }; /** * Get filename * * @return {String} */ Context.prototype.getFilename = function () { return this._filenameStack[this._filenameStack.length - 1]; }; },{"./filters":2,"./opcode":5,"./parser":6,"./utils":7,"./vm":8}],2:[function(require,module,exports){ 'use strict'; /** * Default Filters * * @author Zongmin Lei<leizongmin@gmail.com> */ /** * To string, if it's undefined or null, return an empty string * * @param {Object} text * @return {String} */ var toString = function (text) { return (text === null || typeof(text) === 'undefined') ? '' : String(text); }; /*---------------------------- HTML Filters ----------------------------------*/ /** * Generate <img> tag * * @param {String} url * @param {String} alt * @return {String} */ exports.img_tag = function (url, alt) { return '<img src="' + exports.escape(url) + '" alt="' + exports.escape(alt || '') + '">'; }; /** * Generate <script> tag * * @param {String} url * @return {String} */ exports.script_tag = function (url) { return '<script src="' + exports.escape(url) + '"></sc' + 'ript>'; }; /** * Generate <link> tag * * @param {String} url * @param {String} media * @return {String} */ exports.stylesheet_tag = function (url, media) { return '<link href="' + exports.escape(url) + '" rel="stylesheet" type="text/css" media="' + exports.escape(media || 'all') + '" />'; }; /** * Generate <a> tag * * @param {String} link * @param {String} url * @param {String} title * @return {String} */ exports.link_to = function (link, url, title) { return '<a href="' + exports.escape(url || '') + '" title="' + exports.escape(title || '') + '">' + exports.escape(link) + '</a>'; }; /*-----------------------------Math Filters-----------------------------------*/ /** * Add * * @param {Number} input * @param {Number} operand * @return {Number} */ exports.plus = function (input, operand) { input = Number(input) || 0; operand = Number(operand) || 0; return input + operand; }; /** * Subtract * * @param {Number} input * @param {Number} operand * @return {Number} */ exports.minus = function (input, operand) { input = Number(input) || 0; operand = Number(operand) || 0; return input - operand; }; /** * Multiply * * @param {Number} input * @param {Number} operand * @return {Number} */ exports.times = function (input, operand) { input = Number(input) || 0; operand = Number(operand) || 0; return input * operand; }; /** * Divide * * @param {Number} input * @param {Number} operand * @return {Number} */ exports.divided_by = function (input, operand) { input = Number(input) || 0; operand = Number(operand) || 0; return input / operand; }; /** * Round (specify how many places after the decimal) * * @param {Number} input * @param {Number} point * @return {Number} */ exports.round = function (input, point) { point = parseInt(point, 10) || 0; if (point < 1) return Math.round(input); var n = Math.pow(10, point); return Math.round(input * n) / n; }; /** * Round * * @param {Number} input * @return {Number} */ exports.integer = function (input) { return parseInt(input, 10) || 0; }; /** * Generate random number such that: m <= Number < n * * @param {Number} m * @param {Number} n * @return {Number} */ exports.random = function (m, n) { m = parseInt(m); n = parseInt(n); if (!isFinite(m)) return Math.random(); if (!isFinite(n)) { n = m; m = 0; } return Math.random() * (n - m) + m; }; /** * If input > 1 return singular, otherwise plural * * @param {Number} input * @param {String} singular * @param {String} plural * @return {String} */ exports.pluralize = function (input, singular, plural) { return Number(input) > 1 ? plural : singular; }; /*-------------------------- Date and Time filters ----------------------------*/ /** * Take the current time in milliseconds and add 0 * * @param {Number} input * @return {Number} */ exports.timestamp = function (input) { input = parseInt(input, 10) || 0; return new Date().getTime() + input; }; /** * Format date/time * see syntax reference: http://liquid.rubyforge.org/classes/Liquid/StandardFilters.html#M000012 * * @param {String} input * @param {String} format * @return {String} */ exports.date = function (input, format) { if (toString(input).toLowerCase() == 'now') { var time = new Date(); } else { var timestamp = parseInt(input, 10); if (timestamp == input) { var time = new Date(timestamp); } else { var time = new Date(input); } } if (!time || !isFinite(time.valueOf())) return 'Invalid Date'; if (!format) format = '%Y-%m-%j %H:%M:%S'; // example: ["Wed", "Apr", "11", "2012"] var dates = time.toDateString().split(/\s/); // example: ["Wednesday,", "April", "11,", "2012"] var dateS = time.toLocaleDateString().split(/\s/); // example: ["10", "37", "44", "GMT", "0800", "(中国标准时间)"] var times = time.toTimeString().split(/[\s:\+]/); var n2 = function (n) { return n < 10 ? '0' + n : n; }; var replace = { a: dates[0], // week day A: dateS[0], b: dates[1], // month B: dateS[1], c: time.toLocaleString(), d: dates[2], H: times[0], // 24 hour I: times[0] % 12, // 12 hour j: dates[2], // date m: n2(time.getMonth() + 1), // month M: times[1], // minute p: Number(times[0]) < 12 ? 'AM' : 'PM', S: times[2], // second U: weekNo(time), // start on Sunday W: weekNo(time, true), // start on Monday w: time.getDay(), // week day (0-6) x: time.toDateString(), X: time.toTimeString(), y: dates[3].substr(-2), // year Y: dates[3], Z: times[4] // time zone }; var ret = toString(format); for (var i in replace) { ret = ret.replace(new RegExp('%' + i, 'g'), replace[i]); } return ret; }; function weekNo (now, mondayFirst) { var totalDays = 0; var years = now.getFullYear(); var days = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]; if (years % 100 === 0) { if (years % 400 === 0) days[1] = 29; } else if (years % 4 === 0) { days[1] = 29; } if (now.getMonth() === 0) { totalDays = totalDays + now.getDate(); } else { var curMonth = now.getMonth(); for (var count = 1; count <= curMonth; count++) { totalDays = totalDays + days[count - 1]; } totalDays = totalDays + now.getDate(); } // default to start on Sunday var week = Math.round(totalDays / 7); if (mondayFirst && new Date(toString(years)).getDay() === 0) week += 1; return week; } /*---------------------------Strings Filters-----------------------------*/ /** * Append to the end of string * * @param {String} input * @param {String} characters * @return {String} */ exports.append = function (input, characters) { if (!characters) return toString(input); return toString(input) + toString(characters); }; /** * Prepend to the begining * * @param {String} input * @param {String} characters * @return {String} */ exports.prepend = function (input, characters) { if (!characters) return toString(input); return toString(characters) + toString(input); }; /** * Combine to one camelized name * * @param {String} input * @return {String} */ exports.camelize = function (input) { input = toString(input); return input.replace(/[^a-zA-Z0-9]+(\w)/g, function(_, ch) { return ch.toUpperCase(); }); }; /** * Combine to one capitalized name * * @param {String} input * @return {String} */ exports.capitalize = function (input) { input = toString(input); if (input.length < 1) return input; return input[0].toUpperCase() + input.substr(1); }; /** * To lowercase * * @param {String} input * @return {String} */ exports.downcase = function (input) { return toString(input).toLowerCase(); }; /** * To uppercase * * @param {String} input * @return {String} */ exports.upcase = function (input) { return toString(input).toUpperCase(); }; /** * Escape for use in HTML * * @param {String} input * @return {String} */ exports.escape = function (input) { return toString(input) .replace(/&(?!\w+;)/g, '&') .replace(/</g, '<') .replace(/>/g, '>') .replace(/"/g, '"'); }; /** * Unescape HTML * * @param {String} input * @return {String} */ exports.unescape = function (input) { return toString(input) .replace(/</g, '<') .replace(/>/g, '>') .replace(/"/g, '"') .replace(/&/g, '&'); }; /** * Combine to hyphen separated word: 'this-is-a-book' * * @param {String} input * @return {String} */ exports.handleize = function (input) { return toString(input).replace(/[^0-9a-zA-Z ]/g, '').replace(/[ ]+/g, '-').toLowerCase(); }; /** * Replace the first occurrence of substring with replacement * * @param {String} input * @param {String} substring * @param {String} replacement * @return {String} */ exports.replace_first = function (input, substring, replacement) { return toString(input).replace(substring, replacement); }; /** * Replace all occurrences of substring with replacement * * @param {String} input * @param {String} substring * @param {String} replacement * @return {String} */ exports.replace = function (input, substring, replacement) { input = toString(input); while (input.indexOf(substring) > -1) { input = input.replace(substring, replacement); } return input; }; /** * Remove all occurrences of substring * * @param {String} input * @param {String} substring * @return {String} */ exports.remove = function (input, substring) { return exports.replace(input, substring, ''); }; /** * Remove the first occurrence of substring * * @param {String} input * @param {String} substring * @return {String} */ exports.remove_first = function (input, substring) { return exports.replace_first(input, substring, ''); }; /** * Replace all newline characters with "<br>" * * @param {String} input * @return {String} */ exports.newline_to_br = function (input) { return toString(input).replace(/\n/img, '<br>'); }; /** * Split the string at each occurrence of '-' (returns an array) * * @param {String} input * @param {String} delimiter * @return {String} */ exports.split = function (input, delimiter) { if (!delimiter) delimiter = ''; return toString(input).split(delimiter); }; /** * Return the string length * * @param {array|string} input * @return {String} */ exports.size = function (input) { if (!input) return 0; var len = input.length; return len > 0 ? len : 0; }; /** * Remove all HTML tags * * @param {String} text * @return {String} */ exports.strip_html = function (text) { return toString(text).replace(/<[^>]*>/img, ''); }; /** * Remove all newline characters * * @param {String} input * @return {String} */ exports.strip_newlines = function (input) { return toString(input).replace(/[\r\n]+/g, ''); }; /** * Return only the first N characters * * @param {String} input * @param {Number} n * @return {String} */ exports.truncate = function (input, n) { n = parseInt(n, 10); if (!isFinite(n) || n < 0) n = 100; return toString(input).substr(0, n); }; /** * Return only the first N words * * @param {String} input * @param {Number} n * @return {String} */ exports.truncatewords = function (input, n) { n = parseInt(n, 10); if (!isFinite(n) || n < 0) n = 15; return toString(input).trim().split(/ +/).slice(0, n).join(' '); }; /** * Reverse the characters in the string * * @param {string|array} arr * @return {string|array} */ exports.reverse = function (arr) { return Array.isArray(arr) ? arr.reverse() : toString(arr).split('').reverse().join(''); }; /** * Extracts parts of a string, beginning at the character at the specified posistion 'start', * and returns the specified number of characters 'length'. * * @param {String} input * @param {Number} start * @param {Number} length * @return {String} */ exports.substr = function (input, start, length) { return toString(input).substr(start, length); }; /** * Search a substring, return its index position * * @param {string|array} arr * @param {Object} searchvalue * @param {Number} fromindex * @return {Number} */ exports.indexOf = function (arr, searchvalue, fromindex) { if (!Array.isArray(arr)) arr = toString(arr); return arr.indexOf(searchvalue, fromindex); }; /** * If input is empty, default returns value, otherwise, the input. * Can be used with strings, arrays, and hashes. * * @param {string|array|object} input * @param {string|array|object} value * @return {string|array|object} */ exports.default = function(input, value) { return (input && input.length > 0) ? toString(input) : toString(value); }; /*----------------------- Arrays and Objects Filters -------------------------*/ function objectGetKeys (obj) { return ((obj && typeof obj === 'object') ? Object.keys(obj) : []); } function getFirstKey (obj) { if (Array.isArray(obj)) { return 0; } else { var keys = objectGetKeys(obj); return keys[0] || ''; } }; function getLastKey (obj) { if (Array.isArray(obj)) { return obj.length - 1; } else { var keys = objectGetKeys(obj); return keys.pop() || ''; } }; /** * Return an array of the object's keys * * @param {Object} input * @return {Array} */ exports.keys = function (input) { try { return objectGetKeys(input); } catch (err) { return []; } }; /** * Return the first element of an array * * @param {Array} array * @return {Object} */ exports.first = function (array) { return array && array[getFirstKey(array)]; }; /** * Return the last element of an array * * @param {Array} array * @return {Object} */ exports.last = function (array) { return array && array[getLastKey(array)]; }; /** * Join the array's elements into a string * * @param {Array} input * @param {String} segmenter * @return {String} */ exports.join = function (input, segmenter) { if (!segmenter) segmenter = ' '; if (Array.isArray(input)) { return input.join(segmenter); } else { return ''; } }; /** * Return a JSON string of the object * * @param {Object} input * @return {String} */ exports.json = function (input) { try { var ret = JSON.stringify(input); } catch (err) { return '{}'; } return typeof ret !== 'string' ? '{}' : ret; }; /** * Get an item of the Object by property name * * @param {Object} obj * @param {String} prop * @return {Object} */ exports.get = function(obj, prop){ if (!obj) obj = {}; return obj[prop]; }; /** * Take the specified property of each element in the array, returning a new array * * @param {Array} arr * @param {String} prop * @return {Array} */ exports.map = function (arr, prop) { if (!Array.isArray(arr)) return []; return arr.map(function(obj){ return obj && obj[prop]; }); }; /** * Sort the array's elements by asc or desc order * * @param {Array} arr * @param {Number} order * @return {Array} */ exports.sort = function (arr, order) { if (!Array.isArray(arr)) return []; order = toString(order).trim().toLowerCase(); var ret1 = order === 'desc' ? -1 : 1; var ret2 = 0 - ret1; return arr.sort(function (a, b) { if (a > b) return ret1; if (a < b) return ret2; return 0; }); }; /** * Sort the array's elements by each element's specified property * * @param {Array} obj * @param {String} prop * @param {Number} order * @return {Array} */ exports.sort_by = function (obj, prop, order) { if (!Array.isArray(obj)) return []; order = toString(order).trim().toLowerCase(); var ret1 = order === 'desc' ? -1 : 1; var ret2 = 0 - ret1; return Object.create(obj).sort(function (a, b) { a = a[prop]; b = b[prop]; if (a > b) return ret1; if (a < b) return ret2; return 0; }); }; /*------------------------------- Other Filters ------------------------------*/ /** * Get page count of the items when paginated * * @param {Number} count * @param {Number} size * @param {Number} page * @listurn {Array} */ exports.pagination = function (count, size, page) { if (count % size === 0) { var maxPage = parseInt(count / size, 10); } else { var maxPage = parseInt(count / size, 10) + 1; } if (isNaN(page) || page < 1) { page = 1; } page = parseInt(page); var list = [page - 2, page - 1, page, page + 1, page + 2]; for (var i = 0; i < list.length;) { if (list[i] < 1 || list[i] > maxPage) { list.splice(i, 1); } else { i++; } } if (list[0] !== 1) { list.unshift('...'); list.unshift(1); } if (list[list.length - 1] < maxPage) { list.push('...'); list.push(maxPage); } var ret = { current: page, next: page + 1, previous: page - 1, list: list }; if (ret.next > maxPage) ret.next = maxPage; if (ret.previous < 1) ret.previous = 1; return ret; }; },{}],3:[function(require,module,exports){ /** * TinyLiquid * * @author Zongmin Lei<leizongmin@gmail.com> */ var packageInfo = require('../package.json'); var parser = require('./parser'); var vm = require('./vm'); var Context = require('./context'); var filters = require('./filters'); var utils = require('./utils'); var OPCODE = require('./opcode'); // TinyLiquid version exports.version = packageInfo.version; // AST parser exports.parser = parser; /** * Parse template * * @param {String} tpl * @param {Object} options * @return {Array} */ exports.parse = function (tpl, options) { return parser.apply(null, arguments); }; /** * Run AST code * * @param {Array} astList * @param {Object} context * @param {Function} callback */ exports.run = function (astList, context, callback) { if (arguments.length < 3) { var callback = arguments[arguments.length - 1]; var err = new Error('Not enough arguments.') if (typeof callback === 'function') { return callback(err); } else { throw err; } } // if astList is not an AST array, then parse it firstly if (!Array.isArray(astList)) { try { astList = exports.parse(astList); } catch (err) { return callback(err); } } // ensure that the callback function is called only once var originCallback = callback; var hasCallback = false; var callback = function (err) { if (hasCallback) { if (err) throw err; return; } hasCallback = true; clearTimeout(tid); originCallback.apply(null, arguments); }; // timeout if (context.options && context.options.timeout > 0) { var tid = setTimeout(function () { callback(new Error('Timeout.')); }, context.options.timeout); } // if it throws an error, catch it try { vm.run(astList, context, function (err, ret) { if (err) return callback(err); if (!context._layout) { return callback(err, ret); } // if layout was set, then render the layout template var c = exports.newContext(); c.from(context); c._isLayout = true; c.extends(c._layout, function (err, astList) { if (err) return callback(err); delete c._layout; vm.run(astList, c, function (err) { context.setBuffer(c.getBuffer()); callback(err); }); }); }); } catch (err) { return callback(err); } }; /** * Compile to a function * * @param {String} tpl * @param {Object} options * @return {Function} */ exports.compile = function (tpl, options) { var ast = exports.parse(tpl, options); return function (context, callback) { exports.run(ast, context, function (err) { callback(err, context.getBuffer()); }); }; }; // Context exports.Context = Context; /** * Create a new context * * @param {Object} options * @return {Object} */ exports.newContext = function (options) { return new Context(options); }; // Utils exports.utils = utils; // Default filters exports.filters = filters; // OPCODE define exports.OPCODE = OPCODE; /** * Insert filename * * @param {Array} astList * @param {String} filename * @return {Array} */ exports.insertFilename = function (astList, filename) { astList.unshift([0, 0, OPCODE.TEMPLATE_FILENAME_PUSH, filename]); astList.push([0, 0, OPCODE.TEMPLATE_FILENAME_POP]); return astList; }; },{"../package.json":10,"./context":1,"./filters":2,"./opcode":5,"./parser":6,"./utils":7,"./vm":8}],4:[function(require,module,exports){ /* * A JavaScript implementation of the RSA Data Security, Inc. MD5 Message * Digest Algorithm, as defined in RFC 1321. * Version 2.2 Copyright (C) Paul Johnston 1999 - 2009 * Other contributors: Greg Holt, Andrew Kepert, Ydnar, Lostinet * Distributed under the BSD License * See http://pajhome.org.uk/crypt/md5 for more info. */ var hexcase=0;function hex_md5(a){return rstr2hex(rstr_md5(str2rstr_utf8(a)))}function hex_hmac_md5(a,b){return rstr2hex(rstr_hmac_md5(str2rstr_utf8(a),str2rstr_utf8(b)))}function md5_vm_test(){return hex_md5("abc").toLowerCase()=="900150983cd24fb0d6963f7d28e17f72"}function rstr_md5(a){return binl2rstr(binl_md5(rstr2binl(a),a.length*8))}function rstr_hmac_md5(c,f){var e=rstr2binl(c);if(e.length>16){e=binl_md5(e,c.length*8)}var a=Array(16),d=Array(16);for(var b=0;b<16;b++){a[b]=e[b]^909522486;d[b]=e[b]^1549556828}var g=binl_md5(a.concat(rstr2binl(f)),512+f.length*8);return binl2rstr(binl_md5(d.concat(g),512+128))}function rstr2hex(c){try{hexcase}catch(g){hexcase=0}var f=hexcase?"0123456789ABCDEF":"0123456789abcdef";var b="";var a;for(var d=0;d<c.length;d++){a=c.charCodeAt(d);b+=f.charAt((a>>>4)&15)+f.charAt(a&15)}return b}function str2rstr_utf8(c){var b="";var d=-1;var a,e;while(++d<c.length){a=c.charCodeAt(d);e=d+1<c.length?c.charCodeAt(d+1):0;if(55296<=a&&a<=56319&&56320<=e&&e<=57343){a=65536+((a&1023)<<10)+(e&1023);d++}if(a<=127){b+=String.fromCharCode(a)}else{if(a<=2047){b+=String.fromCharCode(192|((a>>>6)&31),128|(a&63))}else{if(a<=65535){b+=String.fromCharCode(224|((a>>>12)&15),128|((a>>>6)&63),128|(a&63))}else{if(a<=2097151){b+=String.fromCharCode(240|((a>>>18)&7),128|((a>>>12)&63),128|((a>>>6)&63),128|(a&63))}}}}}return b}function rstr2binl(b){var a=Array(b.length>>2);for(var c=0;c<a.length;c++){a[c]=0}for(var c=0;c<b.length*8;c+=8){a[c>>5]|=(b.charCodeAt(c/8)&255)<<(c%32)}return a}function binl2rstr(b){var a="";for(var c=0;c<b.length*32;c+=8){a+=String.fromCharCode((b[c>>5]>>>(c%32))&255)}return a}function binl_md5(p,k){p[k>>5]|=128<<((k)%32);p[(((k+64)>>>9)<<4)+14]=k;var o=1732584193;var n=-271733879;var m=-1732584194;var l=271733878;for(var g=0;g<p.length;g+=16){var j=o;var h=n;var f=m;var e=l;o=md5_ff(o,n,m,l,p[g+0],7,-680876936);l=md5_ff(l,o,n,m,p[g+1],12,-389564586);m=md5_ff(m,l,o,n,p[g+2],17,606105819);n=md5_ff(n,m,l,o,p[g+3],22,-1044525330);o=md5_ff(o,n,m,l,p[g+4],7,-176418897);l=md5_ff(l,o,n,m,p[g+5],12,1200080426);m=md5_ff(m,l,o,n,p[g+6],17,-1473231341);n=md5_ff(n,m,l,o,p[g+7],22,-45705983);o=md5_ff(o,n,m,l,p[g+8],7,1770035416);l=md5_ff(l,o,n,m,p[g+9],12,-1958414417);m=md5_ff(m,l,o,n,p[g+10],17,-42063);n=md5_ff(n,m,l,o,p[g+11],22,-1990404162);o=md5_ff(o,n,m,l,p[g+12],7,1804603682);l=md5_ff(l,o,n,m,p[g+13],12,-40341101);m=md5_ff(m,l,o,n,p[g+14],17,-1502002290);n=md5_ff(n,m,l,o,p[g+15],22,1236535329);o=md5_gg(o,n,m,l,p[g+1],5,-165796510);l=md5_gg(l,o,n,m,p[g+6],9,-1069501632);m=md5_gg(m,l,o,n,p[g+11],14,643717713);n=md5_gg(n,m,l,o,p[g+0],20,-373897302);o=md5_gg(o,n,m,l,p[g+5],5,-701558691);l=md5_gg(l,o,n,m,p[g+10],9,38016083);m=md5_gg(m,l,o,n,p[g+15],14,-660478335);n=md5_gg(n,m,l,o,p[g+4],20,-405537848);o=md5_gg(o,n,m,l,p[g+9],5,568446438);l=md5_gg(l,o,n,m,p[g+14],9,-1019803690);m=md5_gg(m,l,o,n,p[g+3],14,-187363961);n=md5_gg(n,m,l,o,p[g+8],20,1163531501);o=md5_gg(o,n,m,l,p[g+13],5,-1444681467);l=md5_gg(l,o,n,m,p[g+2],9,-51403784);m=md5_gg(m,l,o,n,p[g+7],14,1735328473);n=md5_gg(n,m,l,o,p[g+12],20,-1926607734);o=md5_hh(o,n,m,l,p[g+5],4,-378558);l=md5_hh(l,o,n,m,p[g+8],11,-2022574463);m=md5_hh(m,l,o,n,p[g+11],16,1839030562);n=md5_hh(n,m,l,o,p[g+14],23,-35309556);o=md5_hh(o,n,m,l,p[g+1],4,-1530992060);l=md5_hh(l,o,n,m,p[g+4],11,1272893353);m=md5_hh(m,l,o,n,p[g+7],16,-155497632);n=md5_hh(n,m,l,o,p[g+10],23,-1094730640);o=md5_hh(o,n,m,l,p[g+13],4,681279174);l=md5_hh(l,o,n,m,p[g+0],11,-358537222);m=md5_hh(m,l,o,n,p[g+3],16,-722521979);n=md5_hh(n,m,l,o,p[g+6],23,76029189);o=md5_hh(o,n,m,l,p[g+9],4,-640364487);l=md5_hh(l,o,n,m,p[g+12],11,-421815835);m=md5_hh(m,l,o,n,p[g+15],16,530742520);n=md5_hh(n,m,l,o,p[g+2],23,-995338651);o=md5_ii(o,n,m,l,p[g+0],6,-198630844);l=md5_ii(l,o,n,m,p[g+7],10,1126891415);m=md5_ii(m,l,o,n,p[g+14],15,-1416354905);n=md5_ii(n,m,l,o,p[g+5],21,-57434055);o=md5_ii(o,n,m,l,p[g+12],6,1700485571);l=md5_ii(l,o,n,m,p[g+3],10,-1894986606);m=md5_ii(m,l,o,n,p[g+10],15,-1051523);n=md5_ii(n,m,l,o,p[g+1],21,-2054922799);o=md5_ii(o,n,m,l,p[g+8],6,1873313359);l=md5_ii(l,o,n,m,p[g+15],10,-30611744);m=md5_ii(m,l,o,n,p[g+6],15,-1560198380);n=md5_ii(n,m,l,o,p[g+13],21,1309151649);o=md5_ii(o,n,m,l,p[g+4],6,-145523070);l=md5_ii(l,o,n,m,p[g+11],10,-1120210379);m=md5_ii(m,l,o,n,p[g+2],15,718787259);n=md5_ii(n,m,l,o,p[g+9],21,-343485551);o=safe_add(o,j);n=safe_add(n,h);m=safe_add(m,f);l=safe_add(l,e)}return Array(o,n,m,l)}function md5_cmn(h,e,d,c,g,f){return safe_add(bit_rol(safe_add(safe_add(e,h),safe_add(c,f)),g),d)}function md5_ff(g,f,k,j,e,i,h){return md5_cmn((f&k)|((~f)&j),g,f,e,i,h)}function md5_gg(g,f,k,j,e,i,h){return md5_cmn((f&j)|(k&(~j)),g,f,e,i,h)}function md5_hh(g,f,k,j,e,i,h){return md5_cmn(f^k^j,g,f,e,i,h)}function md5_ii(g,f,k,j,e,i,h){return md5_cmn(k^(f|(~j)),g,f,e,i,h)}function safe_add(a,d){var c=(a&65535)+(d&65535);var b=(a>>16)+(d>>16)+(c>>16);return(b<<16)|(c&65535)}function bit_rol(a,b){return(a<<b)|(a>>>(32-b))}; module.exports = hex_md5; },{}],5:[function(require,module,exports){ /** * Define OPCODE * * @author Zongmin Lei<leizongmin@gmail.com> */ var OPCODE = { // unknown opcode UNKNOWN: 0, // base opcode AND: 1, ASSIGN: 2, CAPTURE: 3, CASE: 4, COMMENT: 5, COMPILER_VERSION: 6, CONTAINS: 7, CYCLE: 8, DEBUG: 9, ED: 10, EQ: 11, EXISTS: 12, FILTER: 13, FOR: 14, FORLOOPITEM: 15, FORLOOPLOCALS: 16, GE: 17, GT: 18, HASKEY: 19, HASVALUE: 20, IF: 21, INCLUDE: 22, LE: 23, LIST: 24, LOCALS: 25, LT: 26, NE: 27, NOT: 28, OBJECT: 29, OR: 30, RANGE: 31, PRINT: 32, PRINTLOCALS: 33, PRINTSTRING: 34, TABLEROW: 35, TABLEROWITEM: 36, TABLEROWLOOPLOCALS: 37, UNKNOWN_TAG: 38, WHEN: 39, // forloop/tablerow attribute LOOPLOCALS_LENGTH: 50, LOOPLOCALS_NAME: 51, LOOPLOCALS_INDEX0: 52, LOOPLOCALS_INDEX: 53, LOOPLOCALS_RINDEX0: 54, LOOPLOCALS_RINDEX: 55, LOOPLOCALS_FIRST: 56, LOOPLOCALS_LAST: 57, LOOPLOCALS_COL0: 58, LOOPLOCALS_COL: 59, LOOPLOCALS_COL_FIRST: 60, LOOPLOCALS_COL_LAST: 61, LOOPLOCALS_UNKNOWN: 62, // extension instruction TEMPLATE_FILENAME_PUSH: 80, TEMPLATE_FILENAME_POP: 81, // this "assign" will only affected current context WEAK_ASSIGN: 82, // extends and block EXTENDS: 83, BLOCK: 84 }; module.exports = exports = OPCODE; // just for test // for (var i in OPCODE) OPCODE[i] = i; },{}],6:[function(require,module,exports){ /** * Parse template * * @author Zongmin Lei<leizongmin@gmail.com> */ var utils = require('./utils'); var OPCODE = require('./opcode'); var merge = utils.merge; var ASTStack = utils.ASTStack; var localsAstNode = utils.localsAstNode; var isQuoteWrapString = utils.isQuoteWrapString; var textIndexOf = utils.textIndexOf; var splitText = utils.splitText; var stripQuoteWrap = utils.stripQuoteWrap; var jsonStringify = utils.jsonStringify; var md5 = utils.md5; var arrayRemoveEmptyString = utils.arrayRemoveEmptyString; var genRandomName = utils.genRandomName; /** * Parser context object * * @param {Object} options */ var Context = function (options) { this.astStack = new ASTStack(); this.tags = options.customTags; this.raw = ''; this.disableParseTag = false; this.line = 1; this.lineStart = 0; this.position = 0; this.parseTagStack = []; this.forItems = []; this.tablerowItems = []; this.forItems.test = this.tablerowItems.test = function (name) { var name = name.split('.')[0]; return this.indexOf(name) === -1 ? false : true; }; }; /** * Enable parse tag */ Context.prototype.enableParseTag = function () { var parseTagStack = this.parseTagStack; if (parseTagStack.length < 1) { return true; } else { return parseTagStack[parseTagStack.length - 1].apply(null, arguments); } }; /** * Get current position * * @return {Object} */ Context.prototype.getPosition = function () { return { line: this.line, column: this.position - this.lineStart + 2 }; }; /** * Generate a new AST node * * @return {Array} */ Context.prototype.astNode = function () { var pos = this.getPosition(); var ast = [pos.line, pos.column]; for (var i = 0, len = arguments.length; i < len; i++) { ast.push(arguments[i]); } return ast; }; /** * Parse template, return AST array * * @param {String} tpl * @param {Object} options * - {Object} customTags * @return {Array} */ var parser = exports = module.exports = function (tpl, options) { options =options || {}; var customTags = options.customTags = merge(baseTags, options.customTags); // parser context var context = new Context(options); // compiler version context.astStack.push(context.astNode(OPCODE.COMPILER_VERSION, 1)); var mainAst = context.astNode(OPCODE.LIST); var strTmp = ''; function flush () { context.astStack.push(context.astNode(OPCODE.PRINTSTRING, strTmp)); strTmp = ''; } for (var i = 0, len = tpl.length; i < len; i++) { context.position = i; var c = tpl[i]; if (c === '\n') { context.line++; context.lineStart = i; } var text = tpl.substr(i, 2); if (context.disableParseTag) { // ----------------------------------------------------------------------- // raw if (text === '{%') { var e = textIndexOf(tpl, '%}', i); var body = tpl.slice(i + 2, e).trim(); context.raw = strTmp; if (e > i && context.enableParseTag(context, body, body)) { context.disableParseTag = false; strTmp = ''; context.raw = ''; i = e + 1; } else { strTmp += c; } } else { strTmp += c; } } else { // ---------------------------------------------------------------- // normal if (text === '{{') { var e = textIndexOf(tpl, '}}', i); if (e > i) { flush(); context.astStack.push(parseOutput(tpl.slice(i + 2, e).trim(), context)); i = e + 1; } } else if (text === '{%') { var e = textIndexOf(tpl, '%}', i); if (e > i) { // optimize: trim left var e2 = strTmp.lastIndexOf('\n'); if (e2 !== -1) { if (strTmp.slice(e2 + 1).trim() === '') { strTmp = strTmp.slice(0, e2 + 1); } } // parse tag flush(); parseTag(context, tpl.slice(i + 2, e).trim()); i = e + 1; // optimize: trim right var e3 = tpl.indexOf('\n', i + 1); if (e3 !== -1) { if ((tpl.slice(i + 1, e3 + 1).trim() === '')) { i = e3; context.line++; context.lineStart = i; } } } } else { strTmp += c; } // ----------------------------------------------------------------------- } } flush(); return mainAst.concat(context.astStack.result()); }; // Default parser component var baseTags = { 'if': function (context, name, body) { var ast = parseCondition(body, context); context.astStack.newChild(context.astNode(OPCODE.IF, ast)).newChild(context.astNode(OPCODE.LIST)); }, 'unless': function (context, name, body) { var ast = parseCondition(body, context); context.astStack.newChild(context.astNode(OPCODE.IF, context.astNode(OPCODE.NOT, ast))).newChild(context.astNode(OPCODE.LIST)); }, 'else': function (context, name, body) { context.astStack.close().newChild(context.astNode(OPCODE.LIST)); }, 'endif': function (context, name, body) { context.astStack.close(); // reset the AST structure var ast = context.astStack.last(); context.astStack.close(); var reset = function (ast) { if (ast.length > 6) { var a = ast.slice(0, 5); a[5] = reset(context.astNode(OPCODE.IF).concat(ast.slice(5))); return a; } else { return ast; } }; var list = context.astStack.last(); if (list) { list.pop(); list.push(reset(ast)); } else { context.astStack.list.push(context.astNode(OPCODE.PRINTSTRING, '{% endif %}')); } }, 'endunless': function (context, name, body) { context.astStack.close().close(); }, 'elseif': function (context, name, body) { context.astStack.close(); var ast = parseCondition(body, context); context.astStack.push(ast).newChild(context.astNode(OPCODE.LIST)); }, 'elsif': function (context, name, body) { context.astStack.close(); var ast = parseCondition(body, context); context.astStack.push(ast).newChild(context.astNode(OPCODE.LIST)); }, 'case': function (context, name, body) { var ast = parseVariables(body, context); context.astStack.newChild(context.astNode(OPCODE.CASE)).newChild(ast); }, 'when': function (context, name, body) { context.astStack.close(); var ast = parseWhen(body, context); context.astStack.push(context.astNode(OPCODE.WHEN, ast)).newChild(context.astNode(OPCODE.LIST)); }, 'endcase': function (context, name, body) { context.astStack.close().close(); }, 'for': function (context, name, body) { var arr = parseFor(body); var attrs = arr[2]; context.astStack.newChild(context.astNode(OPCODE.FOR, localsAstNode(arr[0], context), arr[1], attrs.offset, attrs.limit)).newChild(context.astNode(OPCODE.LIST)); context.forItems.push(arr[1]); }, 'endfor': function (context, name, body) { context.astStack.close().close(); context.forItems.pop(); }, 'tablerow': function (context, name, body) { var arr = parseFor(body); var attrs = arr[2]; attrs.cols = parseInt(attrs.cols); if (!(attrs.cols > 1)) attrs.cols = 1; context.astStack.newChild(context.astNode(OPCODE.TABLEROW, localsAstNode(arr[0], context), arr[1], attrs.offset, attrs.limit, attrs.cols)) .newChild(context.astNode(OPCODE.LIST)); context.tablerowItems.push(arr[1]); }, 'endtablerow': function (context, name, body) { context.astStack.close().close(); context.tablerowItems.pop(); }, 'assign': function (context, name, body) { var i = body.indexOf('='); if (i !== -1) { var left = body.substr(0, i).trim(); var right = body.substr(i + 1).trim(); var ast = parseVariables(right, context); context.astStack.push(context.astNode(OPCODE.ASSIGN, left, ast)); } }, 'capture': function (context, name, body) { var blocks = arrayRemoveEmptyString(splitText(body, [' '])); var name = blocks[0] || genRandomName(); if (!blocks[0]) { context.astStack.push(context.astNode(OPCODE.PRINTSTRING, 'warning: missing name in {% capture %}')); } context.astStack.newChild(context.astNode(OPCODE.CAPTURE, name)); }, 'endcapture': function (context, name, body) { context.astStack.close(); }, 'block': function (context, name, body) { var blocks = arrayRemoveEmptyString(splitText(body, [' '])); var name = blocks[0] || genRandomName(); if (!blocks[0]) { context.astStack.push(context.astNode(OPCODE.PRINTSTRING, 'warning: missing name in {% block %}')); } context.astStack.newChild(context.astNode(OPCODE.BLOCK, name)); }, 'endblock': function (context, name, body) { context.astStack.close(); }, 'cycle': function (context, name, body) { var blocks = arrayRemoveEmptyString(splitText(body, [' ', ','])); blocks = blocks.filter(function (item) { return item === ',' ? false : true; }); if (blocks.length > 0) { var i = blocks[0].indexOf(':'); if (i !== -1) { var key = blocks[0].substr(0, i); blocks[0] = blocks[0].substr(i + 1); if (blocks[0].length < 1) { blocks.shift(); } } else { var key = md5(blocks.join(':')).substr(0, 8); } blocks = blocks.map(function (item) { return localsAstNode(item, context); }); context.astStack.push(context.astNode(OPCODE.CYCLE, key).concat(blocks)); } }, 'extends': function (context, name, body) { var blocks = arrayRemoveEmptyString(splitText(body, [' '])); if (blocks.length === 0) { // syntax error context.astStack.push(context.astNode(OPCODE.PRINTSTRING, '{% extends ' + body + ' %}')); return; } // get the filename var bf = blocks[0]; if (bf.substr(0, 2) === '{{') { // filename is a variable for (var i = 1; i < blocks.length; i++) { var b = blocks[i]; bf += b; if (b.substr(-2) === '}}') { break; } } filename = parseVariables(bf.slice(2, -2), context); blocks = blocks.slice(i + 1); } else { // filename is a string filename = stripQuoteWrap(bf); blocks = blocks.slice(1); } context.astStack.push(context.astNode(OPCODE.EXTENDS, filename)); }, 'include': function (context, name, body) { var blocks = arrayRemoveEmptyString(splitText(body, [' '])); var filename, withLocals, parameters; // support the following pattern: // {% include xxx %} or {% include "xxx" %} // {% include {{xx}} %} and with filters: {% include {{xx | yy}} %} // {% include xxx with yy %} // {% include xxx a=1 b=2 %} if (blocks.length === 0) { // syntax error context.astStack.push(context.astNode(OPCODE.PRINTSTRING, '{% include ' + body + ' %}')); return; } else if (blocks.length === 1 && !(blocks[0].substr(0, 2) === '{{' && blocks[0].substr(-2) === '}}')) { // filename is a string filename = stripQuoteWrap(blocks[0]).trim(); } else { if (blocks.length >= 3 && blocks[blocks.length - 2].toLowerCase() === 'with') { // if include "with" syntax withLocals = localsAstNode(stripQuoteWrap(blocks[blocks.length - 1]), context); blocks = blocks.slice(0, -2); } // get the filename var bf = blocks[0]; if (bf.substr(0, 2) === '{{') { // filename is a variable for (var i = 1; i < blocks.length; i++) { var b = blocks[i]; bf += b; if (b.substr(-2) === '}}') { break; } } filename = parseVariables(bf.slice(2, -2), context); blocks = blocks.slice(i + 1); } else { // filename is a string filename = stripQuoteWrap(bf).trim(); blocks = blocks.slice(1); } // parse multi-part parameters if (blocks.length > 0) { blocks = arrayRemoveEmptyString(splitText(blocks.join(' '), [' ', '='])); var parts = []; var pi = 0; function addPart (i) { if (i < 0) return; parts.push(blocks.slice(pi, i + 1).join('')); pi = i + 1; } for (var i = 0; i < blocks.length; i++) { var b = blocks[i]; if (b === '=') { addPart(i - 2); } } addPart(i); parameters = context.astNode(OPCODE.LIST); //console.log(blocks, parts); parts.forEach(function (part) { var i = part.indexOf('='); if (i !== -1) { var left = part.substr(0, i).trim(); var right = part.substr(i + 1).trim(); var ast = parseVariables(right, context); parameters.push(context.astNode(OPCODE.WEAK_ASSIGN, left, ast)); } }); } } context.astStack.push(context.astNode(OPCODE.INCLUDE, filename, withLocals, parameters)); }, 'raw': function (context, name, body) { context.disableParseTag = true; context.parseTagStack.push(context.tags.endraw); }, 'endraw': function (context, name, body) { if (name.toLowerCase() === 'endraw') { context.astStack.push(context.astNode(OPCODE.PRINTSTRING, context.raw)); return true; } else { return false; } }, 'comment': function (context, name, body) { context.disableParseTag = true; context.parseTagStack.push(context.tags.endcomment); }, 'endcomment': function (context, name, body) { if (name.toLowerCase() === 'endcomment') { context.astStack.push(context.astNode(OPCODE.COMMENT, context.raw)); return true; } else { return false; } } }; /** * Parse "filter" * * @param {String} text * @param {Array} firstArg * @param {Array} link * @param {Object} context * @return {Array} */ var parseFilter = parser.parseFilter = function (text, firstArg, link, context) { text = text.trim(); var i = text.indexOf(':'); if (i === -1) { var name = text; var args = []; } else { var name = text.slice(0, i); var args = splitText(text.slice(i + 1).trim(), [',']).filter(function (item) { return (item !== ','); }); } args = args.map(function (item) { return localsAstNode(item.trim(), context); }); args.unshift(firstArg); var ast = context.astNode(OPCODE.FILTER, name).concat(args); if (link.length > 0) { return parseFilter(link.shift(), ast, link, context); } else { return ast; } }; /** * Parse "condition" * * @param {String} body * @param {Object} context * @return {Array} */ var parseCondition = parser.parseCondition = function (body, context) { var cond = body.trim(); var blocks = arrayRemoveEmptyString(splitText(cond, [' ', '===', '&&', '||', '>=', '<=', '==', '!=', '<>', '=', '>', '<', '!'])); var trans = { '&&': 'and', '||': 'or', '>': 'gt', '<': 'lt', '=': 'eq', '==': 'eq', '===':'ed', '<>': 'ne', '!=': 'ne', '>=': 'ge', '<=': 'le', '!': 'not' }; blocks = blocks.map(function (item) { return (trans[item] || item); }); // extract the "and" and "or" var _blocks = blocks; blocks = []; var tmp = []; var flush = function () { if (tmp.length > 0) { blocks.push(tmp); tmp = []; } }; _blocks.forEach(function (item) { if (item.toLowerCase() === 'and' || item.toLowerCase() === 'or') { flush(); blocks.push(item.toLowerCase()); } else { tmp.push(item); } }); flush(); // generate condition AST var condAst = []; blocks.forEach(function (item) { if (Array.isArray(item)) { if (item.length === 1) { var ast = context.astNode(OPCODE.EXISTS, localsAstNode(item[0], context)); } else if (item.length === 2) { var code = OPCODE[item[0].toUpperCase()] || OPCODE.DEBUG; var ast = context.astNode(code, localsAstNode(item[1], context)); } else { var code = OPCODE[item[1].toUpperCase()] || OPCODE.DEBUG; var ast = context.astNode(code, localsAstNode(item[0], context), localsAstNode(item[2], context)); } condAst.push(ast); } else { condAst.push(item); } }); var mergeCond = function (op) { var ret = false; if (blocks.length < 3) return ret; var _condAst = condAst; condAst = []; for (var i = 0, len = _condAst.length; i < len; i++) { var mid = _condAst[i + 1]; if (typeof(mid) === 'string' && mid.toLowerCase() === op && i + 2 < len) { var code = OPCODE[op.toUpperCase()] || OPCODE.DEBUG; condAst.push(context.astNode(code, _condAst[i], _condAst[i + 2])); i += 2; ret = true; } else { condAst.push(_condAst[i]); } } return ret; }; // and > or while (mergeCond('and')) { // do nothing } while (mergeCond('or')) { // do nothing } return condAst[0]; }; /** * Parse "when" * * @param {String} body * @param {Object} context * @return {Array} */ var parseWhen = parser.parseWhen = function (body, context) { var blocks = arrayRemoveEmptyString(splitText(body, [' ', 'or'])); blocks = blocks.filter(function (item) { return item === 'or' ? false : true; }).map(function (item) { var ast = localsAstNode(item, context); if (!Array.isArray(ast)) ast = context.astNode(OPCODE.OBJECT, ast); return ast; }); return blocks; }; /** * Parse "variables" * 如: a | call:1,2 | lower * * @param {String} text * @param {Object} context * @return {Array} */ var parseVariables = parser.parseVariables = function (text, context) { var i = 0; var filters = []; while (true) { var e = textIndexOf(text, '|', i); if (e === -1) { break; } else { filters.push(text.slice(i, e).trim()); i = e + 1; } } if (filters.length > 0) { filters.push(text.slice(i).trim()); } if (filters.length > 1) { var name = filters.shift(); var astList = parseFilter(filters.shift(), localsAstNode(name, context), filters, context); } else { var astList = localsAstNode(text, context); } return astList; }; /** * Parse "for" * * @param {String} body * @return {Array} */ var parseFor = parser.parseFor = function (body) { var blocks = arrayRemoveEmptyString(splitText(body, [' '])); var parseAttrs = function (blocks) { if (blocks.length < 1) return {}; var attrString = blocks.reduce(function (sum, item) { if (item === ':') return sum; if (sum.substr(-1) === ':') return sum + item; return sum + ' ' + item; }); var attrs = {}; arrayRemoveEmptyString(splitText(attrString, [' '])) .forEach(function (item) { var i = item.indexOf(':'); if (i === -1) { attrs[item.toLowerCase()] = true; } else { attrs[item.substr(0, i).toLowerCase()] = item.substr(i + 1); } }); return attrs; }; if (blocks.length >= 3 && blocks[1].toLowerCase() === 'in') { // normal var itemName = blocks[0]; var arrayName = blocks[2]; var attrs = parseAttrs(blocks.slice(3)); } else if (blocks.length === 1 || (blocks.length > 1 && blocks[1].toLowerCase() !== 'in' && blocks[1].indexOf(':') === -1)) { // non-standard writing: {% for array %} var itemName = 'item'; var arrayName = blocks[0]; var attrs = parseAttrs(blocks.slice(1)); } if (!(attrs.offset > 0)) attrs.offset = 0; if (!(attrs.limit > 0)) attrs.limit = 0; return [arrayName, itemName, attrs]; }; /** * Parse "{{name}}" * * @param {String} text * @param {Object} context * @return {Array} */ var parseOutput = function (text, context) { var astList = parseVariables(text, context); if (Array.isArray(astList)) { if (astList[2] === OPCODE.LOCALS) { return context.astNode(OPCODE.PRINTLOCALS).concat(astList.slice(3)); } else { return context.astNode(OPCODE.PRINT, astList); } } else { return context.astNode(OPCODE.PRINTSTRING, astList); } }; /** * Parse "{%tag%}" * * @param {Object} context * @param {String} text * @return {Array} */ var parseTag = function (context, text) { var i = text.indexOf(' '); if (i === -1) { var name = text; var body = ''; } else { var name = text.slice(0, i); var body = text.slice(i + 1).trim(); } name = name.toLowerCase(); if (typeof(context.tags[name]) === 'function') { context.tags[name](context, name, body); } else { context.astStack.push(context.astNode(OPCODE.UNKNOWN_TAG, name, body)); } }; },{"./opcode":5,"./utils":7}],7:[function(require,module,exports){ (function (process){ /** * Utils * * @author Zongmin Lei<leizongmin@gmail.com> */ var md5 = require('./md5'); var utils = exports = module.exports = {}; var OPCODE = require('./opcode'); /** * Empty function */ utils.noop = function () {}; /** * Debug * * @param {String} name * @return {Function} */ utils.debug = function (name) { if (/tinyliquid/img.test(process.env.DEBUG)) { return function (msg) { console.log('[debug] TinyLiquid:%s: %s', name, msg); }; } else { return utils.noop; } }; /** * MD5 * * @param {String} text * @return {String} */ utils.md5 = md5; /** * Whether a string in quotes * * @param {String} text * @return {Boolean} */ utils.isQuoteWrapString = function (text) { if ((text[0] === '"' && text[text.length - 1] === '"') || (text[0] === '\'' && text[text.length - 1] === '\'')) { return true; } else { return false; } }; /** * Remove the string outside the quotation marks * * @param {string} text * @return {string} */ utils.stripQuoteWrap = function (text) { if (utils.isQuoteWrapString(text)) { return text.substr(1, text.length - 2); } else { return text; } }; /** * Get the index of the substring (not in quotes) * * @param {String} text * @param {String} subject * @param {Integer} start */ utils.textIndexOf = function (text, subject, start) { if (start < 0) { start = text.length + start; } else if (isNaN(start)) { start = 0; } var subjectLength = subject.length; var quote = false; for (var i = start, len = text.length; i < len; i++) { var c = text[i]; if (quote) { if (c === quote && text[i - 1] !== '\\') { quote = false; } } else { if ((c === '\'' || c === '"') && text[i - 1] !== '\\') { quote = c; } else { if (text.substr(i, subjectLength) === subject) { return i; } } } } return -1; }; /** * Split string * * Example: console.log(utils.splitText('a>b "a>b" a < c', [' ', '<', '>'])); * Return: ['a', '>', 'b', ' ', '"a>b"', ' ', 'a', ' ', '<', ' ', 'c'] * Notes: if delimiter > =, and >, this type is > = must be in the front * * @param {String} text * @param {Array} separators * @return {Array} */ utils.splitText = function (text, separators) { if (!Array.isArray(separators)) { separators = [separators || ' ']; } var list = []; var tmp = ''; var flush = function () { if (tmp.length > 0) { list.push(tmp); tmp = ''; } }; // split string in quotes var quote = false; for (var i = 0, len = text.length; i < len; i++) { var c = text[i]; if (quote) { tmp += c; if (c === quote && text[i - 1] !== '\\') { flush(); quote = false; } } else { if ((c === '\'' || c === '"') && text[i - 1] !== '\\') { flush(); tmp += c; quote = c; } else { tmp += c; } } } flush(); // separators var _list = list; list = []; tmp = ''; var isSeparator = function (text) { for (var i = 0, len = separators.length; i < len; i++) { var sep = separators[i]; if (text.substr(0, sep.length) === sep) { return sep; } } return false; }; _list.forEach(function (text) { if (utils.isQuoteWrapString(text)) { list.push(text); } else { for (var i = 0, len = text.length; i < len; i++) { var c = text[i]; var sep = isSeparator(text.slice(i)); if (sep === false) { tmp += c; } else { flush(); list.push(sep); i += sep.length - 1; } } flush(); } }); list = list.filter(function (item) { return item.trim(); }); return list; }; /** * Safe json stringify * * @param {Object} data * @param {String|Number} space indent * @return {String} */ utils.jsonStringify = function (data, space) { var seen = []; return JSON.stringify(data, function (key, val) { if (!val || typeof val !== 'object') { return val; } if (seen.indexOf(val) !== -1) { return '[Circular]'; } seen.push(val); return val; }, space); }; /** * Merge object * * @param {Object} a * @param {Object} b * @return {Object} */ utils.merge = function () { var ret = {}; for (var i in arguments) { var obj = arguments[i]; for (var j in obj) { ret[j] = obj[j]; } } return ret; }; /** * Create a locals AST node * * @param {String} text * @param {Object} context optional, the parser context * @return {Array} */ utils.localsAstNode = function (text, context) { if (text.length > 0) { if (utils.isQuoteWrapString(text)) { // string return text.slice(1, text.length - 1); } else if (text === 'false') { // constants return false; } else if (text === 'true') { // constants return true; } else if (text === 'null' || text === 'empty' || text === 'nil' || text === 'undefined') { // constants return null; } else if (isFinite(text)) { // number return Number(text); } else if (/^\(\d+\.\.\d+\)$/.test(text)) { // range (start_num..end_num) var b = text.match(/^\((\d+)\.\.(\d+)\)$/); return context.astNode(OPCODE.RANGE, b[1], b[2]); } else if (text[0] === '(' && text[text.length - 1] === ')' && text.split('..').length === 2) { // range (start_locals..end_locals) var b = text.slice(1, -1).split('..'); return context.astNode(OPCODE.RANGE, utils.localsAstNode(b[0], context), utils.localsAstNode(b[1], context)); } else { var loopLocals = function (name) { var n = OPCODE['LOOPLOCALS_' + name.toUpperCase()]; if (typeof(n) === 'undefined') { return [OPCODE.LOOPLOCALS_UNKNOWN, name]; } else { return [n]; } }; if (text.substr(0, 8) === 'forloop.') { // forloop locals return context.astNode(OPCODE.FORLOOPLOCALS).concat(loopLocals(text.substr(8))); } else if (text.substr(0, 13) === 'tablerowloop.') { // tablerowloop locals return context.astNode(OPCODE.TABLEROWLOOPLOCALS).concat(loopLocals(text.substr(13))); } else { var localsAst = function (op) { var childs = text.split('.'); return context.astNode(op, text, childs[0], childs.length > 1 ? childs.slice(1) : null); }; if (context && context.forItems.test(text)) { // forloop item return localsAst(OPCODE.FORLOOPITEM); } else if (context && context.tablerowItems.test(text)) { // tablerowloop item return localsAst(OPCODE.TABLEROWITEM); } else { // locals return localsAst(OPCODE.LOCALS); } } } } else { return null; } }; /** * AST stack */ var ASTStack = utils.ASTStack = function () { this.list = []; this._parent = [this.list]; this.newChild(); }; /** * Get last node * * @return {Array} */ ASTStack.prototype.last = function () { return this._parent[this._parent.length - 1]; }; /** * Create a new child node * * @param {Array} astList 初始值 */ ASTStack.prototype.newChild = function (astList) { if (typeof(astList) === 'undefined') { astList = []; } else { astList = Array.isArray(astList) ? astList : [OPCODE.OBJECT, astList]; } this.last().push(astList); this._parent.push(astList); return this; }; /** * Push an AST Node * * @param {Object} ast */ ASTStack.prototype.push = function (ast) { this.last().push(ast); return this; }; /** * Close the current child node */ ASTStack.prototype.close = function () { var list = this.last(); if (list[2] === OPCODE.LIST && list.length < 5) { // optimization for only one element of the OPCODE.LIST var ast = list[3]; list.length = 0; for (var i = 0, len = ast.length; i < len; i++) { list[i] = ast[i]; } } this._parent.pop(); return this; }; /** * Return the stack */ ASTStack.prototype.result = function () { if (this.list.length === 1) { return this.list[0]; } else { return this.list; } }; /** * Remove empty string in the array * * @param {Array} arr * @return {Array} */ utils.arrayRemoveEmptyString = function (arr) { return arr.filter(function (item) { return item.trim().length > 0 ? true : false; }); }; /** * Get a number array from the specify range * * @param {int} s * @param {int} e * @return {array} */ utils.range = function (s, e) { s = parseInt(s); e = parseInt(e); var r = []; if (isNaN(s) || isNaN(e)) return r; for (; s <= e; s++) { r.push(s); } return r; }; /** * Convert an object to an array * * @param {object} data * @return {array} */ utils.toArray = function (data) { if (Array.isArray(data)) return data; var ret = []; for (var i in data) { if (i !== 'size') { ret.push(data[i]); } } return ret; }; /** * Slice the array * * @param {Array} array * @param {Integer} offset * @param {Integer} limit * @return {Array} */ utils.arraySlice = function (array, offset, limit) { if (!Array.isArray(array)) return array; offset = parseInt(offset); limit = parseInt(limit); if (offset > 0) { if (limit > 0) { return array.slice(offset, offset + limit); } else { return array.slice(offset); } } else if (limit > 0) { return array.slice(0, limit); } else { return array; } }; /** * Get the properties from a value * * @param {Object} value * @param {Array} childs * @return {Array} */ utils.getChildValue = function (value, childs) { if (value === null || value === undefined) { return [false, null]; } if (childs && childs.length > 0) { for (var i = 0, len = childs.length; i < len; i++) { if (value === null) return [false, null]; var c = value[childs[i]]; if (value && typeof(c) !== 'undefined') { value = c; } else { return [false, null]; } } } return [true, value]; }; /** * Get each item from an array, and call the function * if fn passed an error argument, then break * * @param {Array} list * @param {Function} fn format: function (item, index, done) * @param {Function} callback */ utils.asyncEach = function (list, fn, callback, a1, a2, a3, b1, b2, b3) { var i = -1; var j = 0; var len = list.length; var next = function (err) { if (err) return callback(err, null, a2, a3); j++; if (j > 10) { // avoid stack overflow j = 0; setImmediate(next); } else { i++; if (i < len) { fn(list[i], i, next, b1, b2, b3); } else { callback(a1 || null, a2, a3); } } }; next(); }; // need `setImmediate` function supported if (typeof setImmediate !== 'function') { throw new Error('Sorry, you JavaScript runtime environment does not support `setImmediate()` [TinyLiquid]'); } /** * According to the condition to decide whether to continue to repeat an asynchronous callback * * @param {Function} test returns true or false to indicate whether or not to continue * @param {Function} fn format: function (done) * @param {Function} callback */ utils.asyncFor = function (test, fn, callback, a1, a2, a3) { var next = function () { if (test()) { fn(next); } else { callback(a1 || null, a2, a3); } }; next(); }; utils.genRandomName = function () { var chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz'; var max = chars.length; var ret = ''; for (var i = 0; i < 10; i++) { ret += chars.charAt(Math.floor(Math.random() * max)); } return ret; }; /******************************************************************************/ /** * 模板Filter的异步函数缓存 * * @param {String} name 函数名称 * @param {Function} fn 格式:function (arg1, arg2, callback) * @param {Number} maxAge 有效期,毫秒 * @return {Function} */ utils.wrapFilterCache = function(name, fn, maxAge) { return function() { var me = this; var args = getFilterArguments(arguments); var callback = getFilterArgumentCallback(arguments); var context = getFilterArgumentContext(arguments); var ret = findFilterCache(context, name, args); if (ret) { callback(null, ret.value); } else { fn.apply(me, newFilterArguments(args, function (err, value) { if (err) return callback(err); setFilterCache(context, name, args, value); callback(null, value); }, context)); } }; }; function getFilterArguments(args) { return Array.prototype.slice.call(args, 0, args.length - 2); } function getFilterArgumentCallback(args) { return args[args.length - 2]; } function getFilterArgumentContext(args) { return args[args.length - 1]; } function newFilterArguments(args, callback, context) { return [].concat(args).concat([callback, context]); } function getArgumentsKey(args) { return md5(JSON.stringify(args)).slice(0, 10); } function findFilterCache (context, name, args) { var map = context._filterCache[name]; if (!map) return false; var key = getArgumentsKey(args); if (key in map) { return {args: args, value: map[key]}; } else { return false; } } function setFilterCache (context, name, args, value) { var key = getArgumentsKey(args); if (!context._filterCache[name]) context._filterCache[name] = {}; context._filterCache[name][key] = value; } }).call(this,require('_process')) },{"./md5":4,"./opcode":5,"_process":9}],8:[function(require,module,exports){ /** * Run AST * * @author Zongmin Lei<leizongmin@gmail.com> */ var utils = require('./utils'); var parser = require('./parser'); var filters = require('./filters'); var Context = require('./context'); var OPCODE = require('./opcode'); var debug = utils.debug('VM'); var merge = utils.merge; var DataStack = utils.DataStack; var range = utils.range; var toArray = utils.toArray; var arraySlice = utils.arraySlice; var getChildValue = utils.getChildValue; var asyncEach = utils.asyncEach; var asyncFor = utils.asyncFor; /** * If it's opcode return true, else return false * * @param {Array} ast * @return {Boolean} */ var isOpcode = function (ast) { return ast instanceof Array && !(ast[0] instanceof Array); }; /** * If it's AST array return true, else return false * * @param {Array} ast * @return {Boolean} */ var isAST = function (ast) { return ast instanceof Array; }; /** * Run AST * * @param {Array} astList * @param {Object} context * @param {Function} callback format: function (err, return_value) */ var run = exports.run = function (astList, context, callback) { if (isAST(astList)) { if (isOpcode(astList)) { // single AST Node var op = execOpcode[astList[2]]; if (!op) op = execOpcode[OPCODE.UNKNOWN]; context.setCurrentPosition(astList[0], astList[1]); // debug('opcode: ' + astList[2] + ' at ' + astList[0] + ', ' + astList[1]); op(context, callback, astList.slice(2)); } else { // AST list var retval = new Array(astList.length); asyncEach(astList, runEachItem, callback, null, retval, null, context, retval); } } else { callback(null, astList); } }; var runEachItem = function (item, index, callback, context, retval) { run(item, context, function (err, vals) { if (err) { callback(err); return; } retval[index] = vals; callback(null); }); }; /** * Get the arguments of the current AST node * * @param {Array} astList * @param {Object} context * @param {Function} callback */ var getOpArgs = function (astList, context, callback) { var retval = new Array(astList.length); asyncEach(astList, function (ast, i, done) { run(ast, context, function (err, val) { if (err) { callback(err); } else { retval[i] = val; done(); } }); }, callback, null, retval); }; /** * Get the two arguments of the current AST node * * @param {Array} astList * @param {Object} context * @param {Function} callback */ var getOpArgs2 = function (astList, context, callback) { var retval = new Array(2); var i = 0; var getOpArgs2_callback = function (err, val) { if (err) { callback(err); } else { retval[i] = val; i++; if (i >= 2) callback(null, retval); } }; run(astList[1], context, getOpArgs2_callback); run(astList[2], context, getOpArgs2_callback); }; var execOpcode = []; execOpcode[OPCODE.UNKNOWN] = function (context, callback, ast) { callback(context.throwUnknownOpcodeError(ast[0])); }; execOpcode[OPCODE.LIST] = function (context, callback, ast) { run(ast.slice(1), context, callback); }; execOpcode[OPCODE.PRINT] = function (context, callback, ast) { if (isAST(ast[1])) { run(ast[1], context, function (err, val) { if (err) { callback(err); } else { context.print(val); callback(null); } }); } else { context.print(ast[1]); callback(null); } }; execOpcode[OPCODE.PRINTLOCALS] = function (context, callback, ast) { getLocals(ast[1], ast[2], ast[3], context, printLocalsAndCallback, context, callback); }; var printLocalsAndCallback = function (err, val, context, callback) { if (err) { callback(err, val); } else { context.print(val); callback(err, val); } }; execOpcode[OPCODE.PRINTSTRING] = function (context, callback, ast) { context.print(ast[1]); callback(null); }; execOpcode[OPCODE.DEBUG] = function (context, callback, ast) { debug(ast[1]); callback(null); }; execOpcode[OPCODE.LOCALS] = function (context, callback, ast) { getLocals(ast[1], ast[2], ast[3], context, callback); }; function getLocals (fullName, mainName, childs, context, callback, a1, a2, a3) { // try to get the fullName first var info = context.getLocals(fullName); if (info) { var name = fullName; childs = null; } else { var name = mainName; } context.fetchSingleLocals(name, function (err, val) { if (err) { callback(err, null, a1, a2, a3); } else { if (childs) { var v = getChildValue(val, childs); if (v[0]) { callback(null, v[1], a1, a2, a3); } else { callback(context.throwLocalsUndefinedError(fullName), null, a1, a2, a3); } } else { callback(null, val, a1, a2, a3); } } }); } execOpcode[OPCODE.FORLOOPITEM] = function (context, callback, ast) { var fullName = ast[1]; var mainName = ast[2]; var childs = ast[3]; if (context._isInForloop) { var loop = context.forloopInfo(); if (mainName === loop.itemName) { if (childs) { var v = getChildValue(loop.item, childs); if (v[0]) { callback(null, v[1]); } else { callback(context.throwLoopItemUndefinedError(fullName), null); } } else { callback(null, loop.item); } return; } } callback(context.throwLoopItemUndefinedError(mainName), null); }; execOpcode[OPCODE.TABLEROWITEM] = function (context, callback, ast) { var fullName = ast[1]; var mainName = ast[2]; var childs = ast[3]; if (context._isInTablerowloop) { var loop = context.tablerowloopInfo(); if (mainName === loop.itemName) { if (childs) { var v = getChildValue(loop.item, childs); if (v[0]) { callback(null, v[1]); } else { callback(context.throwLoopItemUndefinedError(fullName), null); } } else { callback(null, loop.item); } return; } } callback(context.throwLoopItemUndefinedError(mainName), null); }; execOpcode[OPCODE.FORLOOPLOCALS] = function (context, callback, ast) { var loop = context.forloopInfo(); var val = null; if (loop) { switch (ast[1]) { case OPCODE.LOOPLOCALS_LENGTH: val = loop.length; break; case OPCODE.LOOPLOCALS_NAME: val = loop.itemName; break; case OPCODE.LOOPLOCALS_INDEX0: val = loop.index; break; case OPCODE.LOOPLOCALS_INDEX: val = loop.index + 1; break; case OPCODE.LOOPLOCALS_RINDEX0: val = loop.length - loop.index - 1; break; case OPCODE.LOOPLOCALS_RINDEX: val = loop.length - loop.index; break; case OPCODE.LOOPLOCALS_FIRST: val = loop.index < 1; break; case OPCODE.LOOPLOCALS_LAST: val = loop.index + 1 >= loop.length; break; default: callback(context.throwLoopLocalsUndefinedError('forloop.' + ast[2]), val); return; } } callback(null, val); }; execOpcode[OPCODE.TABLEROWLOOPLOCALS] = function (context, callback, ast) { var loop = context.tablerowloopInfo(); var val = null; if (loop) { switch (ast[1]) { case OPCODE.LOOPLOCALS_LENGTH: val = loop.length; break; case OPCODE.LOOPLOCALS_NAME: val = loop.itemName; break; case OPCODE.LOOPLOCALS_INDEX0: val = loop.index; break; case OPCODE.LOOPLOCALS_INDEX: val = loop.index + 1; break; case OPCODE.LOOPLOCALS_RINDEX0: val = loop.length - loop.index - 1; break; case OPCODE.LOOPLOCALS_RINDEX: val = loop.length - loop.index; break; case OPCODE.LOOPLOCALS_FIRST: val = loop.index < 1; break; case OPCODE.LOOPLOCALS_LAST: val = loop.index + 1 >= loop.length; break; case OPCODE.LOOPLOCALS_COL0: val = loop.colIndex; break; case OPCODE.LOOPLOCALS_COL: val = loop.colIndex + 1; break; case OPCODE.LOOPLOCALS_COL_FIRST: val = loop.colIndex < 1; break; case OPCODE.LOOPLOCALS_COL_LAST: val = loop.colIndex + 1 >= loop.columns; break; default: callback(context.throwLoopLocalsUndefinedError('tablerowloop.' + ast[2]), val); return; } } callback(null, val); }; execOpcode[OPCODE.FILTER] = function (context, callback, ast) { getOpArgs(ast.slice(2), context, function (err, args) { if (err) { callback(err); } else { context.callFilter(ast[1], args, callback); } }); }; execOpcode[OPCODE.IF] = function (context, callback, ast) { run(ast[1], context, function (err, isTrue) { if (err) { callback(err); } else { if (isTrue) { run(ast[2], context, callback); } else { run(ast[3], context, callback); } } }); }; execOpcode[OPCODE.FOR] = function (context, callback, ast) { run(ast[1], context, function (err, arr) { if (err) { callback(err); } else { var arr = arraySlice(toArray(arr), ast[3], ast[4]); var astLoop = ast[5]; var astElse = ast[6]; if (arr.length > 0) { context.forloopEnter(arr.length, ast[2]); asyncEach(arr, function (item, i, done) { context.forloopItem(item, i); run(astLoop, context, done); }, function () { context.forloopEnd(); callback(null); }); } else if (astElse) { run(astElse, context, callback); } else { callback(null); } } }); }; execOpcode[OPCODE.TABLEROW] = function (context, callback, ast) { run(ast[1], context, function (err, arr) { if (err) { callback(err); } else { var arr = arraySlice(toArray(arr), ast[3], ast[4]); var cols = parseInt(ast[5]); var astLoop = ast[6]; var astElse = ast[7]; if (arr.length > 0) { context.tablerowloopEnter(arr.length, ast[2], cols); var ci = 1; var ri = 1; asyncEach(arr, function (item, i, done) { if (ci === 1) context.print('<tr class="row' + ri + '">'); context.print('<td class="col' + ci + '">'); context.tablerowloopItem(item, i, ci); run(astLoop, context, function (err) { if (err) { callback(err); } else { context.print('</td>'); ci++; if (ci > cols) { context.print('</tr>'); ci = 1; ri++; } done(); } }); }, function () { if (arr.length % cols !== 0) context.print('</tr>'); context.tablerowloopEnd(); callback(null); }); } else if (astElse) { run(astElse, context, callback); } else { callback(null); } } }); }; execOpcode[OPCODE.CASE] = function (context, callback, ast) { run(ast[1], context, function (err, val) { if (err) { callback(err); } else { var list = ast.slice(2); var cond = false; asyncFor(function () { return list.length > 0; }, function (done) { var a = list.shift(); var b = list.shift(); if (isOpcode(a) && isAST(b)) { run(a, context, function (err, vals) { if (err) { callback(err); } else { for (var i = 0, len = vals.length; i < len; i++) { if (vals[i] == val) { list = []; cond = true; run(b, context, function (err) { if (err) { callback(err); } else { done(); } }); return; } } done(); } }); } else { list = []; if (cond) { done(); } else { run(a, context, function (err) { if (err) { callback(err); } else { done(); } }); } } }, callback); } }); }; execOpcode[OPCODE.CYCLE] = function (context, callback, ast) { var i = context.getCycleIndex(ast[1]); if (i === null) { context.setCycle(ast[1], ast.slice(2)); i = 0; } var item = ast[2 + i]; run(item, context, function (err, val) { if (err) { callback(err); } else { context.print(val); callback(err); } }); }; execOpcode[OPCODE.EXISTS] = function (context, callback, ast) { run(ast[1], context, function (err, val) { callback(err || null, val ? true : false); }); }; execOpcode[OPCODE.AND] = function (context, callback, ast) { getOpArgs2(ast, context, function (err, vals) { if (err) { callback(err); } else { var val = vals.reduce(reduceAAndB); callback(null, val); } }); }; var reduceAAndB = function (a, b) { return a && b; }; execOpcode[OPCODE.OR] = function (context, callback, ast) { getOpArgs2(ast, context, function (err, vals) { if (err) { callback(err); } else { var val = vals.reduce(reduceAOrB); callback(null, val); } }); }; var reduceAOrB = function (a, b) { return a || b; }; execOpcode[OPCODE.NOT] = function (context, callback, ast) { run(ast[1], context, function (err, val) { callback(err || null, !!!val); }); }; execOpcode[OPCODE.LT] = function (context, callback, ast) { getOpArgs2(ast, context, function (err, vals) { callback(err || null, vals[0] < vals[1]); }); }; execOpcode[OPCODE.LE] = function (context, callback, ast) { getOpArgs2(ast, context, function (err, vals) { callback(err || null, vals[0] <= vals[1]); }); }; execOpcode[OPCODE.GT] = function (context, callback, ast) { getOpArgs2(ast, context, function (err, vals) { callback(err || null, vals[0] > vals[1]); }); }; execOpcode[OPCODE.GE] = function (context, callback, ast) { getOpArgs2(ast, context, function (err, vals) { callback(err || null, vals[0] >= vals[1]); }); }; execOpcode[OPCODE.EQ] = function (context, callback, ast) { getOpArgs2(ast, context, function (err, vals) { callback(err || null, vals[0] == vals[1]); }); }; execOpcode[OPCODE.ED] = function (context, callback, ast) { getOpArgs2(ast, context, function (err, vals) { callback(err || null, vals[0] === vals[1]); }); }; execOpcode[OPCODE.NE] = function (context, callback, ast) { getOpArgs2(ast, context, function (err, vals) { callback(err || null, vals[0] != vals[1]); }); }; execOpcode[OPCODE.CONTAINS] = function (context, callback, ast) { getOpArgs2(ast, context, function (err, vals) { callback(err || null, String(vals[0]).indexOf(vals[1]) !== -1); }); }; execOpcode[OPCODE.HASVALUE] = function (context, callback, ast) { getOpArgs2(ast, context, function (err, vals) { callback(err || null, toArray(vals[0]).indexOf(vals[1]) !== -1); }); }; execOpcode[OPCODE.HASKEY] = function (context, callback, ast) { getOpArgs2(ast, context, function (err, vals) { callback(err || null, vals[0] && typeof(vals[0][vals[1]]) !== 'undefined'); }); }; execOpcode[OPCODE.WHEN] = function (context, callback, ast) { run(ast[1], context, function (err, vals) { callback(err || null, vals); }); }; execOpcode[OPCODE.ASSIGN] = function (context, callback, ast) { run(ast[2], context, function (err, val) { if (err) { callback(err); } else { context.setLocals(ast[1], val); callback(err); } }); }; execOpcode[OPCODE.WEAK_ASSIGN] = function (context, callback, ast) { run(ast[2], context, function (err, val) { if (err) { callback(err); } else { context._locals[ast[1]] = val; callback(err); } }); }; execOpcode[OPCODE.CAPTURE] = function (context, callback, ast) { var oldBuf = context.getBuffer(); context.setBuffer(''); run(ast.slice(2), context, function (err) { if (err) { callback(err); } else { var buf = context.getBuffer(); context.setBuffer(oldBuf); context.setLocals(ast[1], buf); callback(err); } }); }; execOpcode[OPCODE.BLOCK] = function (context, callback, ast) { var oldBuf = context.getBuffer(); context.setBuffer(''); run(ast.slice(2), context, function (err) { if (err) { callback(err); } else { var buf = context.getBuffer(); context.setBuffer(oldBuf); // if on layout, output the block buffer if (context._isLayout) { context.setBlockIfEmpty(ast[1], buf); context.print(context.getBlock(ast[1])); } else { context.setBlock(ast[1], buf); } callback(err); } }); }; execOpcode[OPCODE.RANGE] = function (context, callback, ast) { run(ast[1], context, function (err, a) { if (err) { callback(err); } else { run(ast[2], context, function (err, b) { if (err) { callback(err); } else { callback(null, range(a, b)); } }); } }); }; execOpcode[OPCODE.OBJECT] = function (context, callback, ast) { callback(null, ast[1]); }; execOpcode[OPCODE.COMMENT] = function (context, callback, ast) { callback(null); }; execOpcode[OPCODE.EXTENDS] = function (context, callback, ast) { run(ast[1], context, function (err, filename) { if (err) return callback(err); context.setLayout(filename); callback(null); }); }; execOpcode[OPCODE.INCLUDE] = function (context, callback, ast) { run(ast[1], context, function (err, filename) { if (err) return callback(err); context.include(filename, ast[2], ast[3], callback); }); }; execOpcode[OPCODE.COMPILER_VERSION] = function (context, callback, ast) { callback(null); }; execOpcode[OPCODE.UNKNOWN_TAG] = function (context, callback, ast) { callback(context.throwUnknownTagError(ast[1], ast[2] || '')); }; execOpcode[OPCODE.TEMPLATE_FILENAME_PUSH] = function (context, callback, ast) { context.pushFilename(ast[1]); callback(null); }; execOpcode[OPCODE.TEMPLATE_FILENAME_POP] = function (context, callback, ast) { context.popFilename(); callback(null); }; },{"./context":1,"./filters":2,"./opcode":5,"./parser":6,"./utils":7}],9:[function(require,module,exports){ // shim for using process in browser var process = module.exports = {}; // cached from whatever global is present so that test runners that stub it // don't break things. But we need to wrap it in a try catch in case it is // wrapped in strict mode code which doesn't define any globals. It's inside a // function because try/catches deoptimize in certain engines. var cachedSetTimeout; var cachedClearTimeout; function defaultSetTimout() { throw new Error('setTimeout has not been defined'); } function defaultClearTimeout () { throw new Error('clearTimeout has not been defined'); } (function () { try { if (typeof setTimeout === 'function') { cachedSetTimeout = setTimeout; } else { cachedSetTimeout = defaultSetTimout; } } catch (e) { cachedSetTimeout = defaultSetTimout; } try { if (typeof clearTimeout === 'function') { cachedClearTimeout = clearTimeout; } else { cachedClearTimeout = defaultClearTimeout; } } catch (e) { cachedClearTimeout = defaultClearTimeout; } } ()) function runTimeout(fun) { if (cachedSetTimeout === setTimeout) { //normal enviroments in sane situations return setTimeout(fun, 0); } // if setTimeout wasn't available but was latter defined if ((cachedSetTimeout === defaultSetTimout || !cachedSetTimeout) && setTimeout) { cachedSetTimeout = setTimeout; return setTimeout(fun, 0); } try { // when when somebody has screwed with setTimeout but no I.E. maddness return cachedSetTimeout(fun, 0); } catch(e){ try { // When we are in I.E. but the script has been evaled so I.E. doesn't trust the global object when called normally return cachedSetTimeout.call(null, fun, 0); } catch(e){ // same as above but when it's a version of I.E. that must have the global object for 'this', hopfully our context correct otherwise it will throw a global error return cachedSetTimeout.call(this, fun, 0); } } } function runClearTimeout(marker) { if (cachedClearTimeout === clearTimeout) { //normal enviroments in sane situations return clearTimeout(marker); } // if clearTimeout wasn't available but was latter defined if ((cachedClearTimeout === defaultClearTimeout || !cachedClearTimeout) && clearTimeout) { cachedClearTimeout = clearTimeout; return clearTimeout(marker); } try { // when when somebody has screwed with setTimeout but no I.E. maddness return cachedClearTimeout(marker); } catch (e){ try { // When we are in I.E. but the script has been evaled so I.E. doesn't trust the global object when called normally return cachedClearTimeout.call(null, marker); } catch (e){ // same as above but when it's a version of I.E. that must have the global object for 'this', hopfully our context correct otherwise it will throw a global error. // Some versions of I.E. have different rules for clearTimeout vs setTimeout return cachedClearTimeout.call(this, marker); } } } var queue = []; var draining = false; var currentQueue; var queueIndex = -1; function cleanUpNextTick() { if (!draining || !currentQueue) { return; } draining = false; if (currentQueue.length) { queue = currentQueue.concat(queue); } else { queueIndex = -1; } if (queue.length) { drainQueue(); } } function drainQueue() { if (draining) { return; } var timeout = runTimeout(cleanUpNextTick); draining = true; var len = queue.length; while(len) { currentQueue = queue; queue = []; while (++queueIndex < len) { if (currentQueue) { currentQueue[queueIndex].run(); } } queueIndex = -1; len = queue.length; } currentQueue = null; draining = false; runClearTimeout(timeout); } process.nextTick = function (fun) { var args = new Array(arguments.length - 1); if (arguments.length > 1) { for (var i = 1; i < arguments.length; i++) { args[i - 1] = arguments[i]; } } queue.push(new Item(fun, args)); if (queue.length === 1 && !draining) { runTimeout(drainQueue); } }; // v8 likes predictible objects function Item(fun, array) { this.fun = fun; this.array = array; } Item.prototype.run = function () { this.fun.apply(null, this.array); }; process.title = 'browser'; process.browser = true; process.env = {}; process.argv = []; process.version = ''; // empty string to avoid regexp issues process.versions = {}; function noop() {} process.on = noop; process.addListener = noop; process.once = noop; process.off = noop; process.removeListener = noop; process.removeAllListeners = noop; process.emit = noop; process.binding = function (name) { throw new Error('process.binding is not supported'); }; process.cwd = function () { return '/' }; process.chdir = function (dir) { throw new Error('process.chdir is not supported'); }; process.umask = function() { return 0; }; },{}],10:[function(require,module,exports){ module.exports={ "name": "tinyliquid", "main": "./lib/index.js", "files": [ "lib", "target" ], "version": "0.2.33", "description": "A liquid template engine", "keywords": [ "liquid", "template" ], "author": "Zongmin Lei <leizongmin@gmail.com>", "contributors": [ { "name": "Zongmin Lei", "email": "leizongmin@gmail.com" } ], "repository": { "type": "git", "url": "git://github.com/leizongmin/tinyliquid.git" }, "licenses": [ { "type": "MIT", "url": "https://raw.github.com/leizongmin/tinyliquid/master/MIT-License" } ], "bugs": { "url": "https://github.com/leizongmin/tinyliquid/issues" }, "engines": { "node": ">= 0.10.0" }, "dependencies": {}, "devDependencies": { "async": "^1.5.2", "blanket": "^1.2.3", "browserify": "^13.0.0", "ejs": "^2.4.1", "mocha": "^2.4.5", "uglify-js": "^2.6.1" }, "scripts": { "test": "mocha -t 5000", "test:cov": "mocha --require blanket -R html-cov > coverage.html -t 5000", "browserify": "browserify -e ./lib/index.js -s TinyLiquid -o ./target/tinyliquid.js", "uglifyjs": "uglifyjs ./target/tinyliquid.js -o ./target/tinyliquid.min.js", "build": "npm run browserify && npm run uglifyjs" } } },{}]},{},[3])(3) });