From 6135028015e67771c01f75b0479bd75a217fb159 Mon Sep 17 00:00:00 2001 From: harttle Date: Tue, 25 Oct 2016 01:46:37 +0800 Subject: [PATCH] feature: interpolate bracket access, close #14 --- dist/liquid.js | 1206 ++++++++++++++++++++++---------------------- dist/liquid.min.js | 3 +- package.json | 2 +- src/lexical.js | 7 +- src/scope.js | 216 +++++--- tags/layout.js | 2 +- test/scope.js | 157 ++++-- test/syntax.js | 8 +- 8 files changed, 851 insertions(+), 750 deletions(-) diff --git a/dist/liquid.js b/dist/liquid.js index 125d2b79e..ecbc83c2c 100644 --- a/dist/liquid.js +++ b/dist/liquid.js @@ -1,165 +1,151 @@ (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.Liquid = 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': '>', + '"': '"', + "'": ''' +}; +var unescapeMap = { + '&': '&', + '<': '<', + '>': '>', + '"': '"', + ''': "'" +}; + +var filters = { + 'abs': function abs(v) { return Math.abs(v); - }); - liquid.registerFilter('append', function (v, arg) { + }, + 'append': function append(v, arg) { return v + arg; - }); - liquid.registerFilter('capitalize', function (str) { + }, + 'capitalize': function capitalize(str) { return stringify(str).charAt(0).toUpperCase() + str.slice(1); - }); - liquid.registerFilter('ceil', function (v) { + }, + 'ceil': function ceil(v) { return Math.ceil(v); - }); - - //liquid.registerFilter('date', (v, arg) => strftime(arg, v)); - liquid.registerFilter('date', function (v, arg) { + }, + 'date': function date(v, arg) { + if (v === 'now') v = new Date(); return strftime(v, arg); - }); - - liquid.registerFilter('default', function (v, arg) { + }, + 'default': function _default(v, arg) { return arg || v; - }); - liquid.registerFilter('divided_by', function (v, arg) { + }, + 'divided_by': function divided_by(v, arg) { return Math.floor(v / arg); - }); - liquid.registerFilter('downcase', function (v) { + }, + 'downcase': function downcase(v) { return v.toLowerCase(); - }); + }, + 'escape': escape, - var escapeMap = { - '&': '&', - '<': '<', - '>': '>', - '"': '"', - "'": ''' - }; - - function escape(str) { - return stringify(str).replace(/&|<|>|"|'/g, function (m) { - return escapeMap[m]; - }); - } - liquid.registerFilter('escape', escape); - - var unescapeMap = { - '&': '&', - '<': '<', - '>': '>', - '"': '"', - ''': "'" - }; - - function unescape(str) { - return stringify(str).replace(/&(amp|lt|gt|#34|#39);/g, function (m) { - return unescapeMap[m]; - }); - } - liquid.registerFilter('escape_once', function (str) { + 'escape_once': function escape_once(str) { return escape(unescape(str)); - }); - liquid.registerFilter('first', function (v) { + }, + 'first': function first(v) { return v[0]; - }); - liquid.registerFilter('floor', function (v) { + }, + 'floor': function floor(v) { return Math.floor(v); - }); - liquid.registerFilter('join', function (v, arg) { + }, + 'join': function join(v, arg) { return v.join(arg); - }); - liquid.registerFilter('last', function (v) { + }, + 'last': function last(v) { return v[v.length - 1]; - }); - liquid.registerFilter('lstrip', function (v) { + }, + 'lstrip': function lstrip(v) { return stringify(v).replace(/^\s+/, ''); - }); - liquid.registerFilter('map', function (arr, arg) { + }, + 'map': function map(arr, arg) { return arr.map(function (v) { return v[arg]; }); - }); - liquid.registerFilter('minus', bindFixed(function (v, arg) { + }, + 'minus': bindFixed(function (v, arg) { return v - arg; - })); - liquid.registerFilter('modulo', bindFixed(function (v, arg) { + }), + 'modulo': bindFixed(function (v, arg) { return v % arg; - })); - liquid.registerFilter('newline_to_br', function (v) { + }), + 'newline_to_br': function newline_to_br(v) { return v.replace(/\n/g, '
'); - }); - liquid.registerFilter('plus', bindFixed(function (v, arg) { + }, + 'plus': bindFixed(function (v, arg) { return v + arg; - })); - liquid.registerFilter('prepend', function (v, arg) { + }), + 'prepend': function prepend(v, arg) { return arg + v; - }); - liquid.registerFilter('remove', function (v, arg) { + }, + 'remove': function remove(v, arg) { return v.split(arg).join(''); - }); - liquid.registerFilter('remove_first', function (v, l) { + }, + 'remove_first': function remove_first(v, l) { return v.replace(l, ''); - }); - liquid.registerFilter('replace', function (v, pattern, replacement) { + }, + 'replace': function replace(v, pattern, replacement) { return stringify(v).split(pattern).join(replacement); - }); - liquid.registerFilter('replace_first', function (v, arg1, arg2) { + }, + 'replace_first': function replace_first(v, arg1, arg2) { return stringify(v).replace(arg1, arg2); - }); - liquid.registerFilter('reverse', function (v) { + }, + 'reverse': function reverse(v) { return v.reverse(); - }); - liquid.registerFilter('round', function (v, arg) { + }, + 'round': function round(v, arg) { var amp = Math.pow(10, arg || 0); return Math.round(v * amp, arg) / amp; - }); - liquid.registerFilter('rstrip', function (str) { + }, + 'rstrip': function rstrip(str) { return stringify(str).replace(/\s+$/, ''); - }); - liquid.registerFilter('size', function (v) { + }, + 'size': function size(v) { return v.length; - }); - liquid.registerFilter('slice', function (v, begin, length) { + }, + 'slice': function slice(v, begin, length) { return v.substr(begin, length === undefined ? 1 : length); - }); - liquid.registerFilter('sort', function (v, arg) { + }, + 'sort': function sort(v, arg) { return v.sort(arg); - }); - liquid.registerFilter('split', function (v, arg) { + }, + 'split': function split(v, arg) { return stringify(v).split(arg); - }); - liquid.registerFilter('strip', function (v) { + }, + 'strip': function strip(v) { return stringify(v).trim(); - }); - liquid.registerFilter('strip_html', function (v) { + }, + 'strip_html': function strip_html(v) { return stringify(v).replace(/<\/?\s*\w+\s*\/?>/g, ''); - }); - liquid.registerFilter('strip_newlines', function (v) { + }, + 'strip_newlines': function strip_newlines(v) { return stringify(v).replace(/\n/g, ''); - }); - liquid.registerFilter('times', function (v, arg) { + }, + 'times': function times(v, arg) { return v * arg; - }); - liquid.registerFilter('truncate', function (v, l, o) { + }, + 'truncate': function truncate(v, l, o) { v = stringify(v); o = o === undefined ? '...' : o; l = l || 16; if (v.length <= l) return v; return v.substr(0, l - o.length) + o; - }); - liquid.registerFilter('truncatewords', function (v, l, o) { + }, + 'truncatewords': function truncatewords(v, l, o) { if (o === undefined) o = '...'; var arr = v.split(' '); var ret = arr.slice(0, l).join(' '); if (arr.length > l) ret += o; return ret; - }); - liquid.registerFilter('uniq', function (arr) { + }, + 'uniq': function uniq(arr) { var u = {}; return (arr || []).filter(function (val) { if (u.hasOwnProperty(val)) { @@ -168,13 +154,25 @@ module.exports = function (liquid) { u[val] = true; return true; }); - }); - liquid.registerFilter('upcase', function (str) { + }, + 'upcase': function upcase(str) { return stringify(str).toUpperCase(); - }); - liquid.registerFilter('url_encode', encodeURIComponent); + }, + 'url_encode': encodeURIComponent }; +function escape(str) { + return stringify(str).replace(/&|<|>|"|'/g, function (m) { + return escapeMap[m]; + }); +} + +function unescape(str) { + return stringify(str).replace(/&(amp|lt|gt|#34|#39);/g, function (m) { + return unescapeMap[m]; + }); +} + function getFixed(v) { var p = (v + "").split("."); return p.length > 1 ? p[1].length : 0; @@ -196,7 +194,16 @@ function bindFixed(cb) { }; } -},{"./src/strftime.js":14}],2:[function(require,module,exports){ +function registerAll(liquid) { + return _.forOwn(filters, function (func, name) { + return liquid.registerFilter(name, func); + }); +} + +registerAll.filters = filters; +module.exports = registerAll; + +},{"./src/util/strftime.js":17,"./src/util/underscore.js":18}],2:[function(require,module,exports){ 'use strict'; var Scope = require('./src/scope'); @@ -207,7 +214,7 @@ var lexical = require('./src/lexical.js'); var Tag = require('./src/tag.js'); var Filter = require('./src/filter.js'); var Template = require('./src/parser'); -var Expression = require('./src/expression.js'); +var Syntax = require('./src/syntax.js'); var tags = require('./tags'); var filters = require('./filters'); var Promise = require('any-promise'); @@ -331,14 +338,14 @@ function resolvePath(root, path) { } factory.lexical = lexical; -factory.isTruthy = Expression.isTruthy; -factory.isFalsy = Expression.isFalsy; -factory.evalExp = Expression.evalExp; -factory.evalValue = Expression.evalValue; +factory.isTruthy = Syntax.isTruthy; +factory.isFalsy = Syntax.isFalsy; +factory.evalExp = Syntax.evalExp; +factory.evalValue = Syntax.evalValue; module.exports = factory; -},{"./filters":1,"./src/expression.js":8,"./src/filter.js":9,"./src/lexical.js":10,"./src/parser":11,"./src/render.js":12,"./src/scope":13,"./src/tag.js":16,"./src/tokenizer.js":17,"./tags":28,"any-promise":3,"fs":6}],3:[function(require,module,exports){ +},{"./filters":1,"./src/filter.js":8,"./src/lexical.js":9,"./src/parser":11,"./src/render.js":12,"./src/scope":13,"./src/syntax.js":14,"./src/tag.js":15,"./src/tokenizer.js":16,"./tags":29,"any-promise":3,"fs":6}],3:[function(require,module,exports){ 'use strict'; module.exports = require('./register')().Promise; @@ -477,67 +484,8 @@ module.exports = { },{}],8:[function(require,module,exports){ 'use strict'; -var syntax = require('./syntax.js'); var lexical = require('./lexical.js'); - -function evalExp(exp, scope) { - if (!scope) throw new Error('unable to evalExp: scope undefined'); - var operatorREs = lexical.operators, - match; - for (var i = 0; i < operatorREs.length; i++) { - var operatorRE = operatorREs[i]; - var expRE = new RegExp('^(' + lexical.quoteBalanced.source + ')(' + operatorRE.source + ')(' + lexical.quoteBalanced.source + ')$'); - if (match = exp.match(expRE)) { - var l = evalExp(match[1], scope); - var op = syntax.operators[match[2].trim()]; - var r = evalExp(match[3], scope); - return op(l, r); - } - } - - if (match = exp.match(lexical.rangeLine)) { - var low = evalValue(match[1], scope), - high = evalValue(match[2], scope); - var range = []; - for (var j = low; j <= high; j++) { - range.push(j); - } - return range; - } - - return evalValue(exp, scope); -} - -function evalValue(str, scope) { - str = str && str.trim(); - if (!str) return undefined; - - if (lexical.isLiteral(str)) { - return lexical.parseLiteral(str); - } - if (lexical.isVariable(str)) { - return scope.get(str); - } -} - -function isTruthy(val) { - if (val instanceof Array) return !!val.length; - return !!val; -} - -function isFalsy(val) { - return !isTruthy(val); -} - -module.exports = { - evalExp: evalExp, evalValue: evalValue, isTruthy: isTruthy, isFalsy: isFalsy -}; - -},{"./lexical.js":10,"./syntax.js":15}],9:[function(require,module,exports){ -'use strict'; - -var lexical = require('./lexical.js'); -var Exp = require('./expression.js'); +var Syntax = require('./syntax.js'); var valueRE = new RegExp('' + lexical.value.source, 'g'); @@ -547,7 +495,7 @@ module.exports = function () { var _filterInstance = { render: function render(output, scope) { var args = this.args.map(function (arg) { - return Exp.evalValue(arg, scope); + return Syntax.evalValue(arg, scope); }); args.unshift(output); return this.filter.apply(null, args); @@ -597,20 +545,23 @@ module.exports = function () { }; }; -},{"./expression.js":8,"./lexical.js":10}],10:[function(require,module,exports){ +},{"./lexical.js":9,"./syntax.js":14}],9:[function(require,module,exports){ 'use strict'; // quote related var singleQuoted = /'[^']*'/; var doubleQuoted = /"[^"]*"/; -var quoteBalanced = new RegExp('(?:' + singleQuoted.source + '|' + doubleQuoted.source + '|[^\'"])*'); +var quoted = new RegExp(singleQuoted.source + '|' + doubleQuoted.source); +var quoteBalanced = new RegExp('(?:' + quoted.source + '|[^\'"])*'); -var number = /(?:-?\d+\.?\d*|\.?\d+)/; +// basic types +var integer = /-?\d+/; +var number = /-?\d+\.?\d*|\.?\d+/; var bool = /true|false/; -var identifier = /[a-zA-Z_$][a-zA-Z_$0-9]*/; -var subscript = /\[\d+\]/; -var quoted = new RegExp('(?:' + singleQuoted.source + '|' + doubleQuoted.source + ')'); +// peoperty access +var identifier = /[\w-]+/; +var subscript = new RegExp('\\[(?:' + quoted.source + '|[\\w-\\.]+)\\]'); var literal = new RegExp('(?:' + quoted.source + '|' + bool.source + '|' + number.source + ')'); var variable = new RegExp(identifier.source + '(?:\\.' + identifier.source + '|' + subscript.source + ')*'); @@ -619,12 +570,13 @@ var rangeLimit = new RegExp('(?:' + variable.source + '|' + number.source + ')') var range = new RegExp('\\(' + rangeLimit.source + '\\.\\.' + rangeLimit.source + '\\)'); var rangeCapture = new RegExp('\\((' + rangeLimit.source + ')\\.\\.(' + rangeLimit.source + ')\\)'); -var value = new RegExp('(?:' + literal.source + '|' + variable.source + '|' + range.source + ')'); +var value = new RegExp('(?:' + variable.source + '|' + literal.source + '|' + range.source + ')'); // hash related var hash = new RegExp('(?:' + identifier.source + ')\\s*:\\s*(?:' + value.source + ')'); var hashCapture = new RegExp('(' + identifier.source + ')\\s*:\\s*(' + value.source + ')', 'g'); +// full match var tagLine = new RegExp('^\\s*(' + identifier.source + ')\\s*(.*)\\s*$'); var literalLine = new RegExp('^' + literal.source + '$', 'i'); var variableLine = new RegExp('^' + variable.source + '$'); @@ -632,6 +584,7 @@ var numberLine = new RegExp('^' + number.source + '$'); var boolLine = new RegExp('^' + bool.source + '$', 'i'); var quotedLine = new RegExp('^' + quoted.source + '$'); var rangeLine = new RegExp('^' + rangeCapture.source + '$'); +var integerLine = new RegExp('^' + integer.source + '$'); // filter related var valueList = new RegExp(value.source + '(\\s*,\\s*' + value.source + ')*'); @@ -641,6 +594,10 @@ var filterLine = new RegExp('^' + filterCapture.source + '$'); var operators = [/\s+or\s+/, /\s+and\s+/, /==|!=|<=|>=|<|>|\s+contains\s+/]; +function isInteger(str) { + return integerLine.test(str); +} + function isLiteral(str) { return literalLine.test(str); } @@ -653,6 +610,10 @@ function isVariable(str) { return variableLine.test(str); } +function matchValue(str) { + return value.exec(str); +} + function parseLiteral(str) { var res; if (res = str.match(numberLine)) { @@ -667,14 +628,49 @@ function parseLiteral(str) { } module.exports = { - quoted: quoted, number: number, bool: bool, literal: literal, filter: filter, + quoted: quoted, number: number, bool: bool, literal: literal, filter: filter, integer: integer, hash: hash, hashCapture: hashCapture, range: range, rangeCapture: rangeCapture, identifier: identifier, value: value, quoteBalanced: quoteBalanced, operators: operators, quotedLine: quotedLine, numberLine: numberLine, boolLine: boolLine, rangeLine: rangeLine, literalLine: literalLine, filterLine: filterLine, tagLine: tagLine, - isLiteral: isLiteral, isVariable: isVariable, parseLiteral: parseLiteral, isRange: isRange + isLiteral: isLiteral, isVariable: isVariable, parseLiteral: parseLiteral, isRange: isRange, matchValue: matchValue, isInteger: isInteger }; +},{}],10:[function(require,module,exports){ +'use strict'; + +var operators = { + '==': function _(l, r) { + return l == r; + }, + '!=': function _(l, r) { + return l != r; + }, + '>': function _(l, r) { + return l > r; + }, + '<': function _(l, r) { + return l < r; + }, + '>=': function _(l, r) { + return l >= r; + }, + '<=': function _(l, r) { + return l <= r; + }, + 'contains': function contains(l, r) { + return l.indexOf(r) > -1; + }, + 'and': function and(l, r) { + return l && r; + }, + 'or': function or(l, r) { + return l || r; + } +}; + +module.exports = operators; + },{}],11:[function(require,module,exports){ 'use strict'; @@ -750,7 +746,7 @@ module.exports = function (Tag, Filter) { } function parseOutput(str) { - var match = lexical.value.exec(str); + var match = lexical.matchValue(str); if (!match) throw new Error('illegal output string: ' + str); var initial = match[0]; @@ -780,10 +776,10 @@ module.exports = function (Tag, Filter) { }; }; -},{"./error.js":7,"./lexical.js":10}],12:[function(require,module,exports){ +},{"./error.js":7,"./lexical.js":9}],12:[function(require,module,exports){ 'use strict'; -var Exp = require('./expression.js'); +var Syntax = require('./syntax.js'); var Promise = require('any-promise'); var render = { @@ -801,7 +797,7 @@ var render = { // It's fundamentally equivalent to the following... // emptyPromise.then(renderTag(template0).then(renderTag(template1).then(renderTag(template2)... var lastPromise = templates.reduce(function (promise, template) { - return promise.then(function (partial) { + return promise.then(function () { if (scope.safeGet('forloop.skip')) { return Promise.resolve(''); } @@ -869,7 +865,7 @@ var render = { evalOutput: function evalOutput(template, scope, opts) { if (!scope) throw new Error('unable to evalOutput: scope undefined'); - var val = Exp.evalExp(template.initial, scope); + var val = Syntax.evalExp(template.initial, scope); template.filters.some(function (filter) { if (filter.error) { if (opts.strict_filters) { @@ -903,90 +899,376 @@ function stringify(val) { module.exports = factory; -},{"./expression.js":8,"any-promise":3}],13:[function(require,module,exports){ +},{"./syntax.js":14,"any-promise":3}],13:[function(require,module,exports){ 'use strict'; +var _ = require('./util/underscore.js'); +var lexical = require('./lexical.js'); + var Scope = { - safeGet: function safeGet(str) { - var i; - // get all - if (str === undefined) { - var ctx = {}; - for (i = this.scopes.length - 1; i >= 0; i--) { - var scp = this.scopes[i]; - for (var k in scp) { - if (scp.hasOwnProperty(k)) { - ctx[k] = scp[k]; - } - } - } - return ctx; - } - // get one path - for (i = this.scopes.length - 1; i >= 0; i--) { - var v = getPropertyByPath(this.scopes[i], str); - if (v !== undefined) return v; - } - }, - get: function get(str) { - var val = this.safeGet(str); - if (val === undefined && this.opts.strict) { - throw new Error('[strict_variables] undefined variable: ' + str); - } - return val; - }, - set: function set(k, v) { - setPropertyByPath(this.scopes[this.scopes.length - 1], k, v); - return this; - }, - push: function push(ctx) { - if (!ctx) throw new Error('trying to push ' + ctx + ' into scopes'); - return this.scopes.push(ctx); - }, - pop: function pop() { - return this.scopes.pop(); - } + safeGet: function safeGet(str) { + var i; + // get all + if (str === undefined) { + var ctx = {}; + for (i = this.scopes.length - 1; i >= 0; i--) { + var scp = this.scopes[i]; + for (var k in scp) { + if (scp.hasOwnProperty(k)) { + ctx[k] = scp[k]; + } + } + } + return ctx; + } + // get one path + for (i = this.scopes.length - 1; i >= 0; i--) { + var v = this.getPropertyByPath(this.scopes[i], str); + if (v !== undefined) return v; + } + }, + get: function get(str) { + var val = this.safeGet(str); + if (val === undefined && this.opts.strict) { + throw new Error('[strict_variables] undefined variable: ' + str); + } + return val; + }, + set: function set(k, v) { + this.setPropertyByPath(this.scopes[this.scopes.length - 1], k, v); + return this; + }, + push: function push(ctx) { + if (!ctx) throw new Error('trying to push ' + ctx + ' into scopes'); + return this.scopes.push(ctx); + }, + pop: function pop() { + return this.scopes.pop(); + }, + + setPropertyByPath: function setPropertyByPath(obj, path, val) { + if (_.isString(path)) { + var paths = path.replace(/\[/g, '.').replace(/\]/g, '').split('.'); + for (var i = 0; i < paths.length; i++) { + var key = paths[i]; + if (i === paths.length - 1) { + return obj[key] = val; + } + if (undefined === obj[key]) obj[key] = {}; + // case for readonly objects + obj = obj[key] || {}; + } + return obj; + } + return obj[path] = val; + }, + + getPropertyByPath: function getPropertyByPath(obj, path) { + if (_.isString(path) && path.length) { + var paths = this.propertyAccessSeq(path); + paths.forEach(function (p) { + return obj = obj && obj[p]; + }); + return obj; + } + return obj[path]; + }, + + /* + * Parse property access sequence from access string + * @example + * accessSeq("foo.bar") // ['foo', 'bar'] + * accessSeq("foo['bar']") // ['foo', 'bar'] + * accessSeq("foo['b]r']") // ['foo', 'b]r'] + * accessSeq("foo[bar.coo]") // ['foo', 'bar'], for bar.coo == 'bar' + */ + propertyAccessSeq: function propertyAccessSeq(str) { + var seq = [], + name = ''; + for (var i = 0; i < str.length; i++) { + if (str[i] === '[') { + seq.push(name); + name = ''; + + var delemiter = str[i + 1]; + // foo[bar.coo] + if (delemiter !== "'" && delemiter !== '"') { + var j = matchRightBracket(str, i + 1); + if (j === -1) { + throw new Error('unbalanced []: ' + str); + } + name = str.slice(i + 1, j); + // foo[1] + if (lexical.isInteger(name)) { + seq.push(name); + } + // foo["bar"] + else { + seq.push(this.get(name)); + } + name = ''; + i = j; + } + // foo["bar"] + else { + var j = str.indexOf(delemiter, i + 2); + if (j === -1) { + throw new Error('unbalanced ' + delemiter + ': ' + str); + } + name = str.slice(i + 2, j); + seq.push(name); + name = ''; + i = j + 1; + } + } + // foo.bar + else if (str[i] === ".") { + seq.push(name); + name = ''; + } + //foo.bar + else { + name += str[i]; + } + } + if (name.length) seq.push(name); + return seq; + } }; -function setPropertyByPath(obj, path, val) { - if (path instanceof String || typeof path === 'string') { - var paths = path.replace(/\[/g, '.').replace(/\]/g, '').split('.'); - for (var i = 0; i < paths.length; i++) { - var key = paths[i]; - if (i === paths.length - 1) { - return obj[key] = val; - } - if (undefined === obj[key]) obj[key] = {}; - // case for readonly objects - obj = obj[key] || {}; - } - return obj; - } - return obj[path] = val; -} - -function getPropertyByPath(obj, path) { - if (path instanceof String || typeof path === 'string') { - var paths = path.replace(/\[/g, '.').replace(/\]/g, '').split('.'); - paths.forEach(function (p) { - return obj = obj && obj[p]; - }); - return obj; - } - return obj[path]; +function matchRightBracket(str, begin) { + var stack = 1; // count of '[' - count of ']' + for (var i = begin; i < str.length; i++) { + if (str[i] === '[') { + stack++; + } + if (str[i] === ']') { + stack--; + if (stack === 0) { + return i; + } + } + } + return -1; } exports.factory = function (_ctx, opts) { - opts = opts || {}; - opts.strict = opts.strict || false; + opts = opts || {}; + opts.strict = opts.strict || false; - var scope = Object.create(Scope); - scope.opts = opts; - scope.scopes = [_ctx || {}]; - return scope; + var scope = Object.create(Scope); + scope.opts = opts; + scope.scopes = [_ctx || {}]; + return scope; }; -},{}],14:[function(require,module,exports){ +},{"./lexical.js":9,"./util/underscore.js":18}],14:[function(require,module,exports){ +'use strict'; + +var operators = require('./operators.js'); +var lexical = require('./lexical.js'); + +function evalExp(exp, scope) { + if (!scope) throw new Error('unable to evalExp: scope undefined'); + var operatorREs = lexical.operators, + match; + for (var i = 0; i < operatorREs.length; i++) { + var operatorRE = operatorREs[i]; + var expRE = new RegExp('^(' + lexical.quoteBalanced.source + ')(' + operatorRE.source + ')(' + lexical.quoteBalanced.source + ')$'); + if (match = exp.match(expRE)) { + var l = evalExp(match[1], scope); + var op = operators[match[2].trim()]; + var r = evalExp(match[3], scope); + return op(l, r); + } + } + + if (match = exp.match(lexical.rangeLine)) { + var low = evalValue(match[1], scope), + high = evalValue(match[2], scope); + var range = []; + for (var j = low; j <= high; j++) { + range.push(j); + } + return range; + } + + return evalValue(exp, scope); +} + +function evalValue(str, scope) { + str = str && str.trim(); + if (!str) return undefined; + + if (lexical.isLiteral(str)) { + return lexical.parseLiteral(str); + } + if (lexical.isVariable(str)) { + return scope.get(str); + } +} + +function isTruthy(val) { + if (val instanceof Array) return !!val.length; + return !!val; +} + +function isFalsy(val) { + return !isTruthy(val); +} + +module.exports = { + evalExp: evalExp, evalValue: evalValue, isTruthy: isTruthy, isFalsy: isFalsy +}; + +},{"./lexical.js":9,"./operators.js":10}],15:[function(require,module,exports){ +'use strict'; + +var lexical = require('./lexical.js'); +var Promise = require('any-promise'); +var Syntax = require('./syntax.js'); + +function hash(markup, scope) { + var obj = {}, + match; + lexical.hashCapture.lastIndex = 0; + while (match = lexical.hashCapture.exec(markup)) { + var k = match[1], + v = match[2]; + obj[k] = Syntax.evalValue(v, scope); + } + return obj; +} + +module.exports = function () { + var tagImpls = {}; + + var _tagInstance = { + render: function render(scope, register) { + var reg = register[this.name]; + if (!reg) reg = register[this.name] = {}; + var obj = hash(this.token.args, scope); + return this.tagImpl.render && this.tagImpl.render(scope, obj, reg) || Promise.resolve(''); + }, + parse: function parse(token, tokens) { + this.type = 'tag'; + this.token = token; + this.name = token.name; + + var tagImpl = tagImpls[this.name]; + if (!tagImpl) throw new Error('tag ' + this.name + ' not found'); + this.tagImpl = Object.create(tagImpl); + if (this.tagImpl.parse) { + this.tagImpl.parse(token, tokens); + } + } + }; + + function register(name, tag) { + tagImpls[name] = tag; + } + + function construct(token, tokens) { + var instance = Object.create(_tagInstance); + instance.parse(token, tokens); + return instance; + } + + function clear() { + tagImpls = {}; + } + + return { + construct: construct, register: register, clear: clear + }; +}; + +},{"./lexical.js":9,"./syntax.js":14,"any-promise":3}],16:[function(require,module,exports){ +'use strict'; + +var lexical = require('./lexical.js'); +var TokenizationError = require('./error.js').TokenizationError; + +function parse(html) { + var tokens = []; + if (!html) return tokens; + + var syntax = /({%(.*?)%})|({{(.*?)}})/g; + var result, htmlFragment, token; + var lastMatchEnd = 0, + lastMatchBegin = -1, + parsedLinesCount = 0; + + while ((result = syntax.exec(html)) !== null) { + // passed html fragments + if (result.index > lastMatchEnd) { + htmlFragment = html.slice(lastMatchEnd, result.index); + tokens.push({ + type: 'html', + raw: htmlFragment, + value: htmlFragment + }); + } + // tag appeared + if (result[1]) { + token = factory('tag', 1, result); + + var match = token.value.match(lexical.tagLine); + if (!match) { + throw new TokenizationError('illegal tag: ' + token.raw, token.input, token.line); + } + token.name = match[1]; + token.args = match[2]; + + tokens.push(token); + } + // output + else { + token = factory('output', 3, result); + tokens.push(token); + } + lastMatchEnd = syntax.lastIndex; + } + + // remaining html + if (html.length > lastMatchEnd) { + htmlFragment = html.slice(lastMatchEnd, html.length); + tokens.push({ + type: 'html', + raw: htmlFragment, + value: htmlFragment + }); + } + return tokens; + + function factory(type, offset, match) { + return { + type: type, + raw: match[offset], + value: match[offset + 1].trim(), + line: getLineNum(match), + input: getLineContent(match) + }; + } + + function getLineContent(match) { + var idx1 = match.input.lastIndexOf('\n', match.index); + var idx2 = match.input.indexOf('\n', match.index); + if (idx2 === -1) idx2 = match.input.length; + return match.input.slice(idx1 + 1, idx2); + } + + function getLineNum(match) { + var lines = match.input.slice(lastMatchBegin + 1, match.index).split('\n'); + parsedLinesCount += lines.length - 1; + lastMatchBegin = match.index; + return parsedLinesCount + 1; + } +} + +exports.parse = parse; + +},{"./error.js":7,"./lexical.js":9}],17:[function(require,module,exports){ "use strict"; var monthNames = ["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"]; @@ -1065,18 +1347,6 @@ var _date = { } }; -var _obj = { - values_of: function values_of(obj) { - var values = []; - for (var k in obj) { - if (obj.hasOwnProperty(k)) { - values.push(obj[k]); - } - } - return values; - } -}; - var _number = { pad: function pad(value, size, ch) { if (!ch) ch = '0'; @@ -1193,151 +1463,6 @@ var format_codes = { format_codes.h = format_codes.b; format_codes.N = format_codes.L; -// * r stands for regex, p stands for parser -// * all parseInt calls have to have the base supplied as the second -// parameter, otherwise they will default to octal when parsing numbers -// with leading zeros. This is most evident when parsing a date with 08 as -// the minutes / year as 08 is an invalid octal number, and so returns 0 -var parse_codes = { - a: { - r: "(?:" + dayNamesShort.join("|") + ")" - }, - A: { - r: "(?:" + dayNames.join("|") + ")" - }, - b: { - r: "(" + monthNamesShort.join("|") + ")", - p: function p(data) { - this.month = $.inArray(data, monthNamesShort); - } - }, - B: { - r: "(" + monthNames.join("|") + ")", - p: function p(data) { - this.month = $.inArray(data, monthNames); - } - }, - C: { - r: "(\\d{1,2})", - p: function p(d) { - this.century = parseInt(d, 10); - } - }, - d: { - r: "(\\d{1,2})", - p: function p(d) { - this.day = parseInt(d, 10); - } - }, - H: { - r: "(\\d{1,2})", - p: function p(d) { - this.hour = parseInt(d, 10); - } - }, - // This gives only the day. Parsing of the month happens at the end because - // we also need the year - j: { - r: "(\\d{1,3})", - p: function p(d) { - this.day = parseInt(d, 10); - } - }, - L: { - r: "(\\d{3})", - p: function p(d) { - this.milliseconds = parseInt(d, 10); - } - }, - m: { - r: "(\\d{1,2})", - p: function p(d) { - this.month = parseInt(d, 10) - 1; - } - }, - M: { - r: "(\\d{2})", - p: function p(d) { - this.minute = parseInt(d, 10); - } - }, - p: { - r: "(AM|PM)", - p: function p(d) { - if (d == 'AM') { - if (this.hour == 12) { - this.hour = 0; - } - } else { - if (this.hour < 12) { - this.hour += 12; - } - } - } - }, - P: { - r: "(am|pm)", - p: function p(d) { - if (d == 'am') { - if (this.hour == 12) { - this.hour = 0; - } - } else { - if (this.hour < 12) { - this.hour += 12; - } - } - } - }, - q: { - r: "(?:" + _obj.values_of(suffixes).join('|') + ")" - }, - S: { - r: "(\\d{2})", - p: function p(d) { - this.second = parseInt(d, 10); - } - }, - y: { - r: "(\\d{1,2})", - p: function p(d) { - this.year = parseInt(d, 10); - } - }, - Y: { - r: "(\\d{4})", - p: function p(d) { - this.century = Math.floor(parseInt(d, 10) / 100); - this.year = parseInt(d, 10) % 100; - } - }, - z: { // "Z", "+05:00", "+0500" all acceptable. - r: "(Z|[+-]\\d{2}:?\\d{2})", - p: function p(d) { - // UTC, no offset. - if (d == "Z") { - this.zone = 0; - return; - } - - var seconds = parseInt(d[0] + d[1] + d[2], 10) * 3600; // e.g., "+05" or "-08" - if (d[3] == ":") { - // "+HH:MM" is preferred iso8601 format - seconds += parseInt(d[4] + d[5], 10) * 60; - } else { - // "+HHMM" is frequently used, though. - seconds += parseInt(d[3] + d[4], 10) * 60; - } - this.zone = seconds; - } - } -}; -parse_codes.e = parse_codes.d; -parse_codes.h = parse_codes.b; -parse_codes.I = parse_codes.H; -parse_codes.k = parse_codes.H; -parse_codes.l = parse_codes.H; - var strftime = function strftime(d, format) { // I used to use string split with a regex and a capturing block here, // which I thought was really clever, but apparently this exact feature is @@ -1377,189 +1502,40 @@ var strftime = function strftime(d, format) { module.exports = strftime; -},{}],15:[function(require,module,exports){ +},{}],18:[function(require,module,exports){ 'use strict'; -var operators = { - '==': function _(l, r) { - return l == r; - }, - '!=': function _(l, r) { - return l != r; - }, - '>': function _(l, r) { - return l > r; - }, - '<': function _(l, r) { - return l < r; - }, - '>=': function _(l, r) { - return l >= r; - }, - '<=': function _(l, r) { - return l <= r; - }, - 'contains': function contains(l, r) { - return l.indexOf(r) > -1; - }, - 'and': function and(l, r) { - return l && r; - }, - 'or': function or(l, r) { - return l || r; - } -}; - -exports.operators = operators; - -},{}],16:[function(require,module,exports){ -'use strict'; - -var lexical = require('./lexical.js'); -var Promise = require('any-promise'); -var Exp = require('./expression.js'); - -function hash(markup, scope) { - var obj = {}, - match; - lexical.hashCapture.lastIndex = 0; - while (match = lexical.hashCapture.exec(markup)) { - var k = match[1], - v = match[2]; - obj[k] = Exp.evalValue(v, scope); - } - return obj; +/* + * Checks if value is classified as a String primitive or object. + * @param {any} value The value to check. + * @return {Boolean} Returns true if value is a string, else false. + */ +function isString(value) { + return value instanceof String || typeof value === 'string'; } -module.exports = function () { - var tagImpls = {}; - - var _tagInstance = { - render: function render(scope, register) { - var reg = register[this.name]; - if (!reg) reg = register[this.name] = {}; - var obj = hash(this.token.args, scope); - return this.tagImpl.render && this.tagImpl.render(scope, obj, reg) || Promise.resolve(''); - }, - parse: function parse(token, tokens) { - this.type = 'tag'; - this.token = token; - this.name = token.name; - - var tagImpl = tagImpls[this.name]; - if (!tagImpl) throw new Error('tag ' + this.name + ' not found'); - this.tagImpl = Object.create(tagImpl); - if (this.tagImpl.parse) { - this.tagImpl.parse(token, tokens); - } +/* + * Iterates over own enumerable string keyed properties of an object and invokes iteratee for each property. + * The iteratee is invoked with three arguments: (value, key, object). + * Iteratee functions may exit iteration early by explicitly returning false. + * @param {Object} object The object to iterate over. + * @param {Function} iteratee The function invoked per iteration. + * @return {Object} Returs object. + */ +function forOwn(object, iteratee) { + object = object || {}; + for (var k in object) { + if (object.hasOwnProperty(k)) { + if (iteratee(object[k], k, object) === false) break; } - }; - - function register(name, tag) { - tagImpls[name] = tag; - } - - function construct(token, tokens) { - var instance = Object.create(_tagInstance); - instance.parse(token, tokens); - return instance; - } - - function clear() { - tagImpls = {}; - } - - return { - construct: construct, register: register, clear: clear - }; -}; - -},{"./expression.js":8,"./lexical.js":10,"any-promise":3}],17:[function(require,module,exports){ -'use strict'; - -var lexical = require('./lexical.js'); -var TokenizationError = require('./error.js').TokenizationError; - -function parse(html) { - var tokens = []; - if (!html) return tokens; - - var syntax = /({%(.*?)%})|({{(.*?)}})/g; - var result, htmlFragment, token; - var lastMatchEnd = 0, - lastMatchBegin = -1, - parsedLinesCount = 0; - - while ((result = syntax.exec(html)) !== null) { - // passed html fragments - if (result.index > lastMatchEnd) { - htmlFragment = html.slice(lastMatchEnd, result.index); - tokens.push({ - type: 'html', - raw: htmlFragment, - value: htmlFragment - }); - } - // tag appeared - if (result[1]) { - token = factory('tag', 1, result); - - var match = token.value.match(lexical.tagLine); - if (!match) { - throw new TokenizationError('illegal tag: ' + token.raw, token.input, token.line); - } - token.name = match[1]; - token.args = match[2]; - - tokens.push(token); - } - // output - else { - token = factory('output', 3, result); - tokens.push(token); - } - lastMatchEnd = syntax.lastIndex; - } - - // remaining html - if (html.length > lastMatchEnd) { - htmlFragment = html.slice(lastMatchEnd, html.length); - tokens.push({ - type: 'html', - raw: htmlFragment, - value: htmlFragment - }); - } - return tokens; - - function factory(type, offset, match) { - return { - type: type, - raw: match[offset], - value: match[offset + 1].trim(), - line: getLineNum(match), - input: getLineContent(match) - }; - } - - function getLineContent(match) { - var idx1 = match.input.lastIndexOf('\n', match.index); - var idx2 = match.input.indexOf('\n', match.index); - if (idx2 === -1) idx2 = match.input.length; - return match.input.slice(idx1 + 1, idx2); - } - - function getLineNum(match) { - var lines = match.input.slice(lastMatchBegin + 1, match.index).split('\n'); - parsedLinesCount += lines.length - 1; - lastMatchBegin = match.index; - return parsedLinesCount + 1; } + return object; } -exports.parse = parse; +exports.isString = isString; +exports.forOwn = forOwn; -},{"./error.js":7,"./lexical.js":10}],18:[function(require,module,exports){ +},{}],19:[function(require,module,exports){ 'use strict'; var Liquid = require('..'); @@ -1576,14 +1552,14 @@ module.exports = function (liquid) { this.key = match[1]; this.value = match[2]; }, - render: function render(scope, hash) { + render: function render(scope) { scope.set(this.key, liquid.evalOutput(this.value, scope)); return Promise.resolve(''); } }); }; -},{"..":2,"any-promise":3}],19:[function(require,module,exports){ +},{"..":2,"any-promise":3}],20:[function(require,module,exports){ 'use strict'; var Liquid = require('..'); @@ -1622,7 +1598,7 @@ module.exports = function (liquid) { }); }; -},{"..":2}],20:[function(require,module,exports){ +},{"..":2}],21:[function(require,module,exports){ 'use strict'; var Liquid = require('..'); @@ -1673,7 +1649,7 @@ module.exports = function (liquid) { }); }; -},{"..":2}],21:[function(require,module,exports){ +},{"..":2}],22:[function(require,module,exports){ 'use strict'; module.exports = function (liquid) { @@ -1691,7 +1667,7 @@ module.exports = function (liquid) { }); }; -},{}],22:[function(require,module,exports){ +},{}],23:[function(require,module,exports){ 'use strict'; var Liquid = require('..'); @@ -1738,7 +1714,7 @@ module.exports = function (liquid) { }); }; -},{"..":2,"any-promise":3}],23:[function(require,module,exports){ +},{"..":2,"any-promise":3}],24:[function(require,module,exports){ 'use strict'; var Liquid = require('..'); @@ -1760,7 +1736,7 @@ module.exports = function (liquid) { }); }; -},{"..":2}],24:[function(require,module,exports){ +},{"..":2}],25:[function(require,module,exports){ 'use strict'; var Liquid = require('..'); @@ -1877,7 +1853,7 @@ module.exports = function (liquid) { }); }; -},{"..":2,"any-promise":3}],25:[function(require,module,exports){ +},{"..":2,"any-promise":3}],26:[function(require,module,exports){ 'use strict'; var Liquid = require('..'); @@ -1931,7 +1907,7 @@ module.exports = function (liquid) { }); }; -},{"..":2}],26:[function(require,module,exports){ +},{"..":2}],27:[function(require,module,exports){ 'use strict'; var Liquid = require('..'); @@ -1967,7 +1943,7 @@ module.exports = function (liquid) { }); }; -},{"..":2}],27:[function(require,module,exports){ +},{"..":2}],28:[function(require,module,exports){ 'use strict'; var Liquid = require('..'); @@ -1989,7 +1965,7 @@ module.exports = function (liquid) { }); }; -},{"..":2}],28:[function(require,module,exports){ +},{"..":2}],29:[function(require,module,exports){ "use strict"; module.exports = function (engine) { @@ -2009,7 +1985,7 @@ module.exports = function (engine) { require("./unless.js")(engine); }; -},{"./assign.js":18,"./capture.js":19,"./case.js":20,"./comment.js":21,"./cycle.js":22,"./decrement.js":23,"./for.js":24,"./if.js":25,"./include.js":26,"./increment.js":27,"./layout.js":29,"./raw.js":30,"./tablerow.js":31,"./unless.js":32}],29:[function(require,module,exports){ +},{"./assign.js":19,"./capture.js":20,"./case.js":21,"./comment.js":22,"./cycle.js":23,"./decrement.js":24,"./for.js":25,"./if.js":26,"./include.js":27,"./increment.js":28,"./layout.js":30,"./raw.js":31,"./tablerow.js":32,"./unless.js":33}],30:[function(require,module,exports){ 'use strict'; var Liquid = require('..'); @@ -2052,7 +2028,7 @@ module.exports = function (liquid) { var _this = this; var match = /\w+/.exec(token.args); - this.block = match ? match[0] : ''; + this.block = match ? match[0] : 'anonymous'; this.tpls = []; var p, @@ -2084,7 +2060,7 @@ module.exports = function (liquid) { }); }; -},{"..":2,"any-promise":3}],30:[function(require,module,exports){ +},{"..":2,"any-promise":3}],31:[function(require,module,exports){ 'use strict'; var Promise = require('any-promise'); @@ -2114,7 +2090,7 @@ module.exports = function (liquid) { }); }; -},{"any-promise":3}],31:[function(require,module,exports){ +},{"any-promise":3}],32:[function(require,module,exports){ 'use strict'; var Liquid = require('..'); @@ -2215,7 +2191,7 @@ module.exports = function (liquid) { }); }; -},{"..":2,"any-promise":3}],32:[function(require,module,exports){ +},{"..":2,"any-promise":3}],33:[function(require,module,exports){ 'use strict'; var Liquid = require('..'); diff --git a/dist/liquid.min.js b/dist/liquid.min.js index 1d50782a4..ec36a6737 100644 --- a/dist/liquid.min.js +++ b/dist/liquid.min.js @@ -1,2 +1 @@ -!function(e){if("object"==typeof exports&&"undefined"!=typeof module)module.exports=e();else if("function"==typeof define&&define.amd)define([],e);else{var r;r="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof self?self:this,r.Liquid=e()}}(function(){return function e(r,t,n){function i(o,a){if(!t[o]){if(!r[o]){var u="function"==typeof require&&require;if(!a&&u)return u(o,!0);if(s)return s(o,!0);var c=new Error("Cannot find module '"+o+"'");throw c.code="MODULE_NOT_FOUND",c}var l=t[o]={exports:{}};r[o][0].call(l.exports,function(e){var t=r[o][1][e];return i(t?t:e)},l,l.exports,e,r,t,n)}return t[o].exports}for(var s="function"==typeof require&&require,o=0;o1?r[1].length:0}function n(e,r){return Math.max(t(e),t(r))}function i(e){return e=e||"",e+""}function s(e){return function(r,t){var i=n(r,t);return e(r,t).toFixed(i)}}var o=e("./src/strftime.js");r.exports=function(e){function r(e){return i(e).replace(/&|<|>|"|'/g,function(e){return n[e]})}function t(e){return i(e).replace(/&(amp|lt|gt|#34|#39);/g,function(e){return a[e]})}e.registerFilter("abs",function(e){return Math.abs(e)}),e.registerFilter("append",function(e,r){return e+r}),e.registerFilter("capitalize",function(e){return i(e).charAt(0).toUpperCase()+e.slice(1)}),e.registerFilter("ceil",function(e){return Math.ceil(e)}),e.registerFilter("date",function(e,r){return o(e,r)}),e.registerFilter("default",function(e,r){return r||e}),e.registerFilter("divided_by",function(e,r){return Math.floor(e/r)}),e.registerFilter("downcase",function(e){return e.toLowerCase()});var n={"&":"&","<":"<",">":">",'"':""","'":"'"};e.registerFilter("escape",r);var a={"&":"&","<":"<",">":">",""":'"',"'":"'"};e.registerFilter("escape_once",function(e){return r(t(e))}),e.registerFilter("first",function(e){return e[0]}),e.registerFilter("floor",function(e){return Math.floor(e)}),e.registerFilter("join",function(e,r){return e.join(r)}),e.registerFilter("last",function(e){return e[e.length-1]}),e.registerFilter("lstrip",function(e){return i(e).replace(/^\s+/,"")}),e.registerFilter("map",function(e,r){return e.map(function(e){return e[r]})}),e.registerFilter("minus",s(function(e,r){return e-r})),e.registerFilter("modulo",s(function(e,r){return e%r})),e.registerFilter("newline_to_br",function(e){return e.replace(/\n/g,"
")}),e.registerFilter("plus",s(function(e,r){return e+r})),e.registerFilter("prepend",function(e,r){return r+e}),e.registerFilter("remove",function(e,r){return e.split(r).join("")}),e.registerFilter("remove_first",function(e,r){return e.replace(r,"")}),e.registerFilter("replace",function(e,r,t){return i(e).split(r).join(t)}),e.registerFilter("replace_first",function(e,r,t){return i(e).replace(r,t)}),e.registerFilter("reverse",function(e){return e.reverse()}),e.registerFilter("round",function(e,r){var t=Math.pow(10,r||0);return Math.round(e*t,r)/t}),e.registerFilter("rstrip",function(e){return i(e).replace(/\s+$/,"")}),e.registerFilter("size",function(e){return e.length}),e.registerFilter("slice",function(e,r,t){return e.substr(r,void 0===t?1:t)}),e.registerFilter("sort",function(e,r){return e.sort(r)}),e.registerFilter("split",function(e,r){return i(e).split(r)}),e.registerFilter("strip",function(e){return i(e).trim()}),e.registerFilter("strip_html",function(e){return i(e).replace(/<\/?\s*\w+\s*\/?>/g,"")}),e.registerFilter("strip_newlines",function(e){return i(e).replace(/\n/g,"")}),e.registerFilter("times",function(e,r){return e*r}),e.registerFilter("truncate",function(e,r,t){return e=i(e),t=void 0===t?"...":t,r=r||16,e.length<=r?e:e.substr(0,r-t.length)+t}),e.registerFilter("truncatewords",function(e,r,t){void 0===t&&(t="...");var n=e.split(" "),i=n.slice(0,r).join(" ");return n.length>r&&(i+=t),i}),e.registerFilter("uniq",function(e){var r={};return(e||[]).filter(function(e){return r.hasOwnProperty(e)?!1:(r[e]=!0,!0)})}),e.registerFilter("upcase",function(e){return i(e).toUpperCase()}),e.registerFilter("url_encode",encodeURIComponent)}},{"./src/strftime.js":14}],2:[function(e,r){"use strict";function t(e){e=e||{},e.root=e.root||"",e.extname=e.extname||".liquid";var r=Object.create(v);return r.init(c(),l(),e),r}function n(e,r){if("/"==r[0])return r;var t=e.split("/").concat(r.split("/")),n=[];return t.forEach(function(e){".."==e?n.pop():e&&"."!=e&&n.push(e)}),"/"+n.join("/")}var i=e("./src/scope"),s=e("./src/tokenizer.js"),o=e("fs"),a=e("./src/render.js"),u=e("./src/lexical.js"),c=e("./src/tag.js"),l=e("./src/filter.js"),f=e("./src/parser"),p=e("./src/expression.js"),h=e("./tags"),g=e("./filters"),d=e("any-promise"),v={init:function(e,r,t){return t.cache&&(this.cache={}),this.options=t,this.tag=e,this.filter=r,this.parser=f(e,r),this.renderer=a(),h(this),g(this),this},parse:function(e){var r=s.parse(e);return this.parser.parse(r)},render:function(e,r,t){t=t||{},t.strict_variables=t.strict_variables||!1,t.strict_filters=t.strict_filters||!1,this.renderer.resetRegisters();var n=i.factory(r,{strict:t.strict_variables});return this.renderer.renderTemplates(e,n,t)},parseAndRender:function(e,r,t){try{var n=this.parse(e);return this.render(n,r,t)}catch(i){return d.reject(i)}},renderFile:function(e,r,t){var n=this;return this.handleCache(e).then(function(e){return n.render(e,r,t)})["catch"](function(r){throw r.file=e,r})},evalOutput:function(e,r){var t=this.parser.parseOutput(e.trim());return this.renderer.evalOutput(t,r)},registerFilter:function(e,r){return this.filter.register(e,r)},registerTag:function(e,r){return this.tag.register(e,r)},handleCache:function(e){var r=this;if(!e)throw new Error("filepath cannot be null");return this.getTemplate(e).then(function(t){var n=r.options.cache&&r.cache[e]||r.parse(t);return r.options.cache?r.cache[e]=n:n})},getTemplate:function(e){return e=n(this.options.root,e),e.match(/\.\w+$/)||(e+=this.options.extname),new d(function(r,t){o.readFile(e,"utf8",function(e,n){e?t(e):r(n)})})},express:function(e){var r=this;return function(t,n,i){r.renderFile(t,n,e).then(function(e){return i(null,e)})["catch"](function(e){return i(e)})}}};t.lexical=u,t.isTruthy=p.isTruthy,t.isFalsy=p.isFalsy,t.evalExp=p.evalExp,t.evalValue=p.evalValue,r.exports=t},{"./filters":1,"./src/expression.js":8,"./src/filter.js":9,"./src/lexical.js":10,"./src/parser":11,"./src/render.js":12,"./src/scope":13,"./src/tag.js":16,"./src/tokenizer.js":17,"./tags":28,"any-promise":3,fs:6}],3:[function(e,r){"use strict";r.exports=e("./register")().Promise},{"./register":5}],4:[function(e,r){"use strict";var t="@@any-promise/REGISTRATION",n=null;r.exports=function(e,r){return function(i,s){i=i||null,s=s||{};var o=s.global!==!1;if(null===n&&o&&(n=e[t]||null),null!==n&&null!==i&&n.implementation!==i)throw new Error('any-promise already defined as "'+n.implementation+'". You can only register an implementation before the first call to require("any-promise") and an implementation cannot be changed');return null===n&&(n=null!==i&&"undefined"!=typeof s.Promise?{Promise:s.Promise,implementation:i}:r(i),o&&(e[t]=n)),n}}},{}],5:[function(e,r){"use strict";function t(){if("undefined"==typeof window.Promise)throw new Error("any-promise browser requires a polyfill or explicit registration e.g: require('any-promise/register/bluebird')");return{Promise:window.Promise,implementation:"window.Promise"}}r.exports=e("./loader")(window,t)},{"./loader":4}],6:[function(){"use strict"},{}],7:[function(e,r){"use strict";function t(e,r,t){Error.captureStackTrace(this,this.constructor),this.name=this.constructor.name,this.message=e||"",this.input=r,this.line=t}function n(e,r,t,n){Error.captureStackTrace(this,this.constructor),this.name=this.constructor.name,this.originalError=n,this.message=e||"",this.input=r,this.line=t}t.prototype=Object.create(Error.prototype),t.prototype.constructor=t,n.prototype=Object.create(Error.prototype),n.prototype.constructor=n,r.exports={TokenizationError:t,ParseError:n}},{}],8:[function(e,r){"use strict";function t(e,r){if(!r)throw new Error("unable to evalExp: scope undefined");for(var i,s=a.operators,u=0;u=m;m++)v.push(m);return v}return n(e,r)}function n(e,r){return e=e&&e.trim(),e?a.isLiteral(e)?a.parseLiteral(e):a.isVariable(e)?r.get(e):void 0:void 0}function i(e){return e instanceof Array?!!e.length:!!e}function s(e){return!i(e)}var o=e("./syntax.js"),a=e("./lexical.js");r.exports={evalExp:t,evalValue:n,isTruthy:i,isFalsy:s}},{"./lexical.js":10,"./syntax.js":15}],9:[function(e,r){"use strict";var t=e("./lexical.js"),n=e("./expression.js"),i=new RegExp(""+t.value.source,"g");r.exports=function(){function e(e){var r=Object.create(a);return r.parse(e)}function r(e,r){o[e]=r}function s(){o={}}var o={},a={render:function(e,r){var t=this.args.map(function(e){return n.evalValue(e,r)});return t.unshift(e),this.filter.apply(null,t)},parse:function(e){var r=t.filterLine.exec(e);if(!r)throw new Error("illegal filter: "+e);var n=r[1],s=r[2]||"",a=o[n];if("function"!=typeof a)return{name:n,error:new Error("undefined filter: "+n)};for(var u=[];r=i.exec(s.trim());)u.push(r[0]);return this.name=n,this.filter=a,this.args=u,this}};return{construct:e,register:r,clear:s}}},{"./expression.js":8,"./lexical.js":10}],10:[function(e,r){"use strict";function t(e){return E.test(e)}function n(e){return R.test(e)}function i(e){return T.test(e)}function s(e){var r;return(r=e.match(F))?Number(e):(r=e.match(O))?"true"===e.toLowerCase():(r=e.match(k))?e.slice(1,-1):void 0}var o=/'[^']*'/,a=/"[^"]*"/,u=new RegExp("(?:"+o.source+"|"+a.source+"|[^'\"])*"),c=/(?:-?\d+\.?\d*|\.?\d+)/,l=/true|false/,f=/[a-zA-Z_$][a-zA-Z_$0-9]*/,p=/\[\d+\]/,h=new RegExp("(?:"+o.source+"|"+a.source+")"),g=new RegExp("(?:"+h.source+"|"+l.source+"|"+c.source+")"),d=new RegExp(f.source+"(?:\\."+f.source+"|"+p.source+")*"),v=new RegExp("(?:"+d.source+"|"+c.source+")"),m=new RegExp("\\("+v.source+"\\.\\."+v.source+"\\)"),w=new RegExp("\\(("+v.source+")\\.\\.("+v.source+")\\)"),x=new RegExp("(?:"+g.source+"|"+d.source+"|"+m.source+")"),y=new RegExp("(?:"+f.source+")\\s*:\\s*(?:"+x.source+")"),b=new RegExp("("+f.source+")\\s*:\\s*("+x.source+")","g"),j=new RegExp("^\\s*("+f.source+")\\s*(.*)\\s*$"),E=new RegExp("^"+g.source+"$","i"),T=new RegExp("^"+d.source+"$"),F=new RegExp("^"+c.source+"$"),O=new RegExp("^"+l.source+"$","i"),k=new RegExp("^"+h.source+"$"),R=new RegExp("^"+w.source+"$"),M=new RegExp(x.source+"(\\s*,\\s*"+x.source+")*"),S=new RegExp(f.source+"(?:\\s*:\\s*"+M.source+")?","g"),I=new RegExp("("+f.source+")(?:\\s*:\\s*("+M.source+"))?"),L=new RegExp("^"+I.source+"$"),_=[/\s+or\s+/,/\s+and\s+/,/==|!=|<=|>=|<|>|\s+contains\s+/];r.exports={quoted:h,number:c,bool:l,literal:g,filter:S,hash:y,hashCapture:b,range:m,rangeCapture:w,identifier:f,value:x,quoteBalanced:u,operators:_,quotedLine:k,numberLine:F,boolLine:O,rangeLine:R,literalLine:E,filterLine:L,tagLine:j,isLiteral:t,isVariable:i,parseLiteral:s,isRange:n}},{}],11:[function(e,r){"use strict";var t=e("./lexical.js"),n=e("./error.js").ParseError;r.exports=function(e,r){function i(e){for(var r,t=[];r=e.shift();)t.push(s(r,e));return t}function s(e,r){try{switch(e.type){case"tag":return o(e,r);case"output":return a(e.value);case"html":return e}}catch(t){throw new n(t.message,e.input,e.line,t)}}function o(r,t){return"continue"===r.name||"break"===r.name?r:e.construct(r,t)}function a(e){var n=t.value.exec(e);if(!n)throw new Error("illegal output string: "+e);var i=n[0];e=e.substr(n.index+n[0].length);for(var s=[];n=t.filter.exec(e);)s.push([n[0].trim()]);return{type:"output",initial:i,filters:s.map(function(e){return r.construct(e)})}}function u(e){var r=Object.create(c);return r.init(e)}var c={init:function(e){return this.tokens=e,this.handlers={},this},on:function(e,r){return this.handlers[e]=r,this},trigger:function(e,r){var t=this.handlers[e];return"function"==typeof t?(t(r),!0):void 0},start:function(){this.trigger("start");for(var e;!this.stopRequested&&(e=this.tokens.shift());)if(!(this.trigger("token",e)||"tag"==e.type&&this.trigger("tag:"+e.name,e))){var r=s(e,this.tokens);this.trigger("template",r)}return this.stopRequested||this.trigger("end"),this},stop:function(){return this.stopRequested=!0,this}};return{parse:i,parseTag:o,parseStream:u,parseOutput:a}}},{"./error.js":7,"./lexical.js":10}],12:[function(e,r){"use strict";function t(){var e=Object.create(o);return e.register={},e}function n(e){return"string"==typeof e?e:JSON.stringify(e)}var i=e("./expression.js"),s=e("any-promise"),o={renderTemplates:function(e,r,t){var i=this;if(!r)throw new Error("unable to evalTemplates: scope undefined");t=t||{},t.strict_filters=t.strict_filters||!1;var o="",a=e.reduce(function(e,a){return e.then(function(){if(r.safeGet("forloop.skip"))return s.resolve("");if(r.safeGet("forloop.stop"))throw new Error("forloop.stop");var e=s.resolve("");switch(a.type){case"tag":e=i.renderTag(a,r,i.register).then(function(e){return void 0===e?!0:o+=e});break;case"html":e=s.resolve(a.value).then(function(e){return o+=e});break;case"output":var u=i.evalOutput(a,r,t);e=s.resolve(void 0===u?"":n(u)).then(function(e){return o+=e})}return e})["catch"](function(e){if("forloop.skip"===e.message)return o;throw e})},s.resolve(""));return a.then(function(e){return e})["catch"](function(e){throw e})},renderTag:function(e,r,t){return"continue"===e.name?(r.set("forloop.skip",!0),s.resolve("")):"break"===e.name?(r.set("forloop.stop",!0),r.set("forloop.skip",!0),s.reject(new Error("forloop.stop"))):e.render(r,t)},evalOutput:function(e,r,t){if(!r)throw new Error("unable to evalOutput: scope undefined");var n=i.evalExp(e.initial,r);return e.filters.some(function(e){if(e.error){if(t.strict_filters)throw e.error;return n="",!0}n=e.render(n,r)}),n},resetRegisters:function(){return this.register={}}};r.exports=t},{"./expression.js":8,"any-promise":3}],13:[function(e,r,t){"use strict";function n(e,r,t){if(r instanceof String||"string"==typeof r){for(var n=r.replace(/\[/g,".").replace(/\]/g,"").split("."),i=0;i=0;r--){var n=this.scopes[r];for(var s in n)n.hasOwnProperty(s)&&(t[s]=n[s])}return t}for(r=this.scopes.length-1;r>=0;r--){var o=i(this.scopes[r],e);if(void 0!==o)return o}},get:function(e){var r=this.safeGet(e);if(void 0===r&&this.opts.strict)throw new Error("[strict_variables] undefined variable: "+e);return r},set:function(e,r){return n(this.scopes[this.scopes.length-1],e,r),this},push:function(e){if(!e)throw new Error("trying to push "+e+" into scopes");return this.scopes.push(e)},pop:function(){return this.scopes.pop()}};t.factory=function(e,r){r=r||{},r.strict=r.strict||!1;var t=Object.create(s);return t.opts=r,t.scopes=[e||{}],t}},{}],14:[function(e,r){"use strict";var t=["January","February","March","April","May","June","July","August","September","October","November","December"],n=["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"],i=["Sunday","Monday","Tuesday","Wednesday","Thursday","Friday","Saturday"],s=["Sun","Mon","Tue","Wed","Thu","Fri","Sat"],o={1:"st",2:"nd",3:"rd","default":"th"},a={daysInMonth:function(e){var r=a.isLeapYear(e)?29:28;return[31,r,31,30,31,30,31,31,30,31,30,31]},getTimezone:function(e){return e.toString().replace(/^.*? ([A-Z]{3}) [0-9]{4}.*$/,"$1").replace(/^.*?\(([A-Z])[a-z]+ ([A-Z])[a-z]+ ([A-Z])[a-z]+\)$/,"$1$2$3")},getGMTOffset:function(e){return(e.getTimezoneOffset()>0?"-":"+")+c.pad(Math.floor(e.getTimezoneOffset()/60),2)+c.pad(e.getTimezoneOffset()%60,2)},getDayOfYear:function(e){for(var r=0,t=0;tr?r+7:r},getLastDayOfMonth:function(e){var r=(e.getDay()+(a.daysInMonth(e)[e.getMonth()]-e.getDate()))%7;return 0>r?r+7:r},getSuffix:function(e){var r=e.getDate().toString(),t=parseInt(r.slice(-1));return o[t]||o["default"]},applyOffset:function(e,r){return e.setTime(e.valueOf()-1e3*r),e},century:function(e){return parseInt(e.getFullYear().toString().substring(0,2),10)}},u={values_of:function(e){var r=[];for(var t in e)e.hasOwnProperty(t)&&r.push(e[t]);return r}},c={pad:function h(e,r,t){t||(t="0");for(var n=e.toString(),h=r-n.length;h-->0;)n=t+n;return n}},l={a:function(e){return s[e.getDay()]},A:function(e){return i[e.getDay()]},b:function(e){return n[e.getMonth()]},B:function(e){return t[e.getMonth()]},c:function(e){return e.toLocaleString()},C:function(e){return a.century(e)},d:function(e){return c.pad(e.getDate(),2)},e:function(e){return c.pad(e.getDate(),2," ")},H:function(e){return c.pad(e.getHours(),2)},I:function(e){return c.pad(e.getHours()%12||12,2)},j:function(e){return c.pad(a.getDayOfYear(e),3)},k:function(e){return c.pad(e.getHours(),2," ")},l:function(e){return c.pad(e.getHours()%12||12,2," ")},L:function(e){return c.pad(e.getMilliseconds(),3)},m:function(e){return c.pad(e.getMonth()+1,2)},M:function(e){return c.pad(e.getMinutes(),2)},p:function(e){return e.getHours()<12?"AM":"PM"},P:function(e){return e.getHours()<12?"am":"pm"},q:function(e){return a.getSuffix(e)},s:function(e){return Math.round(e.valueOf()/1e3)},S:function(e){return c.pad(e.getSeconds(),2)},u:function(e){return e.getDay()||7},U:function(e){return a.getWeekOfYear(e,0)},w:function(e){return e.getDay()},W:function(e){return a.getWeekOfYear(e,1)},x:function(e){return e.toLocaleDateString()},X:function(e){return e.toLocaleTimeString()},y:function(e){return e.getFullYear().toString().substring(2,4)},Y:function(e){return e.getFullYear()},z:function(e){var r=e.getTimezoneOffset()/60*100;return(r>0?"-":"+")+c.pad(r,4)},"%":function(){return"%"}};l.h=l.b,l.N=l.L;var f={a:{r:"(?:"+s.join("|")+")"},A:{r:"(?:"+i.join("|")+")"},b:{r:"("+n.join("|")+")",p:function(e){this.month=$.inArray(e,n)}},B:{r:"("+t.join("|")+")",p:function(e){this.month=$.inArray(e,t)}},C:{r:"(\\d{1,2})",p:function(e){this.century=parseInt(e,10)}},d:{r:"(\\d{1,2})",p:function(e){this.day=parseInt(e,10)}},H:{r:"(\\d{1,2})",p:function(e){this.hour=parseInt(e,10)}},j:{r:"(\\d{1,3})",p:function(e){this.day=parseInt(e,10)}},L:{r:"(\\d{3})",p:function(e){this.milliseconds=parseInt(e,10)}},m:{r:"(\\d{1,2})",p:function(e){this.month=parseInt(e,10)-1}},M:{r:"(\\d{2})",p:function(e){this.minute=parseInt(e,10)}},p:{r:"(AM|PM)",p:function(e){"AM"==e?12==this.hour&&(this.hour=0):this.hour<12&&(this.hour+=12)}},P:{r:"(am|pm)",p:function(e){"am"==e?12==this.hour&&(this.hour=0):this.hour<12&&(this.hour+=12)}},q:{r:"(?:"+u.values_of(o).join("|")+")"},S:{r:"(\\d{2})",p:function(e){this.second=parseInt(e,10)}},y:{r:"(\\d{1,2})",p:function(e){this.year=parseInt(e,10)}},Y:{r:"(\\d{4})",p:function(e){this.century=Math.floor(parseInt(e,10)/100),this.year=parseInt(e,10)%100}},z:{r:"(Z|[+-]\\d{2}:?\\d{2})",p:function(e){if("Z"==e)return void(this.zone=0);var r=3600*parseInt(e[0]+e[1]+e[2],10);r+=":"==e[3]?60*parseInt(e[4]+e[5],10):60*parseInt(e[3]+e[4],10),this.zone=r}}};f.e=f.d,f.h=f.b,f.I=f.H,f.k=f.H,f.l=f.H;var p=function(e,r){for(var t="",n=r;;){var i=/%./g,s=i.exec(n);if(!s)return t+n;t+=n.slice(0,i.lastIndex-2),n=n.slice(i.lastIndex);var o=s[0].charAt(1),a=l[o];t+=a?a.call(this,e):"%"+o}};r.exports=p},{}],15:[function(e,r,t){"use strict";var n={"==":function(e,r){return e==r},"!=":function(e,r){return e!=r},">":function(e,r){return e>r},"<":function(e,r){return r>e},">=":function(e,r){return e>=r},"<=":function(e,r){return r>=e},contains:function(e,r){return e.indexOf(r)>-1},and:function(e,r){return e&&r},or:function(e,r){return e||r}};t.operators=n},{}],16:[function(e,r){"use strict";function t(e,r){var t,i={};for(n.hashCapture.lastIndex=0;t=n.hashCapture.exec(e);){var o=t[1],a=t[2];i[o]=s.evalValue(a,r)}return i}var n=e("./lexical.js"),i=e("any-promise"),s=e("./expression.js");r.exports=function(){function e(e,r){s[e]=r}function r(e,r){var t=Object.create(o);return t.parse(e,r),t}function n(){s={}}var s={},o={render:function(e,r){var n=r[this.name];n||(n=r[this.name]={});var s=t(this.token.args,e);return this.tagImpl.render&&this.tagImpl.render(e,s,n)||i.resolve("")},parse:function(e,r){this.type="tag",this.token=e,this.name=e.name;var t=s[this.name];if(!t)throw new Error("tag "+this.name+" not found");this.tagImpl=Object.create(t),this.tagImpl.parse&&this.tagImpl.parse(e,r)}};return{construct:r,register:e,clear:n}}},{"./expression.js":8,"./lexical.js":10,"any-promise":3}],17:[function(e,r,t){"use strict";function n(e){function r(e,r,i){return{type:e,raw:i[r],value:i[r+1].trim(),line:n(i),input:t(i)}}function t(e){var r=e.input.lastIndexOf("\n",e.index),t=e.input.indexOf("\n",e.index);return-1===t&&(t=e.input.length),e.input.slice(r+1,t)}function n(e){var r=e.input.slice(p+1,e.index).split("\n");return h+=r.length-1,p=e.index,h+1}var o=[];if(!e)return o;for(var a,u,c,l=/({%(.*?)%})|({{(.*?)}})/g,f=0,p=-1,h=0;null!==(a=l.exec(e));){if(a.index>f&&(u=e.slice(f,a.index),o.push({type:"html",raw:u,value:u})),a[1]){c=r("tag",1,a);var g=c.value.match(i.tagLine);if(!g)throw new s("illegal tag: "+c.raw,c.input,c.line);c.name=g[1],c.args=g[2],o.push(c)}else c=r("output",3,a),o.push(c);f=l.lastIndex}return e.length>f&&(u=e.slice(f,e.length),o.push({type:"html",raw:u,value:u})),o}var i=e("./lexical.js"),s=e("./error.js").TokenizationError;t.parse=n},{"./error.js":7,"./lexical.js":10}],18:[function(e,r){"use strict";var t=e(".."),n=t.lexical,i=e("any-promise"),s=new RegExp("("+n.identifier.source+")\\s*=(.*)");r.exports=function(e){e.registerTag("assign",{parse:function(e){var r=e.args.match(s);if(!r)throw new Error("illegal token "+e.raw);this.key=r[1],this.value=r[2]},render:function(r){return r.set(this.key,e.evalOutput(this.value,r)),i.resolve("")}})}},{"..":2,"any-promise":3}],19:[function(e,r){"use strict";var t=e(".."),n=t.lexical,i=new RegExp("("+n.identifier.source+")");r.exports=function(e){e.registerTag("capture",{parse:function(r,t){var n=this,s=r.args.match(i);if(!s)throw new Error(r.args+" not valid identifier");this.variable=s[1],this.templates=[];var o=e.parser.parseStream(t);o.on("tag:endcapture",function(){return o.stop()}).on("template",function(e){return n.templates.push(e)}).on("end",function(){throw new Error("tag "+r.raw+" not closed")}),o.start()},render:function(r){var t=this;return e.renderer.renderTemplates(this.templates,r).then(function(e){r.set(t.variable,e)})}})}},{"..":2}],20:[function(e,r){"use strict";var t=e("..");r.exports=function(e){e.registerTag("case",{parse:function(r,t){var n=this;this.cond=r.args,this.cases=[],this.elseTemplates=[];var i=[],s=e.parser.parseStream(t).on("tag:when",function(e){n.cases[e.args]||n.cases.push({val:e.args,templates:i=[]})}).on("tag:else",function(){return i=n.elseTemplates}).on("tag:endcase",function(){return s.stop()}).on("template",function(e){return i.push(e)}).on("end",function(){throw new Error("tag "+r.raw+" not closed")});s.start()},render:function(r){for(var n=0;n",l=i.offset||0,f=void 0===i.limit?u.length:i.limit,p=i.cols;if(!p)throw new Error("illegal cols: "+p);u=u.slice(l,l+f);var h=[];u.some(function(e){var r={};r[a.variable]=e,h.push(r)});var g=h.reduce(function(t,n,i){return t.then(function(){return s=Math.floor(i/p)+1,o=i%p+1,1===o&&(1!==s&&(c+=""),c+=''),c+=''}).then(function(){return r.push(n),e.renderer.renderTemplates(a.templates,r)}).then(function(e){return r.pop(n),c+=e,c+=""})},n.resolve(""));return g.then(function(){ -return s>0&&(c+=""),c+=""})["catch"](function(e){throw e})}})}},{"..":2,"any-promise":3}],32:[function(e,r){"use strict";var t=e("..");r.exports=function(e){e.registerTag("unless",{parse:function(r,t){var n=this;this.templates=[],this.elseTemplates=[];var i,s=e.parser.parseStream(t).on("start",function(){i=n.templates,n.cond=r.args}).on("tag:else",function(){return i=n.elseTemplates}).on("tag:endunless",function(){return s.stop()}).on("template",function(e){return i.push(e)}).on("end",function(){throw new Error("tag "+r.raw+" not closed")});s.start()},render:function(r){var n=t.evalExp(this.cond,r);return t.isFalsy(n)?e.renderer.renderTemplates(this.templates,r):e.renderer.renderTemplates(this.elseTemplates,r)}})}},{"..":2}]},{},[2])(2)}); \ No newline at end of file +!function(e){if("object"==typeof exports&&"undefined"!=typeof module)module.exports=e();else if("function"==typeof define&&define.amd)define([],e);else{var r;r="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof self?self:this,r.Liquid=e()}}(function(){return function e(r,t,n){function i(o,a){if(!t[o]){if(!r[o]){var u="function"==typeof require&&require;if(!a&&u)return u(o,!0);if(s)return s(o,!0);var c=new Error("Cannot find module '"+o+"'");throw c.code="MODULE_NOT_FOUND",c}var l=t[o]={exports:{}};r[o][0].call(l.exports,function(e){var t=r[o][1][e];return i(t?t:e)},l,l.exports,e,r,t,n)}return t[o].exports}for(var s="function"==typeof require&&require,o=0;o|"|'/g,function(e){return f[e]})}function n(e){return o(e).replace(/&(amp|lt|gt|#34|#39);/g,function(e){return p[e]})}function i(e){var r=(e+"").split(".");return r.length>1?r[1].length:0}function s(e,r){return Math.max(i(e),i(r))}function o(e){return e=e||"",e+""}function a(e){return function(r,t){var n=s(r,t);return e(r,t).toFixed(n)}}function u(e){return l.forOwn(h,function(r,t){return e.registerFilter(t,r)})}var c=e("./src/util/strftime.js"),l=e("./src/util/underscore.js"),f={"&":"&","<":"<",">":">",'"':""","'":"'"},p={"&":"&","<":"<",">":">",""":'"',"'":"'"},h={abs:function(e){return Math.abs(e)},append:function(e,r){return e+r},capitalize:function(e){return o(e).charAt(0).toUpperCase()+e.slice(1)},ceil:function(e){return Math.ceil(e)},date:function(e,r){return"now"===e&&(e=new Date),c(e,r)},"default":function(e,r){return r||e},divided_by:function(e,r){return Math.floor(e/r)},downcase:function(e){return e.toLowerCase()},escape:t,escape_once:function(e){return t(n(e))},first:function(e){return e[0]},floor:function(e){return Math.floor(e)},join:function(e,r){return e.join(r)},last:function(e){return e[e.length-1]},lstrip:function(e){return o(e).replace(/^\s+/,"")},map:function(e,r){return e.map(function(e){return e[r]})},minus:a(function(e,r){return e-r}),modulo:a(function(e,r){return e%r}),newline_to_br:function(e){return e.replace(/\n/g,"
")},plus:a(function(e,r){return e+r}),prepend:function(e,r){return r+e},remove:function(e,r){return e.split(r).join("")},remove_first:function(e,r){return e.replace(r,"")},replace:function(e,r,t){return o(e).split(r).join(t)},replace_first:function(e,r,t){return o(e).replace(r,t)},reverse:function(e){return e.reverse()},round:function(e,r){var t=Math.pow(10,r||0);return Math.round(e*t,r)/t},rstrip:function(e){return o(e).replace(/\s+$/,"")},size:function(e){return e.length},slice:function(e,r,t){return e.substr(r,void 0===t?1:t)},sort:function(e,r){return e.sort(r)},split:function(e,r){return o(e).split(r)},strip:function(e){return o(e).trim()},strip_html:function(e){return o(e).replace(/<\/?\s*\w+\s*\/?>/g,"")},strip_newlines:function(e){return o(e).replace(/\n/g,"")},times:function(e,r){return e*r},truncate:function(e,r,t){return e=o(e),t=void 0===t?"...":t,r=r||16,e.length<=r?e:e.substr(0,r-t.length)+t},truncatewords:function(e,r,t){void 0===t&&(t="...");var n=e.split(" "),i=n.slice(0,r).join(" ");return n.length>r&&(i+=t),i},uniq:function(e){var r={};return(e||[]).filter(function(e){return r.hasOwnProperty(e)?!1:(r[e]=!0,!0)})},upcase:function(e){return o(e).toUpperCase()},url_encode:encodeURIComponent};u.filters=h,r.exports=u},{"./src/util/strftime.js":17,"./src/util/underscore.js":18}],2:[function(e,r){"use strict";function t(e){e=e||{},e.root=e.root||"",e.extname=e.extname||".liquid";var r=Object.create(v);return r.init(c(),l(),e),r}function n(e,r){if("/"==r[0])return r;var t=e.split("/").concat(r.split("/")),n=[];return t.forEach(function(e){".."==e?n.pop():e&&"."!=e&&n.push(e)}),"/"+n.join("/")}var i=e("./src/scope"),s=e("./src/tokenizer.js"),o=e("fs"),a=e("./src/render.js"),u=e("./src/lexical.js"),c=e("./src/tag.js"),l=e("./src/filter.js"),f=e("./src/parser"),p=e("./src/syntax.js"),h=e("./tags"),g=e("./filters"),d=e("any-promise"),v={init:function(e,r,t){return t.cache&&(this.cache={}),this.options=t,this.tag=e,this.filter=r,this.parser=f(e,r),this.renderer=a(),h(this),g(this),this},parse:function(e){var r=s.parse(e);return this.parser.parse(r)},render:function(e,r,t){t=t||{},t.strict_variables=t.strict_variables||!1,t.strict_filters=t.strict_filters||!1,this.renderer.resetRegisters();var n=i.factory(r,{strict:t.strict_variables});return this.renderer.renderTemplates(e,n,t)},parseAndRender:function(e,r,t){try{var n=this.parse(e);return this.render(n,r,t)}catch(i){return d.reject(i)}},renderFile:function(e,r,t){var n=this;return this.handleCache(e).then(function(e){return n.render(e,r,t)})["catch"](function(r){throw r.file=e,r})},evalOutput:function(e,r){var t=this.parser.parseOutput(e.trim());return this.renderer.evalOutput(t,r)},registerFilter:function(e,r){return this.filter.register(e,r)},registerTag:function(e,r){return this.tag.register(e,r)},handleCache:function(e){var r=this;if(!e)throw new Error("filepath cannot be null");return this.getTemplate(e).then(function(t){var n=r.options.cache&&r.cache[e]||r.parse(t);return r.options.cache?r.cache[e]=n:n})},getTemplate:function(e){return e=n(this.options.root,e),e.match(/\.\w+$/)||(e+=this.options.extname),new d(function(r,t){o.readFile(e,"utf8",function(e,n){e?t(e):r(n)})})},express:function(e){var r=this;return function(t,n,i){r.renderFile(t,n,e).then(function(e){return i(null,e)})["catch"](function(e){return i(e)})}}};t.lexical=u,t.isTruthy=p.isTruthy,t.isFalsy=p.isFalsy,t.evalExp=p.evalExp,t.evalValue=p.evalValue,r.exports=t},{"./filters":1,"./src/filter.js":8,"./src/lexical.js":9,"./src/parser":11,"./src/render.js":12,"./src/scope":13,"./src/syntax.js":14,"./src/tag.js":15,"./src/tokenizer.js":16,"./tags":29,"any-promise":3,fs:6}],3:[function(e,r){"use strict";r.exports=e("./register")().Promise},{"./register":5}],4:[function(e,r){"use strict";var t="@@any-promise/REGISTRATION",n=null;r.exports=function(e,r){return function(i,s){i=i||null,s=s||{};var o=s.global!==!1;if(null===n&&o&&(n=e[t]||null),null!==n&&null!==i&&n.implementation!==i)throw new Error('any-promise already defined as "'+n.implementation+'". You can only register an implementation before the first call to require("any-promise") and an implementation cannot be changed');return null===n&&(n=null!==i&&"undefined"!=typeof s.Promise?{Promise:s.Promise,implementation:i}:r(i),o&&(e[t]=n)),n}}},{}],5:[function(e,r){"use strict";function t(){if("undefined"==typeof window.Promise)throw new Error("any-promise browser requires a polyfill or explicit registration e.g: require('any-promise/register/bluebird')");return{Promise:window.Promise,implementation:"window.Promise"}}r.exports=e("./loader")(window,t)},{"./loader":4}],6:[function(){"use strict"},{}],7:[function(e,r){"use strict";function t(e,r,t){Error.captureStackTrace(this,this.constructor),this.name=this.constructor.name,this.message=e||"",this.input=r,this.line=t}function n(e,r,t,n){Error.captureStackTrace(this,this.constructor),this.name=this.constructor.name,this.originalError=n,this.message=e||"",this.input=r,this.line=t}t.prototype=Object.create(Error.prototype),t.prototype.constructor=t,n.prototype=Object.create(Error.prototype),n.prototype.constructor=n,r.exports={TokenizationError:t,ParseError:n}},{}],8:[function(e,r){"use strict";var t=e("./lexical.js"),n=e("./syntax.js"),i=new RegExp(""+t.value.source,"g");r.exports=function(){function e(e){var r=Object.create(a);return r.parse(e)}function r(e,r){o[e]=r}function s(){o={}}var o={},a={render:function(e,r){var t=this.args.map(function(e){return n.evalValue(e,r)});return t.unshift(e),this.filter.apply(null,t)},parse:function(e){var r=t.filterLine.exec(e);if(!r)throw new Error("illegal filter: "+e);var n=r[1],s=r[2]||"",a=o[n];if("function"!=typeof a)return{name:n,error:new Error("undefined filter: "+n)};for(var u=[];r=i.exec(s.trim());)u.push(r[0]);return this.name=n,this.filter=a,this.args=u,this}};return{construct:e,register:r,clear:s}}},{"./lexical.js":9,"./syntax.js":14}],9:[function(e,r){"use strict";function t(e){return q.test(e)}function n(e){return k.test(e)}function i(e){return L.test(e)}function s(e){return R.test(e)}function o(e){return E.exec(e)}function a(e){var r;return(r=e.match(S))?Number(e):(r=e.match(M))?"true"===e.toLowerCase():(r=e.match(D))?e.slice(1,-1):void 0}var u=/'[^']*'/,c=/"[^"]*"/,l=new RegExp(u.source+"|"+c.source),f=new RegExp("(?:"+l.source+"|[^'\"])*"),p=/-?\d+/,h=/-?\d+\.?\d*|\.?\d+/,g=/true|false/,d=/[\w-]+/,v=new RegExp("\\[(?:"+l.source+"|[\\w-\\.]+)\\]"),m=new RegExp("(?:"+l.source+"|"+g.source+"|"+h.source+")"),w=new RegExp(d.source+"(?:\\."+d.source+"|"+v.source+")*"),x=new RegExp("(?:"+w.source+"|"+h.source+")"),y=new RegExp("\\("+x.source+"\\.\\."+x.source+"\\)"),b=new RegExp("\\(("+x.source+")\\.\\.("+x.source+")\\)"),E=new RegExp("(?:"+w.source+"|"+m.source+"|"+y.source+")"),j=new RegExp("(?:"+d.source+")\\s*:\\s*(?:"+E.source+")"),T=new RegExp("("+d.source+")\\s*:\\s*("+E.source+")","g"),O=new RegExp("^\\s*("+d.source+")\\s*(.*)\\s*$"),k=new RegExp("^"+m.source+"$","i"),R=new RegExp("^"+w.source+"$"),S=new RegExp("^"+h.source+"$"),M=new RegExp("^"+g.source+"$","i"),D=new RegExp("^"+l.source+"$"),L=new RegExp("^"+b.source+"$"),q=new RegExp("^"+p.source+"$"),P=new RegExp(E.source+"(\\s*,\\s*"+E.source+")*"),_=new RegExp(d.source+"(?:\\s*:\\s*"+P.source+")?","g"),F=new RegExp("("+d.source+")(?:\\s*:\\s*("+P.source+"))?"),I=new RegExp("^"+F.source+"$"),$=[/\s+or\s+/,/\s+and\s+/,/==|!=|<=|>=|<|>|\s+contains\s+/];r.exports={quoted:l,number:h,bool:g,literal:m,filter:_,integer:p,hash:j,hashCapture:T,range:y,rangeCapture:b,identifier:d,value:E,quoteBalanced:f,operators:$,quotedLine:D,numberLine:S,boolLine:M,rangeLine:L,literalLine:k,filterLine:I,tagLine:O,isLiteral:n,isVariable:s,parseLiteral:a,isRange:i,matchValue:o,isInteger:t}},{}],10:[function(e,r){"use strict";var t={"==":function(e,r){return e==r},"!=":function(e,r){return e!=r},">":function(e,r){return e>r},"<":function(e,r){return r>e},">=":function(e,r){return e>=r},"<=":function(e,r){return r>=e},contains:function(e,r){return e.indexOf(r)>-1},and:function(e,r){return e&&r},or:function(e,r){return e||r}};r.exports=t},{}],11:[function(e,r){"use strict";var t=e("./lexical.js"),n=e("./error.js").ParseError;r.exports=function(e,r){function i(e){for(var r,t=[];r=e.shift();)t.push(s(r,e));return t}function s(e,r){try{switch(e.type){case"tag":return o(e,r);case"output":return a(e.value);case"html":return e}}catch(t){throw new n(t.message,e.input,e.line,t)}}function o(r,t){return"continue"===r.name||"break"===r.name?r:e.construct(r,t)}function a(e){var n=t.matchValue(e);if(!n)throw new Error("illegal output string: "+e);var i=n[0];e=e.substr(n.index+n[0].length);for(var s=[];n=t.filter.exec(e);)s.push([n[0].trim()]);return{type:"output",initial:i,filters:s.map(function(e){return r.construct(e)})}}function u(e){var r=Object.create(c);return r.init(e)}var c={init:function(e){return this.tokens=e,this.handlers={},this},on:function(e,r){return this.handlers[e]=r,this},trigger:function(e,r){var t=this.handlers[e];return"function"==typeof t?(t(r),!0):void 0},start:function(){this.trigger("start");for(var e;!this.stopRequested&&(e=this.tokens.shift());)if(!(this.trigger("token",e)||"tag"==e.type&&this.trigger("tag:"+e.name,e))){var r=s(e,this.tokens);this.trigger("template",r)}return this.stopRequested||this.trigger("end"),this},stop:function(){return this.stopRequested=!0,this}};return{parse:i,parseTag:o,parseStream:u,parseOutput:a}}},{"./error.js":7,"./lexical.js":9}],12:[function(e,r){"use strict";function t(){var e=Object.create(o);return e.register={},e}function n(e){return"string"==typeof e?e:JSON.stringify(e)}var i=e("./syntax.js"),s=e("any-promise"),o={renderTemplates:function(e,r,t){var i=this;if(!r)throw new Error("unable to evalTemplates: scope undefined");t=t||{},t.strict_filters=t.strict_filters||!1;var o="",a=e.reduce(function(e,a){return e.then(function(){if(r.safeGet("forloop.skip"))return s.resolve("");if(r.safeGet("forloop.stop"))throw new Error("forloop.stop");var e=s.resolve("");switch(a.type){case"tag":e=i.renderTag(a,r,i.register).then(function(e){return void 0===e?!0:o+=e});break;case"html":e=s.resolve(a.value).then(function(e){return o+=e});break;case"output":var u=i.evalOutput(a,r,t);e=s.resolve(void 0===u?"":n(u)).then(function(e){return o+=e})}return e})["catch"](function(e){if("forloop.skip"===e.message)return o;throw e})},s.resolve(""));return a.then(function(e){return e})["catch"](function(e){throw e})},renderTag:function(e,r,t){return"continue"===e.name?(r.set("forloop.skip",!0),s.resolve("")):"break"===e.name?(r.set("forloop.stop",!0),r.set("forloop.skip",!0),s.reject(new Error("forloop.stop"))):e.render(r,t)},evalOutput:function(e,r,t){if(!r)throw new Error("unable to evalOutput: scope undefined");var n=i.evalExp(e.initial,r);return e.filters.some(function(e){if(e.error){if(t.strict_filters)throw e.error;return n="",!0}n=e.render(n,r)}),n},resetRegisters:function(){return this.register={}}};r.exports=t},{"./syntax.js":14,"any-promise":3}],13:[function(e,r,t){"use strict";function n(e,r){for(var t=1,n=r;n=0;r--){var n=this.scopes[r];for(var i in n)n.hasOwnProperty(i)&&(t[i]=n[i])}return t}for(r=this.scopes.length-1;r>=0;r--){var s=this.getPropertyByPath(this.scopes[r],e);if(void 0!==s)return s}},get:function(e){var r=this.safeGet(e);if(void 0===r&&this.opts.strict)throw new Error("[strict_variables] undefined variable: "+e);return r},set:function(e,r){return this.setPropertyByPath(this.scopes[this.scopes.length-1],e,r),this},push:function(e){if(!e)throw new Error("trying to push "+e+" into scopes");return this.scopes.push(e)},pop:function(){return this.scopes.pop()},setPropertyByPath:function(e,r,t){if(i.isString(r)){for(var n=r.replace(/\[/g,".").replace(/\]/g,"").split("."),s=0;s=m;m++)v.push(m);return v}return n(e,r)}function n(e,r){return e=e&&e.trim(),e?a.isLiteral(e)?a.parseLiteral(e):a.isVariable(e)?r.get(e):void 0:void 0}function i(e){return e instanceof Array?!!e.length:!!e}function s(e){return!i(e)}var o=e("./operators.js"),a=e("./lexical.js");r.exports={evalExp:t,evalValue:n,isTruthy:i,isFalsy:s}},{"./lexical.js":9,"./operators.js":10}],15:[function(e,r){"use strict";function t(e,r){var t,i={};for(n.hashCapture.lastIndex=0;t=n.hashCapture.exec(e);){var o=t[1],a=t[2];i[o]=s.evalValue(a,r)}return i}var n=e("./lexical.js"),i=e("any-promise"),s=e("./syntax.js");r.exports=function(){function e(e,r){s[e]=r}function r(e,r){var t=Object.create(o);return t.parse(e,r),t}function n(){s={}}var s={},o={render:function(e,r){var n=r[this.name];n||(n=r[this.name]={});var s=t(this.token.args,e);return this.tagImpl.render&&this.tagImpl.render(e,s,n)||i.resolve("")},parse:function(e,r){this.type="tag",this.token=e,this.name=e.name;var t=s[this.name];if(!t)throw new Error("tag "+this.name+" not found");this.tagImpl=Object.create(t),this.tagImpl.parse&&this.tagImpl.parse(e,r)}};return{construct:r,register:e,clear:n}}},{"./lexical.js":9,"./syntax.js":14,"any-promise":3}],16:[function(e,r,t){"use strict";function n(e){function r(e,r,i){return{type:e,raw:i[r],value:i[r+1].trim(),line:n(i),input:t(i)}}function t(e){var r=e.input.lastIndexOf("\n",e.index),t=e.input.indexOf("\n",e.index);return-1===t&&(t=e.input.length),e.input.slice(r+1,t)}function n(e){var r=e.input.slice(p+1,e.index).split("\n");return h+=r.length-1,p=e.index,h+1}var o=[];if(!e)return o;for(var a,u,c,l=/({%(.*?)%})|({{(.*?)}})/g,f=0,p=-1,h=0;null!==(a=l.exec(e));){if(a.index>f&&(u=e.slice(f,a.index),o.push({type:"html",raw:u,value:u})),a[1]){c=r("tag",1,a);var g=c.value.match(i.tagLine);if(!g)throw new s("illegal tag: "+c.raw,c.input,c.line);c.name=g[1],c.args=g[2],o.push(c)}else c=r("output",3,a),o.push(c);f=l.lastIndex}return e.length>f&&(u=e.slice(f,e.length),o.push({type:"html",raw:u,value:u})),o}var i=e("./lexical.js"),s=e("./error.js").TokenizationError;t.parse=n},{"./error.js":7,"./lexical.js":9}],17:[function(e,r){"use strict";var t=["January","February","March","April","May","June","July","August","September","October","November","December"],n=["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"],i=["Sunday","Monday","Tuesday","Wednesday","Thursday","Friday","Saturday"],s=["Sun","Mon","Tue","Wed","Thu","Fri","Sat"],o={1:"st",2:"nd",3:"rd","default":"th"},a={daysInMonth:function(e){var r=a.isLeapYear(e)?29:28;return[31,r,31,30,31,30,31,31,30,31,30,31]},getTimezone:function(e){return e.toString().replace(/^.*? ([A-Z]{3}) [0-9]{4}.*$/,"$1").replace(/^.*?\(([A-Z])[a-z]+ ([A-Z])[a-z]+ ([A-Z])[a-z]+\)$/,"$1$2$3")},getGMTOffset:function(e){return(e.getTimezoneOffset()>0?"-":"+")+u.pad(Math.floor(e.getTimezoneOffset()/60),2)+u.pad(e.getTimezoneOffset()%60,2)},getDayOfYear:function(e){for(var r=0,t=0;tr?r+7:r},getLastDayOfMonth:function(e){var r=(e.getDay()+(a.daysInMonth(e)[e.getMonth()]-e.getDate()))%7;return 0>r?r+7:r},getSuffix:function(e){var r=e.getDate().toString(),t=parseInt(r.slice(-1));return o[t]||o["default"]},applyOffset:function(e,r){return e.setTime(e.valueOf()-1e3*r),e},century:function(e){return parseInt(e.getFullYear().toString().substring(0,2),10)}},u={pad:function f(e,r,t){t||(t="0");for(var n=e.toString(),f=r-n.length;f-->0;)n=t+n;return n}},c={a:function(e){return s[e.getDay()]},A:function(e){return i[e.getDay()]},b:function(e){return n[e.getMonth()]},B:function(e){return t[e.getMonth()]},c:function(e){return e.toLocaleString()},C:function(e){return a.century(e)},d:function(e){return u.pad(e.getDate(),2)},e:function(e){return u.pad(e.getDate(),2," ")},H:function(e){return u.pad(e.getHours(),2)},I:function(e){return u.pad(e.getHours()%12||12,2)},j:function(e){return u.pad(a.getDayOfYear(e),3)},k:function(e){return u.pad(e.getHours(),2," ")},l:function(e){return u.pad(e.getHours()%12||12,2," ")},L:function(e){return u.pad(e.getMilliseconds(),3)},m:function(e){return u.pad(e.getMonth()+1,2)},M:function(e){return u.pad(e.getMinutes(),2)},p:function(e){return e.getHours()<12?"AM":"PM"},P:function(e){return e.getHours()<12?"am":"pm"},q:function(e){return a.getSuffix(e)},s:function(e){return Math.round(e.valueOf()/1e3)},S:function(e){return u.pad(e.getSeconds(),2)},u:function(e){return e.getDay()||7},U:function(e){return a.getWeekOfYear(e,0)},w:function(e){return e.getDay()},W:function(e){return a.getWeekOfYear(e,1)},x:function(e){return e.toLocaleDateString()},X:function(e){return e.toLocaleTimeString()},y:function(e){return e.getFullYear().toString().substring(2,4)},Y:function(e){return e.getFullYear()},z:function(e){var r=e.getTimezoneOffset()/60*100;return(r>0?"-":"+")+u.pad(r,4)},"%":function(){return"%"}};c.h=c.b,c.N=c.L;var l=function(e,r){for(var t="",n=r;;){var i=/%./g,s=i.exec(n);if(!s)return t+n;t+=n.slice(0,i.lastIndex-2),n=n.slice(i.lastIndex);var o=s[0].charAt(1),a=c[o];t+=a?a.call(this,e):"%"+o}};r.exports=l},{}],18:[function(e,r,t){"use strict";function n(e){return e instanceof String||"string"==typeof e}function i(e,r){e=e||{};for(var t in e)if(e.hasOwnProperty(t)&&r(e[t],t,e)===!1)break;return e}t.isString=n,t.forOwn=i},{}],19:[function(e,r){"use strict";var t=e(".."),n=t.lexical,i=e("any-promise"),s=new RegExp("("+n.identifier.source+")\\s*=(.*)");r.exports=function(e){e.registerTag("assign",{parse:function(e){var r=e.args.match(s);if(!r)throw new Error("illegal token "+e.raw);this.key=r[1],this.value=r[2]},render:function(r){return r.set(this.key,e.evalOutput(this.value,r)),i.resolve("")}})}},{"..":2,"any-promise":3}],20:[function(e,r){"use strict";var t=e(".."),n=t.lexical,i=new RegExp("("+n.identifier.source+")");r.exports=function(e){e.registerTag("capture",{parse:function(r,t){var n=this,s=r.args.match(i);if(!s)throw new Error(r.args+" not valid identifier");this.variable=s[1],this.templates=[];var o=e.parser.parseStream(t);o.on("tag:endcapture",function(){return o.stop()}).on("template",function(e){return n.templates.push(e)}).on("end",function(){throw new Error("tag "+r.raw+" not closed")}),o.start()},render:function(r){var t=this;return e.renderer.renderTemplates(this.templates,r).then(function(e){r.set(t.variable,e)})}})}},{"..":2}],21:[function(e,r){"use strict";var t=e("..");r.exports=function(e){e.registerTag("case",{parse:function(r,t){var n=this;this.cond=r.args,this.cases=[],this.elseTemplates=[];var i=[],s=e.parser.parseStream(t).on("tag:when",function(e){n.cases[e.args]||n.cases.push({val:e.args,templates:i=[]})}).on("tag:else",function(){return i=n.elseTemplates}).on("tag:endcase",function(){return s.stop()}).on("template",function(e){return i.push(e)}).on("end",function(){throw new Error("tag "+r.raw+" not closed")});s.start()},render:function(r){for(var n=0;n",l=i.offset||0,f=void 0===i.limit?u.length:i.limit,p=i.cols;if(!p)throw new Error("illegal cols: "+p);u=u.slice(l,l+f);var h=[];u.some(function(e){var r={};r[a.variable]=e,h.push(r)});var g=h.reduce(function(t,n,i){return t.then(function(){return s=Math.floor(i/p)+1,o=i%p+1,1===o&&(1!==s&&(c+=""),c+=''),c+=''}).then(function(){return r.push(n),e.renderer.renderTemplates(a.templates,r)}).then(function(e){return r.pop(n),c+=e,c+=""})},n.resolve(""));return g.then(function(){return s>0&&(c+=""),c+=""})["catch"](function(e){throw e})}})}},{"..":2,"any-promise":3}],33:[function(e,r){"use strict";var t=e("..");r.exports=function(e){e.registerTag("unless",{parse:function(r,t){var n=this;this.templates=[],this.elseTemplates=[];var i,s=e.parser.parseStream(t).on("start",function(){i=n.templates,n.cond=r.args}).on("tag:else",function(){return i=n.elseTemplates}).on("tag:endunless",function(){return s.stop()}).on("template",function(e){return i.push(e)}).on("end",function(){throw new Error("tag "+r.raw+" not closed")});s.start()},render:function(r){var n=t.evalExp(this.cond,r);return t.isFalsy(n)?e.renderer.renderTemplates(this.templates,r):e.renderer.renderTemplates(this.elseTemplates,r)}})}},{"..":2}]},{},[2])(2)}); \ No newline at end of file diff --git a/package.json b/package.json index 115cd07cb..34dda96c8 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "shopify-liquid", - "version": "1.2.5", + "version": "1.3.0", "description": "Liquid template engine for JavaScript, Node.js and Browser", "main": "index.js", "scripts": { diff --git a/src/lexical.js b/src/lexical.js index 8ef832f5f..8854688e6 100644 --- a/src/lexical.js +++ b/src/lexical.js @@ -34,6 +34,7 @@ var numberLine = new RegExp(`^${number.source}$`); var boolLine = new RegExp(`^${bool.source}$`, 'i'); var quotedLine = new RegExp(`^${quoted.source}$`); var rangeLine = new RegExp(`^${rangeCapture.source}$`); +var integerLine = new RegExp(`^${integer.source}$`); // filter related var valueList = new RegExp(`${value.source}(\\s*,\\s*${value.source})*`); @@ -47,6 +48,10 @@ var operators = [ /==|!=|<=|>=|<|>|\s+contains\s+/ ]; +function isInteger(str){ + return integerLine.test(str); +} + function isLiteral(str) { return literalLine.test(str); } @@ -82,5 +87,5 @@ module.exports = { range, rangeCapture, identifier, value, quoteBalanced, operators, quotedLine, numberLine, boolLine, rangeLine, literalLine, filterLine, tagLine, - isLiteral, isVariable, parseLiteral, isRange, matchValue + isLiteral, isVariable, parseLiteral, isRange, matchValue, isInteger }; diff --git a/src/scope.js b/src/scope.js index 3741ebe3a..9a98fdef3 100644 --- a/src/scope.js +++ b/src/scope.js @@ -1,79 +1,157 @@ const _ = require('./util/underscore.js'); +const lexical = require('./lexical.js'); var Scope = { - safeGet: function(str) { - var i; - // get all - if (str === undefined) { - var ctx = {}; - for (i = this.scopes.length - 1; i >= 0; i--) { - var scp = this.scopes[i]; - for (var k in scp) { - if (scp.hasOwnProperty(k)) { - ctx[k] = scp[k]; - } - } - } - return ctx; - } - // get one path - for (i = this.scopes.length - 1; i >= 0; i--) { - var v = getPropertyByPath(this.scopes[i], str); - if (v !== undefined) return v; - } - }, - get: function(str) { - var val = this.safeGet(str); - if (val === undefined && this.opts.strict) { - throw new Error(`[strict_variables] undefined variable: ${str}`); - } - return val; - }, - set: function(k, v) { - setPropertyByPath(this.scopes[this.scopes.length - 1], k, v); - return this; - }, - push: function(ctx) { - if (!ctx) throw new Error(`trying to push ${ctx} into scopes`); - return this.scopes.push(ctx); - }, - pop: function() { - return this.scopes.pop(); - } + safeGet: function(str) { + var i; + // get all + if (str === undefined) { + var ctx = {}; + for (i = this.scopes.length - 1; i >= 0; i--) { + var scp = this.scopes[i]; + for (var k in scp) { + if (scp.hasOwnProperty(k)) { + ctx[k] = scp[k]; + } + } + } + return ctx; + } + // get one path + for (i = this.scopes.length - 1; i >= 0; i--) { + var v = this.getPropertyByPath(this.scopes[i], str); + if (v !== undefined) return v; + } + }, + get: function(str) { + var val = this.safeGet(str); + if (val === undefined && this.opts.strict) { + throw new Error(`[strict_variables] undefined variable: ${str}`); + } + return val; + }, + set: function(k, v) { + this.setPropertyByPath(this.scopes[this.scopes.length - 1], k, v); + return this; + }, + push: function(ctx) { + if (!ctx) throw new Error(`trying to push ${ctx} into scopes`); + return this.scopes.push(ctx); + }, + pop: function() { + return this.scopes.pop(); + }, + + setPropertyByPath: function(obj, path, val) { + if (_.isString(path)) { + var paths = path.replace(/\[/g, '.').replace(/\]/g, '').split('.'); + for (var i = 0; i < paths.length; i++) { + var key = paths[i]; + if (i === paths.length - 1) { + return obj[key] = val; + } + if (undefined === obj[key]) obj[key] = {}; + // case for readonly objects + obj = obj[key] || {}; + } + return obj; + } + return obj[path] = val; + }, + + getPropertyByPath: function(obj, path) { + if (_.isString(path) && path.length) { + var paths = this.propertyAccessSeq(path); + paths.forEach(p => obj = obj && obj[p]); + return obj; + } + return obj[path]; + }, + + /* + * Parse property access sequence from access string + * @example + * accessSeq("foo.bar") // ['foo', 'bar'] + * accessSeq("foo['bar']") // ['foo', 'bar'] + * accessSeq("foo['b]r']") // ['foo', 'b]r'] + * accessSeq("foo[bar.coo]") // ['foo', 'bar'], for bar.coo == 'bar' + */ + propertyAccessSeq: function(str) { + var seq = [], + name = ''; + for (var i = 0; i < str.length; i++) { + if (str[i] === '[') { + seq.push(name); + name = ''; + + var delemiter = str[i + 1]; + // foo[bar.coo] + if (delemiter !== "'" && delemiter !== '"') { + var j = matchRightBracket(str, i + 1); + if (j === -1) { + throw new Error(`unbalanced []: ${str}`); + } + name = str.slice(i + 1, j); + // foo[1] + if(lexical.isInteger(name)){ + seq.push(name); + } + // foo["bar"] + else{ + seq.push(this.get(name)); + } + name = ''; + i = j; + } + // foo["bar"] + else { + var j = str.indexOf(delemiter, i + 2); + if (j === -1) { + throw new Error(`unbalanced ${delemiter}: ${str}`); + } + name = str.slice(i + 2, j); + seq.push(name); + name = ''; + i = j + 1; + } + } + // foo.bar + else if (str[i] === ".") { + seq.push(name); + name = ''; + } + //foo.bar + else { + name += str[i]; + } + } + if (name.length) seq.push(name); + return seq; + } }; -function setPropertyByPath(obj, path, val) { - if (_.isString(path)) { - var paths = path.replace(/\[/g, '.').replace(/\]/g, '').split('.'); - for (var i = 0; i < paths.length; i++) { - var key = paths[i]; - if (i === paths.length - 1) { - return obj[key] = val; - } - if (undefined === obj[key]) obj[key] = {}; - // case for readonly objects - obj = obj[key] || {}; - } - return obj; - } - return obj[path] = val; -} - -function getPropertyByPath(obj, path) { - if (_.isString(path)) { - var paths = path.replace(/\[/g, '.').replace(/\]/g, '').split('.'); - paths.forEach(p => obj = obj && obj[p]); - return obj; - } - return obj[path]; +function matchRightBracket(str, begin) { + var stack = 1; // count of '[' - count of ']' + for (var i = begin; i < str.length; i++) { + if (str[i] === '[') { + stack++; + } + if (str[i] === ']') { + stack--; + if (stack === 0) { + return i; + } + } + } + return -1; } exports.factory = function(_ctx, opts) { - opts = opts || {}; - opts.strict = opts.strict || false; + opts = opts || {}; + opts.strict = opts.strict || false; - var scope = Object.create(Scope); - scope.opts = opts; - scope.scopes = [_ctx || {}]; - return scope; + var scope = Object.create(Scope); + scope.opts = opts; + scope.scopes = [_ctx || {}]; + return scope; }; diff --git a/tags/layout.js b/tags/layout.js index 7cb9ed355..3b09bf222 100644 --- a/tags/layout.js +++ b/tags/layout.js @@ -40,7 +40,7 @@ module.exports = function(liquid) { liquid.registerTag('block', { parse: function(token, remainTokens){ var match = /\w+/.exec(token.args); - this.block = match ? match[0] : ''; + this.block = match ? match[0] : 'anonymous'; this.tpls = []; var p, stream = liquid.parser.parseStream(remainTokens) diff --git a/test/scope.js b/test/scope.js index c15ee7e3e..3a8332eca 100644 --- a/test/scope.js +++ b/test/scope.js @@ -4,69 +4,114 @@ const expect = chai.expect; var Scope = require('../src/scope.js'); describe('scope', function() { - var scope, ctx; - beforeEach(function() { - ctx = { - foo: 'bar' - }; - scope = Scope.factory(ctx); - }); + var scope, ctx; + beforeEach(function() { + ctx = { + foo: 'zoo', + bar: { + zoo: 'coo', + "Mr.Smith": 'John', + arr: ['a', 'b'] + } + }; + scope = Scope.factory(ctx); + }); - it('should get direct property', function() { - expect(scope.get('foo')).equal('bar'); - }); + describe('#propertyAccessSeq()', function() { + it('should handle dot syntax', function() { + expect(scope.propertyAccessSeq('foo.bar')) + .to.deep.equal(['foo', 'bar']); + }); + it('should handle [] syntax', function() { + expect(scope.propertyAccessSeq('foo["bar"]')) + .to.deep.equal(['foo', 'bar']); + }); + it('should handle [] syntax', function() { + expect(scope.propertyAccessSeq('foo[foo]')) + .to.deep.equal(['foo', 'zoo']); + }); + it('should handle nested access', function() { + expect(scope.propertyAccessSeq('foo[bar.zoo]')) + .to.deep.equal(['foo', 'coo']); + expect(scope.propertyAccessSeq('foo[bar["zoo"]]')) + .to.deep.equal(['foo', 'coo']); + }); + }); - it('should get undefined property', function() { - function fn() { - scope.get('notdefined'); - } - expect(fn).to.not.throw(); - expect(scope.get('notdefined')).to.equal(undefined); - expect(scope.get('')).to.equal(undefined); - expect(scope.get(false)).to.equal(undefined); - }); + describe('#get()', function() { + it('should get direct property', function() { + expect(scope.get('foo')).equal('zoo'); + }); - it('should throw undefined in strict mode', function() { - scope = Scope.factory(ctx, { - strict: true - }); + it('should get undefined property', function() { + function fn() { + scope.get('notdefined'); + } + expect(fn).to.not.throw(); + expect(scope.get('notdefined')).to.equal(undefined); + expect(scope.get('')).to.equal(undefined); + expect(scope.get(false)).to.equal(undefined); + }); - function fn() { - scope.get('notdefined'); - } - expect(fn).to.throw(/undefined variable: notdefined/); - }); + it('should throw undefined in strict mode', function() { + scope = Scope.factory(ctx, { + strict: true + }); - it('should get all properties when arguments empty', function() { - expect(scope.get()).deep.equal(ctx); - }); + function fn() { + scope.get('notdefined'); + } + expect(fn).to.throw(/undefined variable: notdefined/); + }); - it('should access child property via dot syntax', function() { - scope.set('oo.bar', 'FOO'); - expect(scope.get('oo.bar')).to.equal('FOO'); - }); + it('should get all properties when arguments empty', function() { + expect(scope.get()).deep.equal(ctx); + }); - it('should access child property via [] syntax', function() { - scope.set('bar', ['a', {'b': [1,2]}]); - expect(scope.get('bar[0]')).to.equal('a'); - expect(scope.get('bar[1].b')).to.deep.equal([1, 2]); - expect(scope.get('bar[1].b[1]')).to.equal(2); - }); + it('should access child property via dot syntax', function() { + expect(scope.get('bar.zoo')).to.equal('coo'); + expect(scope.get('bar.arr')).to.deep.equal(['a', 'b']); + }); - it('should push scope', function() { - scope.set('bar', 'bar'); - scope.push({ - foo: 'foo' - }); - expect(scope.get('foo')).to.equal('foo'); - expect(scope.get('bar')).to.equal('bar'); - }); + it('should access child property via [] syntax', function() { + expect(scope.get('bar["zoo"]')).to.equal('coo'); + }); - it('should pop scope', function() { - scope.push({ - foo: 'foo' - }); - scope.pop(); - expect(scope.get('foo')).to.equal('bar'); - }); + it('should access child property via [] syntax', function() { + expect(scope.get('bar.arr[0]')).to.equal('a'); + }); + + it('should access child property via [] syntax', function() { + expect(scope.get('bar[foo]')).to.equal('coo'); + }); + + it('should support nested case', function() { + scope.set('posts', { + "first": {"name": "A Nice Day"} + }); + scope.set('category', { + "diary": ["first"] + }); + expect(scope.get('posts[category.diary[0]].name'), 'A Nice Day'); + }); + }); + + describe('.push(), .pop()', function() { + it('should push scope', function() { + scope.set('bar', 'bar'); + scope.push({ + foo: 'foo' + }); + expect(scope.get('foo')).to.equal('foo'); + expect(scope.get('bar')).to.equal('bar'); + }); + + it('should pop scope', function() { + scope.push({ + foo: 'foo' + }); + scope.pop(); + expect(scope.get('foo')).to.equal('zoo'); + }); + }); }); diff --git a/test/syntax.js b/test/syntax.js index 75f2b37a7..b13743443 100644 --- a/test/syntax.js +++ b/test/syntax.js @@ -13,8 +13,7 @@ describe('expression', function() { scope = Scope.factory({ one: 1, two: 2, - x: 'XXX', - z: 'z' + x: 'XXX' }); }); @@ -41,7 +40,6 @@ describe('expression', function() { expect(evalExp('one<=two', scope)).to.equal(true); expect(evalExp('x contains "x"', scope)).to.equal(false); expect(evalExp('x contains "X"', scope)).to.equal(true); - expect(evalExp('x contains z', scope)).to.equal(false); expect(evalExp('"<=" == "<="', scope)).to.equal(true); }); @@ -51,7 +49,7 @@ describe('expression', function() { }); it("should eval range expression", function() { - expect(evalExp('(2..4)', scope)).to.deep.equal([2,3,4]); - expect(evalExp('(two..4)', scope)).to.deep.equal([2,3,4]); + expect(evalExp('(2..4)', scope)).to.deep.equal([2, 3, 4]); + expect(evalExp('(two..4)', scope)).to.deep.equal([2, 3, 4]); }); });