From 16a66f2b18a4109448f406da38ae9b45d96e6150 Mon Sep 17 00:00:00 2001 From: harttle Date: Tue, 27 Sep 2016 02:28:20 +0800 Subject: [PATCH] feature: browser side support, close #6 --- README.md | 32 +- demo/browser/index.html | 26 + demo/{ => express}/bar.liquid | 0 demo/{ => express}/foo.liquid | 0 demo/{ => express}/index.js | 0 demo/{ => express}/package.json | 0 dist/shopify-liquid.js | 3858 +++++++++++++++++++++++++++++++ dist/shopify-liquid.min.js | 4 + filters.js | 91 +- index.js | 17 +- makefile | 17 + package.json | 12 +- src/expression.js | 9 +- src/filter.js | 1 - src/lexical.js | 2 - src/render.js | 6 +- src/scope.js | 45 +- tags/for.js | 1 - test/filters.js | 90 +- test/scope.js | 4 +- 20 files changed, 4108 insertions(+), 107 deletions(-) create mode 100644 demo/browser/index.html rename demo/{ => express}/bar.liquid (100%) rename demo/{ => express}/foo.liquid (100%) rename demo/{ => express}/index.js (100%) rename demo/{ => express}/package.json (100%) create mode 100644 dist/shopify-liquid.js create mode 100644 dist/shopify-liquid.min.js create mode 100644 makefile diff --git a/README.md b/README.md index 4e2774a0b..645007b1a 100644 --- a/README.md +++ b/README.md @@ -86,7 +86,35 @@ app.set('views', './views'); // specify the views directory app.set('view engine', 'liquid'); // set to default ``` -There's an Express demo in [the demo folder](demo/). +There's an Express demo [here](demo/express/). + +## Use in Browser + +[Download][releases] the dist files and import into your HTML. +And `window.Liquid` is what you want. + +```html + + + + + + + + +``` + +There's also a [demo](demo/browser/). ## Includes @@ -248,4 +276,4 @@ Documentation: [shopify-liquid]: https://shopify.github.io/liquid/ [jekyll]: http://jekyllrb.com/ [gh]: https://pages.github.com/ -(test/filters.js): https://github.com/harttle/shopify-liquid/blob/master/test/filters.js +[releases]: https://github.com/harttle/shopify-liquid/releases diff --git a/demo/browser/index.html b/demo/browser/index.html new file mode 100644 index 000000000..e9d228149 --- /dev/null +++ b/demo/browser/index.html @@ -0,0 +1,26 @@ + + + + + + + + + + + + + + + diff --git a/demo/bar.liquid b/demo/express/bar.liquid similarity index 100% rename from demo/bar.liquid rename to demo/express/bar.liquid diff --git a/demo/foo.liquid b/demo/express/foo.liquid similarity index 100% rename from demo/foo.liquid rename to demo/express/foo.liquid diff --git a/demo/index.js b/demo/express/index.js similarity index 100% rename from demo/index.js rename to demo/express/index.js diff --git a/demo/package.json b/demo/express/package.json similarity index 100% rename from demo/package.json rename to demo/express/package.json diff --git a/dist/shopify-liquid.js b/dist/shopify-liquid.js new file mode 100644 index 000000000..050b7dda1 --- /dev/null +++ b/dist/shopify-liquid.js @@ -0,0 +1,3858 @@ +(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': '>', + '"': '"', + "'": ''' + }; + + function escape(str) { + return (str || '').replace(/&|<|>|"|'/g, function (m) { + return escapeMap[m]; + }); + } + liquid.registerFilter('escape', escape); + + var unescapeMap = { + '&': '&', + '<': '<', + '>': '>', + '"': '"', + ''': "'" + }; + + function unescape(str) { + return (str || '').replace(/&(amp|lt|gt|#34|#39);/g, function (m) { + return unescapeMap[m]; + }); + } + liquid.registerFilter('escape_once', function (str) { + return escape(unescape(str)); + }); + liquid.registerFilter('first', function (v) { + return v[0]; + }); + liquid.registerFilter('floor', function (v) { + return Math.floor(v); + }); + liquid.registerFilter('join', function (v, arg) { + return v.join(arg); + }); + liquid.registerFilter('last', function (v) { + return v[v.length - 1]; + }); + liquid.registerFilter('lstrip', function (v) { + return (v || '').replace(/^\s+/, ''); + }); + liquid.registerFilter('map', function (arr, arg) { + return arr.map(function (v) { + return v[arg]; + }); + }); + liquid.registerFilter('minus', bindFixed(function (v, arg) { + return v - arg; + })); + liquid.registerFilter('modulo', bindFixed(function (v, arg) { + return v % arg; + })); + liquid.registerFilter('newline_to_br', function (v) { + return v.replace(/\n/g, '
'); + }); + liquid.registerFilter('plus', bindFixed(function (v, arg) { + return v + arg; + })); + liquid.registerFilter('prepend', function (v, arg) { + return arg + v; + }); + liquid.registerFilter('remove', function (v, arg) { + return v.split(arg).join(''); + }); + liquid.registerFilter('remove_first', function (v, l) { + return v.replace(l, ''); + }); + liquid.registerFilter('replace', function (v, pattern, replacement) { + return (v || '').split(pattern).join(replacement); + }); + liquid.registerFilter('replace_first', function (v, arg1, arg2) { + return (v || '').replace(arg1, arg2); + }); + liquid.registerFilter('reverse', function (v) { + return (v || '').reverse(); + }); + liquid.registerFilter('round', function (v, arg) { + var amp = Math.pow(10, arg || 0); + return Math.round(v * amp, arg) / amp; + }); + liquid.registerFilter('rstrip', function (str) { + return (str || '').replace(/\s+$/, ''); + }); + liquid.registerFilter('size', function (v) { + return v.length; + }); + liquid.registerFilter('slice', function (v, begin, length) { + return v.substr(begin, length === undefined ? 1 : length); + }); + liquid.registerFilter('sort', function (v, arg) { + return (v || '').sort(arg); + }); + liquid.registerFilter('split', function (v, arg) { + return (v || '').split(arg); + }); + liquid.registerFilter('strip', function (v) { + return (v || '').trim(); + }); + liquid.registerFilter('strip_html', function (v) { + return (v || '').replace(/<\/?\s*\w+\s*\/?>/g, ''); + }); + liquid.registerFilter('strip_newlines', function (v) { + return (v || '').replace(/\n/g, ''); + }); + liquid.registerFilter('times', function (v, arg) { + return v * arg; + }); + liquid.registerFilter('truncate', function (v, l, o) { + v = 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) { + 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) { + var u = {}; + return (arr || []).filter(function (val) { + if (u.hasOwnProperty(val)) { + return false; + } + u[val] = true; + return true; + }); + }); + liquid.registerFilter('upcase', function (str) { + return (str || '').toUpperCase(); + }); + liquid.registerFilter('url_encode', encodeURIComponent); +}; + +function getFixed(v) { + var p = (v + "").split("."); + return p.length > 1 ? p[1].length : 0; +} + +function getMaxFixed(l, r) { + return Math.max(getFixed(l), getFixed(r)); +} + +function bindFixed(cb) { + return function (l, r) { + var f = getMaxFixed(l, r); + return cb(l, r).toFixed(f); + }; +} + +},{"strftime":10}],2:[function(require,module,exports){ +'use strict'; + +var Scope = require('./src/scope'); +var assert = require('assert'); +var tokenizer = require('./src/tokenizer.js'); +var Render = require('./src/render.js'); +var lexical = require('./src/lexical.js'); +var path = require("path"); +var fs = require('fs'); +var Tag = require('./src/tag.js'); +var Filter = require('./src/filter.js'); +var Template = require('./src/parser'); +var Expression = require('./src/expression.js'); +var tags = require('./tags'); +var filters = require('./filters'); +var Promise = require('any-promise'); + +var _engine = { + init: function init(tag, filter, options) { + if (options.cache) { + this.cache = {}; + } + this.options = options; + this.tag = tag; + this.filter = filter; + this.parser = Template(tag, filter); + this.renderer = Render(); + + tags(this); + filters(this); + + return this; + }, + parse: function parse(html) { + var tokens = tokenizer.parse(html); + return this.parser.parse(tokens); + }, + render: function render(tpl, ctx, opts) { + opts = opts || {}; + opts.strict_variables = opts.strict_variables || false; + opts.strict_filters = opts.strict_filters || false; + + this.renderer.resetRegisters(); + var scope = Scope.factory(ctx, { + strict: opts.strict_variables + }); + return this.renderer.renderTemplates(tpl, scope, opts); + }, + parseAndRender: function parseAndRender(html, ctx, opts) { + try { + var tpl = this.parse(html); + return this.render(tpl, ctx, opts); + } catch (error) { + // A throw inside of a then or catch of a Promise automatically rejects, but since we mix a sync call + // with an async call, we need to do this in case the sync call throws. + return Promise.reject(error); + } + }, + renderFile: function renderFile(filepath, ctx, opts) { + var _this = this; + + return this.handleCache(filepath).then(function (templates) { + return _this.render(templates, ctx, opts); + }).catch(function (e) { + e.file = filepath; + throw e; + }); + }, + evalOutput: function evalOutput(str, scope) { + var tpl = this.parser.parseOutput(str.trim()); + return this.renderer.evalOutput(tpl, scope); + }, + registerFilter: function registerFilter(name, filter) { + return this.filter.register(name, filter); + }, + registerTag: function registerTag(name, tag) { + return this.tag.register(name, tag); + }, + handleCache: function handleCache(filepath) { + var _this2 = this; + + assert(filepath, 'filepath cannot be null'); + filepath = path.resolve(this.options.root, filepath); + if (path.extname(filepath) === '') { + filepath += this.options.extname; + } + + return this.getTemplate(filepath).then(function (html) { + var tpl = _this2.options.cache && _this2.cache[filepath] || _this2.parse(html); + return _this2.options.cache ? _this2.cache[filepath] = tpl : tpl; + }); + }, + getTemplate: function getTemplate(filepath) { + return new Promise(function (resolve, reject) { + fs.readFile(filepath, 'utf8', function (err, html) { + err ? reject(err) : resolve(html); + }); + }); + }, + express: function express() { + var _this3 = this; + + return function (filePath, options, callback) { + _this3.renderFile(filePath, options).then(function (html) { + return callback(null, html); + }).catch(function (e) { + return callback(e); + }); + }; + } +}; + +function factory(options) { + options = options || {}; + options.root = options.root || ''; + options.extname = options.extname || '.liquid'; + + var engine = Object.create(_engine); + + engine.init(Tag(), Filter(), options); + return engine; +} + +factory.lexical = lexical; +factory.isTruthy = Expression.isTruthy; +factory.isFalsy = Expression.isFalsy; +factory.evalExp = Expression.evalExp; +factory.evalValue = Expression.evalValue; + +module.exports = factory; + +},{"./filters":1,"./src/expression.js":15,"./src/filter.js":16,"./src/lexical.js":17,"./src/parser":18,"./src/render.js":19,"./src/scope":20,"./src/tag.js":22,"./src/tokenizer.js":23,"./tags":34,"any-promise":3,"assert":6,"fs":7,"path":8}],3:[function(require,module,exports){ +'use strict'; + +module.exports = require('./register')().Promise; + +},{"./register":5}],4:[function(require,module,exports){ +"use strict"; +// global key for user preferred registration + +var REGISTRATION_KEY = '@@any-promise/REGISTRATION', + +// Prior registration (preferred or detected) +registered = null; + +/** + * Registers the given implementation. An implementation must + * be registered prior to any call to `require("any-promise")`, + * typically on application load. + * + * If called with no arguments, will return registration in + * following priority: + * + * For Node.js: + * + * 1. Previous registration + * 2. global.Promise if node.js version >= 0.12 + * 3. Auto detected promise based on first sucessful require of + * known promise libraries. Note this is a last resort, as the + * loaded library is non-deterministic. node.js >= 0.12 will + * always use global.Promise over this priority list. + * 4. Throws error. + * + * For Browser: + * + * 1. Previous registration + * 2. window.Promise + * 3. Throws error. + * + * Options: + * + * Promise: Desired Promise constructor + * global: Boolean - Should the registration be cached in a global variable to + * allow cross dependency/bundle registration? (default true) + */ +module.exports = function (root, loadImplementation) { + return function register(implementation, opts) { + implementation = implementation || null; + opts = opts || {}; + // global registration unless explicitly {global: false} in options (default true) + var registerGlobal = opts.global !== false; + + // load any previous global registration + if (registered === null && registerGlobal) { + registered = root[REGISTRATION_KEY] || null; + } + + if (registered !== null && implementation !== null && registered.implementation !== implementation) { + // Throw error if attempting to redefine implementation + throw new Error('any-promise already defined as "' + registered.implementation + '". You can only register an implementation before the first ' + ' call to require("any-promise") and an implementation cannot be changed'); + } + + if (registered === null) { + // use provided implementation + if (implementation !== null && typeof opts.Promise !== 'undefined') { + registered = { + Promise: opts.Promise, + implementation: implementation + }; + } else { + // require implementation if implementation is specified but not provided + registered = loadImplementation(implementation); + } + + if (registerGlobal) { + // register preference globally in case multiple installations + root[REGISTRATION_KEY] = registered; + } + } + + return registered; + }; +}; + +},{}],5:[function(require,module,exports){ +"use strict"; + +module.exports = require('./loader')(window, loadImplementation); + +/** + * Browser specific loadImplementation. Always uses `window.Promise` + * + * To register a custom implementation, must register with `Promise` option. + */ +function loadImplementation() { + if (typeof window.Promise === 'undefined') { + 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' + }; +} + +},{"./loader":4}],6:[function(require,module,exports){ +'use strict'; + +// http://wiki.commonjs.org/wiki/Unit_Testing/1.0 +// +// THIS IS NOT TESTED NOR LIKELY TO WORK OUTSIDE V8! +// +// Originally from narwhal.js (http://narwhaljs.org) +// Copyright (c) 2009 Thomas Robinson <280north.com> +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the 'Software'), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN +// ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +// when used in node, this will actually load the util module we depend on +// versus loading the builtin util module as happens otherwise +// this is a bug in node module loading as far as I am concerned +var util = require('util/'); + +var pSlice = Array.prototype.slice; +var hasOwn = Object.prototype.hasOwnProperty; + +// 1. The assert module provides functions that throw +// AssertionError's when particular conditions are not met. The +// assert module must conform to the following interface. + +var assert = module.exports = ok; + +// 2. The AssertionError is defined in assert. +// new assert.AssertionError({ message: message, +// actual: actual, +// expected: expected }) + +assert.AssertionError = function AssertionError(options) { + this.name = 'AssertionError'; + this.actual = options.actual; + this.expected = options.expected; + this.operator = options.operator; + if (options.message) { + this.message = options.message; + this.generatedMessage = false; + } else { + this.message = getMessage(this); + this.generatedMessage = true; + } + var stackStartFunction = options.stackStartFunction || fail; + + if (Error.captureStackTrace) { + Error.captureStackTrace(this, stackStartFunction); + } else { + // non v8 browsers so we can have a stacktrace + var err = new Error(); + if (err.stack) { + var out = err.stack; + + // try to strip useless frames + var fn_name = stackStartFunction.name; + var idx = out.indexOf('\n' + fn_name); + if (idx >= 0) { + // once we have located the function frame + // we need to strip out everything before it (and its line) + var next_line = out.indexOf('\n', idx + 1); + out = out.substring(next_line + 1); + } + + this.stack = out; + } + } +}; + +// assert.AssertionError instanceof Error +util.inherits(assert.AssertionError, Error); + +function replacer(key, value) { + if (util.isUndefined(value)) { + return '' + value; + } + if (util.isNumber(value) && !isFinite(value)) { + return value.toString(); + } + if (util.isFunction(value) || util.isRegExp(value)) { + return value.toString(); + } + return value; +} + +function truncate(s, n) { + if (util.isString(s)) { + return s.length < n ? s : s.slice(0, n); + } else { + return s; + } +} + +function getMessage(self) { + return truncate(JSON.stringify(self.actual, replacer), 128) + ' ' + self.operator + ' ' + truncate(JSON.stringify(self.expected, replacer), 128); +} + +// At present only the three keys mentioned above are used and +// understood by the spec. Implementations or sub modules can pass +// other keys to the AssertionError's constructor - they will be +// ignored. + +// 3. All of the following functions must throw an AssertionError +// when a corresponding condition is not met, with a message that +// may be undefined if not provided. All assertion methods provide +// both the actual and expected values to the assertion error for +// display purposes. + +function fail(actual, expected, message, operator, stackStartFunction) { + throw new assert.AssertionError({ + message: message, + actual: actual, + expected: expected, + operator: operator, + stackStartFunction: stackStartFunction + }); +} + +// EXTENSION! allows for well behaved errors defined elsewhere. +assert.fail = fail; + +// 4. Pure assertion tests whether a value is truthy, as determined +// by !!guard. +// assert.ok(guard, message_opt); +// This statement is equivalent to assert.equal(true, !!guard, +// message_opt);. To test strictly for the value true, use +// assert.strictEqual(true, guard, message_opt);. + +function ok(value, message) { + if (!value) fail(value, true, message, '==', assert.ok); +} +assert.ok = ok; + +// 5. The equality assertion tests shallow, coercive equality with +// ==. +// assert.equal(actual, expected, message_opt); + +assert.equal = function equal(actual, expected, message) { + if (actual != expected) fail(actual, expected, message, '==', assert.equal); +}; + +// 6. The non-equality assertion tests for whether two objects are not equal +// with != assert.notEqual(actual, expected, message_opt); + +assert.notEqual = function notEqual(actual, expected, message) { + if (actual == expected) { + fail(actual, expected, message, '!=', assert.notEqual); + } +}; + +// 7. The equivalence assertion tests a deep equality relation. +// assert.deepEqual(actual, expected, message_opt); + +assert.deepEqual = function deepEqual(actual, expected, message) { + if (!_deepEqual(actual, expected)) { + fail(actual, expected, message, 'deepEqual', assert.deepEqual); + } +}; + +function _deepEqual(actual, expected) { + // 7.1. All identical values are equivalent, as determined by ===. + if (actual === expected) { + return true; + } else if (util.isBuffer(actual) && util.isBuffer(expected)) { + if (actual.length != expected.length) return false; + + for (var i = 0; i < actual.length; i++) { + if (actual[i] !== expected[i]) return false; + } + + return true; + + // 7.2. If the expected value is a Date object, the actual value is + // equivalent if it is also a Date object that refers to the same time. + } else if (util.isDate(actual) && util.isDate(expected)) { + return actual.getTime() === expected.getTime(); + + // 7.3 If the expected value is a RegExp object, the actual value is + // equivalent if it is also a RegExp object with the same source and + // properties (`global`, `multiline`, `lastIndex`, `ignoreCase`). + } else if (util.isRegExp(actual) && util.isRegExp(expected)) { + return actual.source === expected.source && actual.global === expected.global && actual.multiline === expected.multiline && actual.lastIndex === expected.lastIndex && actual.ignoreCase === expected.ignoreCase; + + // 7.4. Other pairs that do not both pass typeof value == 'object', + // equivalence is determined by ==. + } else if (!util.isObject(actual) && !util.isObject(expected)) { + return actual == expected; + + // 7.5 For all other Object pairs, including Array objects, equivalence is + // determined by having the same number of owned properties (as verified + // with Object.prototype.hasOwnProperty.call), the same set of keys + // (although not necessarily the same order), equivalent values for every + // corresponding key, and an identical 'prototype' property. Note: this + // accounts for both named and indexed properties on Arrays. + } else { + return objEquiv(actual, expected); + } +} + +function isArguments(object) { + return Object.prototype.toString.call(object) == '[object Arguments]'; +} + +function objEquiv(a, b) { + if (util.isNullOrUndefined(a) || util.isNullOrUndefined(b)) return false; + // an identical 'prototype' property. + if (a.prototype !== b.prototype) return false; + // if one is a primitive, the other must be same + if (util.isPrimitive(a) || util.isPrimitive(b)) { + return a === b; + } + var aIsArgs = isArguments(a), + bIsArgs = isArguments(b); + if (aIsArgs && !bIsArgs || !aIsArgs && bIsArgs) return false; + if (aIsArgs) { + a = pSlice.call(a); + b = pSlice.call(b); + return _deepEqual(a, b); + } + var ka = objectKeys(a), + kb = objectKeys(b), + key, + i; + // having the same number of owned properties (keys incorporates + // hasOwnProperty) + if (ka.length != kb.length) return false; + //the same set of keys (although not necessarily the same order), + ka.sort(); + kb.sort(); + //~~~cheap key test + for (i = ka.length - 1; i >= 0; i--) { + if (ka[i] != kb[i]) return false; + } + //equivalent values for every corresponding key, and + //~~~possibly expensive deep test + for (i = ka.length - 1; i >= 0; i--) { + key = ka[i]; + if (!_deepEqual(a[key], b[key])) return false; + } + return true; +} + +// 8. The non-equivalence assertion tests for any deep inequality. +// assert.notDeepEqual(actual, expected, message_opt); + +assert.notDeepEqual = function notDeepEqual(actual, expected, message) { + if (_deepEqual(actual, expected)) { + fail(actual, expected, message, 'notDeepEqual', assert.notDeepEqual); + } +}; + +// 9. The strict equality assertion tests strict equality, as determined by ===. +// assert.strictEqual(actual, expected, message_opt); + +assert.strictEqual = function strictEqual(actual, expected, message) { + if (actual !== expected) { + fail(actual, expected, message, '===', assert.strictEqual); + } +}; + +// 10. The strict non-equality assertion tests for strict inequality, as +// determined by !==. assert.notStrictEqual(actual, expected, message_opt); + +assert.notStrictEqual = function notStrictEqual(actual, expected, message) { + if (actual === expected) { + fail(actual, expected, message, '!==', assert.notStrictEqual); + } +}; + +function expectedException(actual, expected) { + if (!actual || !expected) { + return false; + } + + if (Object.prototype.toString.call(expected) == '[object RegExp]') { + return expected.test(actual); + } else if (actual instanceof expected) { + return true; + } else if (expected.call({}, actual) === true) { + return true; + } + + return false; +} + +function _throws(shouldThrow, block, expected, message) { + var actual; + + if (util.isString(expected)) { + message = expected; + expected = null; + } + + try { + block(); + } catch (e) { + actual = e; + } + + message = (expected && expected.name ? ' (' + expected.name + ').' : '.') + (message ? ' ' + message : '.'); + + if (shouldThrow && !actual) { + fail(actual, expected, 'Missing expected exception' + message); + } + + if (!shouldThrow && expectedException(actual, expected)) { + fail(actual, expected, 'Got unwanted exception' + message); + } + + if (shouldThrow && actual && expected && !expectedException(actual, expected) || !shouldThrow && actual) { + throw actual; + } +} + +// 11. Expected to throw an error: +// assert.throws(block, Error_opt, message_opt); + +assert.throws = function (block, /*optional*/error, /*optional*/message) { + _throws.apply(this, [true].concat(pSlice.call(arguments))); +}; + +// EXTENSION! This is annoying to write outside this module. +assert.doesNotThrow = function (block, /*optional*/message) { + _throws.apply(this, [false].concat(pSlice.call(arguments))); +}; + +assert.ifError = function (err) { + if (err) { + throw err; + } +}; + +var objectKeys = Object.keys || function (obj) { + var keys = []; + for (var key in obj) { + if (hasOwn.call(obj, key)) keys.push(key); + } + return keys; +}; + +},{"util/":13}],7:[function(require,module,exports){ +"use strict"; + +},{}],8:[function(require,module,exports){ +(function (process){ +'use strict'; + +// Copyright Joyent, Inc. and other Node contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit +// persons to whom the Software is furnished to do so, subject to the +// following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +// USE OR OTHER DEALINGS IN THE SOFTWARE. + +// resolves . and .. elements in a path array with directory names there +// must be no slashes, empty elements, or device names (c:\) in the array +// (so also no leading and trailing slashes - it does not distinguish +// relative and absolute paths) +function normalizeArray(parts, allowAboveRoot) { + // if the path tries to go above the root, `up` ends up > 0 + var up = 0; + for (var i = parts.length - 1; i >= 0; i--) { + var last = parts[i]; + if (last === '.') { + parts.splice(i, 1); + } else if (last === '..') { + parts.splice(i, 1); + up++; + } else if (up) { + parts.splice(i, 1); + up--; + } + } + + // if the path is allowed to go above the root, restore leading ..s + if (allowAboveRoot) { + for (; up--; up) { + parts.unshift('..'); + } + } + + return parts; +} + +// Split a filename into [root, dir, basename, ext], unix version +// 'root' is just a slash, or nothing. +var splitPathRe = /^(\/?|)([\s\S]*?)((?:\.{1,2}|[^\/]+?|)(\.[^.\/]*|))(?:[\/]*)$/; +var splitPath = function splitPath(filename) { + return splitPathRe.exec(filename).slice(1); +}; + +// path.resolve([from ...], to) +// posix version +exports.resolve = function () { + var resolvedPath = '', + resolvedAbsolute = false; + + for (var i = arguments.length - 1; i >= -1 && !resolvedAbsolute; i--) { + var path = i >= 0 ? arguments[i] : process.cwd(); + + // Skip empty and invalid entries + if (typeof path !== 'string') { + throw new TypeError('Arguments to path.resolve must be strings'); + } else if (!path) { + continue; + } + + resolvedPath = path + '/' + resolvedPath; + resolvedAbsolute = path.charAt(0) === '/'; + } + + // At this point the path should be resolved to a full absolute path, but + // handle relative paths to be safe (might happen when process.cwd() fails) + + // Normalize the path + resolvedPath = normalizeArray(filter(resolvedPath.split('/'), function (p) { + return !!p; + }), !resolvedAbsolute).join('/'); + + return (resolvedAbsolute ? '/' : '') + resolvedPath || '.'; +}; + +// path.normalize(path) +// posix version +exports.normalize = function (path) { + var isAbsolute = exports.isAbsolute(path), + trailingSlash = substr(path, -1) === '/'; + + // Normalize the path + path = normalizeArray(filter(path.split('/'), function (p) { + return !!p; + }), !isAbsolute).join('/'); + + if (!path && !isAbsolute) { + path = '.'; + } + if (path && trailingSlash) { + path += '/'; + } + + return (isAbsolute ? '/' : '') + path; +}; + +// posix version +exports.isAbsolute = function (path) { + return path.charAt(0) === '/'; +}; + +// posix version +exports.join = function () { + var paths = Array.prototype.slice.call(arguments, 0); + return exports.normalize(filter(paths, function (p, index) { + if (typeof p !== 'string') { + throw new TypeError('Arguments to path.join must be strings'); + } + return p; + }).join('/')); +}; + +// path.relative(from, to) +// posix version +exports.relative = function (from, to) { + from = exports.resolve(from).substr(1); + to = exports.resolve(to).substr(1); + + function trim(arr) { + var start = 0; + for (; start < arr.length; start++) { + if (arr[start] !== '') break; + } + + var end = arr.length - 1; + for (; end >= 0; end--) { + if (arr[end] !== '') break; + } + + if (start > end) return []; + return arr.slice(start, end - start + 1); + } + + var fromParts = trim(from.split('/')); + var toParts = trim(to.split('/')); + + var length = Math.min(fromParts.length, toParts.length); + var samePartsLength = length; + for (var i = 0; i < length; i++) { + if (fromParts[i] !== toParts[i]) { + samePartsLength = i; + break; + } + } + + var outputParts = []; + for (var i = samePartsLength; i < fromParts.length; i++) { + outputParts.push('..'); + } + + outputParts = outputParts.concat(toParts.slice(samePartsLength)); + + return outputParts.join('/'); +}; + +exports.sep = '/'; +exports.delimiter = ':'; + +exports.dirname = function (path) { + var result = splitPath(path), + root = result[0], + dir = result[1]; + + if (!root && !dir) { + // No dirname whatsoever + return '.'; + } + + if (dir) { + // It has a dirname, strip trailing slash + dir = dir.substr(0, dir.length - 1); + } + + return root + dir; +}; + +exports.basename = function (path, ext) { + var f = splitPath(path)[2]; + // TODO: make this comparison case-insensitive on windows? + if (ext && f.substr(-1 * ext.length) === ext) { + f = f.substr(0, f.length - ext.length); + } + return f; +}; + +exports.extname = function (path) { + return splitPath(path)[3]; +}; + +function filter(xs, f) { + if (xs.filter) return xs.filter(f); + var res = []; + for (var i = 0; i < xs.length; i++) { + if (f(xs[i], i, xs)) res.push(xs[i]); + } + return res; +} + +// String.prototype.substr - negative index don't work in IE8 +var substr = 'ab'.substr(-1) === 'b' ? function (str, start, len) { + return str.substr(start, len); +} : function (str, start, len) { + if (start < 0) start = str.length + start; + return str.substr(start, len); +}; + +}).call(this,require('_process')) +},{"_process":9}],9:[function(require,module,exports){ +'use strict'; + +// shim for using process in browser +var process = module.exports = {}; + +// cached from whatever global is present so that test runners that stub it +// don't break things. But we need to wrap it in a try catch in case it is +// wrapped in strict mode code which doesn't define any globals. It's inside a +// function because try/catches deoptimize in certain engines. + +var cachedSetTimeout; +var cachedClearTimeout; + +function defaultSetTimout() { + throw new Error('setTimeout has not been defined'); +} +function defaultClearTimeout() { + throw new Error('clearTimeout has not been defined'); +} +(function () { + try { + if (typeof setTimeout === 'function') { + cachedSetTimeout = setTimeout; + } else { + cachedSetTimeout = defaultSetTimout; + } + } catch (e) { + cachedSetTimeout = defaultSetTimout; + } + try { + if (typeof clearTimeout === 'function') { + cachedClearTimeout = clearTimeout; + } else { + cachedClearTimeout = defaultClearTimeout; + } + } catch (e) { + cachedClearTimeout = defaultClearTimeout; + } +})(); +function runTimeout(fun) { + if (cachedSetTimeout === setTimeout) { + //normal enviroments in sane situations + return setTimeout(fun, 0); + } + // if setTimeout wasn't available but was latter defined + if ((cachedSetTimeout === defaultSetTimout || !cachedSetTimeout) && setTimeout) { + cachedSetTimeout = setTimeout; + return setTimeout(fun, 0); + } + try { + // when when somebody has screwed with setTimeout but no I.E. maddness + return cachedSetTimeout(fun, 0); + } catch (e) { + try { + // When we are in I.E. but the script has been evaled so I.E. doesn't trust the global object when called normally + return cachedSetTimeout.call(null, fun, 0); + } catch (e) { + // same as above but when it's a version of I.E. that must have the global object for 'this', hopfully our context correct otherwise it will throw a global error + return cachedSetTimeout.call(this, fun, 0); + } + } +} +function runClearTimeout(marker) { + if (cachedClearTimeout === clearTimeout) { + //normal enviroments in sane situations + return clearTimeout(marker); + } + // if clearTimeout wasn't available but was latter defined + if ((cachedClearTimeout === defaultClearTimeout || !cachedClearTimeout) && clearTimeout) { + cachedClearTimeout = clearTimeout; + return clearTimeout(marker); + } + try { + // when when somebody has screwed with setTimeout but no I.E. maddness + return cachedClearTimeout(marker); + } catch (e) { + try { + // When we are in I.E. but the script has been evaled so I.E. doesn't trust the global object when called normally + return cachedClearTimeout.call(null, marker); + } catch (e) { + // same as above but when it's a version of I.E. that must have the global object for 'this', hopfully our context correct otherwise it will throw a global error. + // Some versions of I.E. have different rules for clearTimeout vs setTimeout + return cachedClearTimeout.call(this, marker); + } + } +} +var queue = []; +var draining = false; +var currentQueue; +var queueIndex = -1; + +function cleanUpNextTick() { + if (!draining || !currentQueue) { + return; + } + draining = false; + if (currentQueue.length) { + queue = currentQueue.concat(queue); + } else { + queueIndex = -1; + } + if (queue.length) { + drainQueue(); + } +} + +function drainQueue() { + if (draining) { + return; + } + var timeout = runTimeout(cleanUpNextTick); + draining = true; + + var len = queue.length; + while (len) { + currentQueue = queue; + queue = []; + while (++queueIndex < len) { + if (currentQueue) { + currentQueue[queueIndex].run(); + } + } + queueIndex = -1; + len = queue.length; + } + currentQueue = null; + draining = false; + runClearTimeout(timeout); +} + +process.nextTick = function (fun) { + var args = new Array(arguments.length - 1); + if (arguments.length > 1) { + for (var i = 1; i < arguments.length; i++) { + args[i - 1] = arguments[i]; + } + } + queue.push(new Item(fun, args)); + if (queue.length === 1 && !draining) { + runTimeout(drainQueue); + } +}; + +// v8 likes predictible objects +function Item(fun, array) { + this.fun = fun; + this.array = array; +} +Item.prototype.run = function () { + this.fun.apply(null, this.array); +}; +process.title = 'browser'; +process.browser = true; +process.env = {}; +process.argv = []; +process.version = ''; // empty string to avoid regexp issues +process.versions = {}; + +function noop() {} + +process.on = noop; +process.addListener = noop; +process.once = noop; +process.off = noop; +process.removeListener = noop; +process.removeAllListeners = noop; +process.emit = noop; + +process.binding = function (name) { + throw new Error('process.binding is not supported'); +}; + +process.cwd = function () { + return '/'; +}; +process.chdir = function (dir) { + throw new Error('process.chdir is not supported'); +}; +process.umask = function () { + return 0; +}; + +},{}],10:[function(require,module,exports){ +'use strict'; + +var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) { return typeof obj; } : function (obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol ? "symbol" : typeof obj; }; + +// +// strftime +// github.com/samsonjs/strftime +// @_sjs +// +// Copyright 2010 - 2015 Sami Samhuri +// +// MIT License +// http://sjs.mit-license.org +// + +;(function () { + + var DefaultLocale = { + days: ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'], + shortDays: ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'], + months: ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December'], + shortMonths: ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'], + AM: 'AM', + PM: 'PM', + am: 'am', + pm: 'pm', + formats: { + D: '%m/%d/%y', + F: '%Y-%m-%d', + R: '%H:%M', + T: '%H:%M:%S', + X: '%T', + c: '%a %b %d %X %Y', + r: '%I:%M:%S %p', + v: '%e-%b-%Y', + x: '%D' + } + }, + defaultStrftime = new Strftime(DefaultLocale, 0, false), + isCommonJS = typeof module !== 'undefined', + namespace; + + // CommonJS / Node module + if (isCommonJS) { + namespace = module.exports = adaptedStrftime; + namespace.strftime = deprecatedStrftime; + } + // Browsers and other environments + else { + // Get the global object. Works in ES3, ES5, and ES5 strict mode. + namespace = function () { + return this || (1, eval)('this'); + }(); + namespace.strftime = adaptedStrftime; + } + + // Deprecated API, to be removed in v1.0 + var _require = isCommonJS ? "require('strftime')" : "strftime"; + var _deprecationWarnings = {}; + function deprecationWarning(name, instead) { + if (!_deprecationWarnings[name]) { + if (typeof console !== 'undefined' && typeof console.warn == 'function') { + console.warn("[WARNING] " + name + " is deprecated and will be removed in version 1.0. Instead, use `" + instead + "`."); + } + _deprecationWarnings[name] = true; + } + } + + namespace.strftimeTZ = deprecatedStrftimeTZ; + namespace.strftimeUTC = deprecatedStrftimeUTC; + namespace.localizedStrftime = deprecatedStrftimeLocalized; + + // Adapt the old API while preserving the new API. + function adaptForwards(fn) { + fn.localize = defaultStrftime.localize.bind(defaultStrftime); + fn.timezone = defaultStrftime.timezone.bind(defaultStrftime); + fn.utc = defaultStrftime.utc.bind(defaultStrftime); + } + + adaptForwards(adaptedStrftime); + function adaptedStrftime(fmt, d, locale) { + // d and locale are optional, check if this is (format, locale) + if (d && d.days) { + locale = d; + d = undefined; + } + if (locale) { + deprecationWarning("`" + _require + "(format, [date], [locale])`", "var s = " + _require + ".localize(locale); s(format, [date])"); + } + var strftime = locale ? defaultStrftime.localize(locale) : defaultStrftime; + return strftime(fmt, d); + } + + adaptForwards(deprecatedStrftime); + function deprecatedStrftime(fmt, d, locale) { + if (locale) { + deprecationWarning("`" + _require + ".strftime(format, [date], [locale])`", "var s = " + _require + ".localize(locale); s(format, [date])"); + } else { + deprecationWarning("`" + _require + ".strftime(format, [date])`", _require + "(format, [date])"); + } + var strftime = locale ? defaultStrftime.localize(locale) : defaultStrftime; + return strftime(fmt, d); + } + + function deprecatedStrftimeTZ(fmt, d, locale, timezone) { + // locale is optional, check if this is (format, date, timezone) + if ((typeof locale == 'number' || typeof locale == 'string') && timezone == null) { + timezone = locale; + locale = undefined; + } + + if (locale) { + deprecationWarning("`" + _require + ".strftimeTZ(format, date, locale, tz)`", "var s = " + _require + ".localize(locale).timezone(tz); s(format, [date])` or `var s = " + _require + ".localize(locale); s.timezone(tz)(format, [date])"); + } else { + deprecationWarning("`" + _require + ".strftimeTZ(format, date, tz)`", "var s = " + _require + ".timezone(tz); s(format, [date])` or `" + _require + ".timezone(tz)(format, [date])"); + } + + var strftime = (locale ? defaultStrftime.localize(locale) : defaultStrftime).timezone(timezone); + return strftime(fmt, d); + } + + var utcStrftime = defaultStrftime.utc(); + function deprecatedStrftimeUTC(fmt, d, locale) { + if (locale) { + deprecationWarning("`" + _require + ".strftimeUTC(format, date, locale)`", "var s = " + _require + ".localize(locale).utc(); s(format, [date])"); + } else { + deprecationWarning("`" + _require + ".strftimeUTC(format, [date])`", "var s = " + _require + ".utc(); s(format, [date])"); + } + var strftime = locale ? utcStrftime.localize(locale) : utcStrftime; + return strftime(fmt, d); + } + + function deprecatedStrftimeLocalized(locale) { + deprecationWarning("`" + _require + ".localizedStrftime(locale)`", _require + ".localize(locale)"); + return defaultStrftime.localize(locale); + } + // End of deprecated API + + // Polyfill Date.now for old browsers. + if (typeof Date.now !== 'function') { + Date.now = function () { + return +new Date(); + }; + } + + function Strftime(locale, customTimezoneOffset, useUtcTimezone) { + var _locale = locale || DefaultLocale, + _customTimezoneOffset = customTimezoneOffset || 0, + _useUtcBasedDate = useUtcTimezone || false, + + + // we store unix timestamp value here to not create new Date() each iteration (each millisecond) + // Date.now() is 2 times faster than new Date() + // while millisecond precise is enough here + // this could be very helpful when strftime triggered a lot of times one by one + _cachedDateTimestamp = 0, + _cachedDate; + + function _strftime(format, date) { + var timestamp; + + if (!date) { + var currentTimestamp = Date.now(); + if (currentTimestamp > _cachedDateTimestamp) { + _cachedDateTimestamp = currentTimestamp; + _cachedDate = new Date(_cachedDateTimestamp); + + timestamp = _cachedDateTimestamp; + + if (_useUtcBasedDate) { + // how to avoid duplication of date instantiation for utc here? + // we tied to getTimezoneOffset of the current date + _cachedDate = new Date(_cachedDateTimestamp + getTimestampToUtcOffsetFor(_cachedDate) + _customTimezoneOffset); + } + } else { + timestamp = _cachedDateTimestamp; + } + date = _cachedDate; + } else { + timestamp = date.getTime(); + + if (_useUtcBasedDate) { + date = new Date(date.getTime() + getTimestampToUtcOffsetFor(date) + _customTimezoneOffset); + } + } + + return _processFormat(format, date, _locale, timestamp); + } + + function _processFormat(format, date, locale, timestamp) { + var resultString = '', + padding = null, + isInScope = false, + length = format.length, + extendedTZ = false; + + for (var i = 0; i < length; i++) { + + var currentCharCode = format.charCodeAt(i); + + if (isInScope === true) { + // '-' + if (currentCharCode === 45) { + padding = ''; + continue; + } + // '_' + else if (currentCharCode === 95) { + padding = ' '; + continue; + } + // '0' + else if (currentCharCode === 48) { + padding = '0'; + continue; + } + // ':' + else if (currentCharCode === 58) { + if (extendedTZ) { + if (typeof console !== 'undefined' && typeof console.warn == 'function') { + console.warn("[WARNING] detected use of unsupported %:: or %::: modifiers to strftime"); + } + } + extendedTZ = true; + continue; + } + + switch (currentCharCode) { + + // Examples for new Date(0) in GMT + + // 'Thursday' + // case 'A': + case 65: + resultString += locale.days[date.getDay()]; + break; + + // 'January' + // case 'B': + case 66: + resultString += locale.months[date.getMonth()]; + break; + + // '19' + // case 'C': + case 67: + resultString += padTill2(Math.floor(date.getFullYear() / 100), padding); + break; + + // '01/01/70' + // case 'D': + case 68: + resultString += _processFormat(locale.formats.D, date, locale, timestamp); + break; + + // '1970-01-01' + // case 'F': + case 70: + resultString += _processFormat(locale.formats.F, date, locale, timestamp); + break; + + // '00' + // case 'H': + case 72: + resultString += padTill2(date.getHours(), padding); + break; + + // '12' + // case 'I': + case 73: + resultString += padTill2(hours12(date.getHours()), padding); + break; + + // '000' + // case 'L': + case 76: + resultString += padTill3(Math.floor(timestamp % 1000)); + break; + + // '00' + // case 'M': + case 77: + resultString += padTill2(date.getMinutes(), padding); + break; + + // 'am' + // case 'P': + case 80: + resultString += date.getHours() < 12 ? locale.am : locale.pm; + break; + + // '00:00' + // case 'R': + case 82: + resultString += _processFormat(locale.formats.R, date, locale, timestamp); + break; + + // '00' + // case 'S': + case 83: + resultString += padTill2(date.getSeconds(), padding); + break; + + // '00:00:00' + // case 'T': + case 84: + resultString += _processFormat(locale.formats.T, date, locale, timestamp); + break; + + // '00' + // case 'U': + case 85: + resultString += padTill2(weekNumber(date, 'sunday'), padding); + break; + + // '00' + // case 'W': + case 87: + resultString += padTill2(weekNumber(date, 'monday'), padding); + break; + + // '16:00:00' + // case 'X': + case 88: + resultString += _processFormat(locale.formats.X, date, locale, timestamp); + break; + + // '1970' + // case 'Y': + case 89: + resultString += date.getFullYear(); + break; + + // 'GMT' + // case 'Z': + case 90: + if (_useUtcBasedDate && _customTimezoneOffset === 0) { + resultString += "GMT"; + } else { + // fixme optimize + var tzString = date.toString().match(/\(([\w\s]+)\)/); + resultString += tzString && tzString[1] || ''; + } + break; + + // 'Thu' + // case 'a': + case 97: + resultString += locale.shortDays[date.getDay()]; + break; + + // 'Jan' + // case 'b': + case 98: + resultString += locale.shortMonths[date.getMonth()]; + break; + + // '' + // case 'c': + case 99: + resultString += _processFormat(locale.formats.c, date, locale, timestamp); + break; + + // '01' + // case 'd': + case 100: + resultString += padTill2(date.getDate(), padding); + break; + + // ' 1' + // case 'e': + case 101: + resultString += padTill2(date.getDate(), padding == null ? ' ' : padding); + break; + + // 'Jan' + // case 'h': + case 104: + resultString += locale.shortMonths[date.getMonth()]; + break; + + // '000' + // case 'j': + case 106: + var y = new Date(date.getFullYear(), 0, 1); + var day = Math.ceil((date.getTime() - y.getTime()) / (1000 * 60 * 60 * 24)); + resultString += padTill3(day); + break; + + // ' 0' + // case 'k': + case 107: + resultString += padTill2(date.getHours(), padding == null ? ' ' : padding); + break; + + // '12' + // case 'l': + case 108: + resultString += padTill2(hours12(date.getHours()), padding == null ? ' ' : padding); + break; + + // '01' + // case 'm': + case 109: + resultString += padTill2(date.getMonth() + 1, padding); + break; + + // '\n' + // case 'n': + case 110: + resultString += '\n'; + break; + + // '1st' + // case 'o': + case 111: + resultString += String(date.getDate()) + ordinal(date.getDate()); + break; + + // 'AM' + // case 'p': + case 112: + resultString += date.getHours() < 12 ? locale.AM : locale.PM; + break; + + // '12:00:00 AM' + // case 'r': + case 114: + resultString += _processFormat(locale.formats.r, date, locale, timestamp); + break; + + // '0' + // case 's': + case 115: + resultString += Math.floor(timestamp / 1000); + break; + + // '\t' + // case 't': + case 116: + resultString += '\t'; + break; + + // '4' + // case 'u': + case 117: + var day = date.getDay(); + resultString += day === 0 ? 7 : day; + break; // 1 - 7, Monday is first day of the week + + // ' 1-Jan-1970' + // case 'v': + case 118: + resultString += _processFormat(locale.formats.v, date, locale, timestamp); + break; + + // '4' + // case 'w': + case 119: + resultString += date.getDay(); + break; // 0 - 6, Sunday is first day of the week + + // '12/31/69' + // case 'x': + case 120: + resultString += _processFormat(locale.formats.x, date, locale, timestamp); + break; + + // '70' + // case 'y': + case 121: + resultString += ('' + date.getFullYear()).slice(2); + break; + + // '+0000' + // case 'z': + case 122: + if (_useUtcBasedDate && _customTimezoneOffset === 0) { + resultString += extendedTZ ? "+00:00" : "+0000"; + } else { + var off; + if (_customTimezoneOffset !== 0) { + off = _customTimezoneOffset / (60 * 1000); + } else { + off = -date.getTimezoneOffset(); + } + var sign = off < 0 ? '-' : '+'; + var sep = extendedTZ ? ':' : ''; + var hours = Math.floor(Math.abs(off / 60)); + var mins = Math.abs(off % 60); + resultString += sign + padTill2(hours) + sep + padTill2(mins); + } + break; + + default: + resultString += format[i]; + break; + } + + padding = null; + isInScope = false; + continue; + } + + // '%' + if (currentCharCode === 37) { + isInScope = true; + continue; + } + + resultString += format[i]; + } + + return resultString; + } + + var strftime = _strftime; + + strftime.localize = function (locale) { + return new Strftime(locale || _locale, _customTimezoneOffset, _useUtcBasedDate); + }; + + strftime.timezone = function (timezone) { + var customTimezoneOffset = _customTimezoneOffset; + var useUtcBasedDate = _useUtcBasedDate; + + var timezoneType = typeof timezone === 'undefined' ? 'undefined' : _typeof(timezone); + if (timezoneType === 'number' || timezoneType === 'string') { + useUtcBasedDate = true; + + // ISO 8601 format timezone string, [-+]HHMM + if (timezoneType === 'string') { + var sign = timezone[0] === '-' ? -1 : 1, + hours = parseInt(timezone.slice(1, 3), 10), + minutes = parseInt(timezone.slice(3, 5), 10); + + customTimezoneOffset = sign * (60 * hours + minutes) * 60 * 1000; + // in minutes: 420 + } else if (timezoneType === 'number') { + customTimezoneOffset = timezone * 60 * 1000; + } + } + + return new Strftime(_locale, customTimezoneOffset, useUtcBasedDate); + }; + + strftime.utc = function () { + return new Strftime(_locale, _customTimezoneOffset, true); + }; + + return strftime; + } + + function padTill2(numberToPad, paddingChar) { + if (paddingChar === '' || numberToPad > 9) { + return numberToPad; + } + if (paddingChar == null) { + paddingChar = '0'; + } + return paddingChar + numberToPad; + } + + function padTill3(numberToPad) { + if (numberToPad > 99) { + return numberToPad; + } + if (numberToPad > 9) { + return '0' + numberToPad; + } + return '00' + numberToPad; + } + + function hours12(hour) { + if (hour === 0) { + return 12; + } else if (hour > 12) { + return hour - 12; + } + return hour; + } + + // firstWeekday: 'sunday' or 'monday', default is 'sunday' + // + // Pilfered & ported from Ruby's strftime implementation. + function weekNumber(date, firstWeekday) { + firstWeekday = firstWeekday || 'sunday'; + + // This works by shifting the weekday back by one day if we + // are treating Monday as the first day of the week. + var weekday = date.getDay(); + if (firstWeekday === 'monday') { + if (weekday === 0) // Sunday + weekday = 6;else weekday--; + } + + var firstDayOfYearUtc = Date.UTC(date.getFullYear(), 0, 1), + dateUtc = Date.UTC(date.getFullYear(), date.getMonth(), date.getDate()), + yday = Math.floor((dateUtc - firstDayOfYearUtc) / 86400000), + weekNum = (yday + 7 - weekday) / 7; + + return Math.floor(weekNum); + } + + // Get the ordinal suffix for a number: st, nd, rd, or th + function ordinal(number) { + var i = number % 10; + var ii = number % 100; + + if (ii >= 11 && ii <= 13 || i === 0 || i >= 4) { + return 'th'; + } + switch (i) { + case 1: + return 'st'; + case 2: + return 'nd'; + case 3: + return 'rd'; + } + } + + function getTimestampToUtcOffsetFor(date) { + return (date.getTimezoneOffset() || 0) * 60000; + } +})(); + +},{}],11:[function(require,module,exports){ +'use strict'; + +if (typeof Object.create === 'function') { + // implementation from standard node.js 'util' module + module.exports = function inherits(ctor, superCtor) { + ctor.super_ = superCtor; + ctor.prototype = Object.create(superCtor.prototype, { + constructor: { + value: ctor, + enumerable: false, + writable: true, + configurable: true + } + }); + }; +} else { + // old school shim for old browsers + module.exports = function inherits(ctor, superCtor) { + ctor.super_ = superCtor; + var TempCtor = function TempCtor() {}; + TempCtor.prototype = superCtor.prototype; + ctor.prototype = new TempCtor(); + ctor.prototype.constructor = ctor; + }; +} + +},{}],12:[function(require,module,exports){ +'use strict'; + +var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) { return typeof obj; } : function (obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol ? "symbol" : typeof obj; }; + +module.exports = function isBuffer(arg) { + return arg && (typeof arg === 'undefined' ? 'undefined' : _typeof(arg)) === 'object' && typeof arg.copy === 'function' && typeof arg.fill === 'function' && typeof arg.readUInt8 === 'function'; +}; + +},{}],13:[function(require,module,exports){ +(function (process,global){ +'use strict'; + +var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) { return typeof obj; } : function (obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol ? "symbol" : typeof obj; }; + +// Copyright Joyent, Inc. and other Node contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit +// persons to whom the Software is furnished to do so, subject to the +// following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +// USE OR OTHER DEALINGS IN THE SOFTWARE. + +var formatRegExp = /%[sdj%]/g; +exports.format = function (f) { + if (!isString(f)) { + var objects = []; + for (var i = 0; i < arguments.length; i++) { + objects.push(inspect(arguments[i])); + } + return objects.join(' '); + } + + var i = 1; + var args = arguments; + var len = args.length; + var str = String(f).replace(formatRegExp, function (x) { + if (x === '%%') return '%'; + if (i >= len) return x; + switch (x) { + case '%s': + return String(args[i++]); + case '%d': + return Number(args[i++]); + case '%j': + try { + return JSON.stringify(args[i++]); + } catch (_) { + return '[Circular]'; + } + default: + return x; + } + }); + for (var x = args[i]; i < len; x = args[++i]) { + if (isNull(x) || !isObject(x)) { + str += ' ' + x; + } else { + str += ' ' + inspect(x); + } + } + return str; +}; + +// Mark that a method should not be used. +// Returns a modified function which warns once by default. +// If --no-deprecation is set, then it is a no-op. +exports.deprecate = function (fn, msg) { + // Allow for deprecating things in the process of starting up. + if (isUndefined(global.process)) { + return function () { + return exports.deprecate(fn, msg).apply(this, arguments); + }; + } + + if (process.noDeprecation === true) { + return fn; + } + + var warned = false; + function deprecated() { + if (!warned) { + if (process.throwDeprecation) { + throw new Error(msg); + } else if (process.traceDeprecation) { + console.trace(msg); + } else { + console.error(msg); + } + warned = true; + } + return fn.apply(this, arguments); + } + + return deprecated; +}; + +var debugs = {}; +var debugEnviron; +exports.debuglog = function (set) { + if (isUndefined(debugEnviron)) debugEnviron = process.env.NODE_DEBUG || ''; + set = set.toUpperCase(); + if (!debugs[set]) { + if (new RegExp('\\b' + set + '\\b', 'i').test(debugEnviron)) { + var pid = process.pid; + debugs[set] = function () { + var msg = exports.format.apply(exports, arguments); + console.error('%s %d: %s', set, pid, msg); + }; + } else { + debugs[set] = function () {}; + } + } + return debugs[set]; +}; + +/** + * Echos the value of a value. Trys to print the value out + * in the best way possible given the different types. + * + * @param {Object} obj The object to print out. + * @param {Object} opts Optional options object that alters the output. + */ +/* legacy: obj, showHidden, depth, colors*/ +function inspect(obj, opts) { + // default options + var ctx = { + seen: [], + stylize: stylizeNoColor + }; + // legacy... + if (arguments.length >= 3) ctx.depth = arguments[2]; + if (arguments.length >= 4) ctx.colors = arguments[3]; + if (isBoolean(opts)) { + // legacy... + ctx.showHidden = opts; + } else if (opts) { + // got an "options" object + exports._extend(ctx, opts); + } + // set default options + if (isUndefined(ctx.showHidden)) ctx.showHidden = false; + if (isUndefined(ctx.depth)) ctx.depth = 2; + if (isUndefined(ctx.colors)) ctx.colors = false; + if (isUndefined(ctx.customInspect)) ctx.customInspect = true; + if (ctx.colors) ctx.stylize = stylizeWithColor; + return formatValue(ctx, obj, ctx.depth); +} +exports.inspect = inspect; + +// http://en.wikipedia.org/wiki/ANSI_escape_code#graphics +inspect.colors = { + 'bold': [1, 22], + 'italic': [3, 23], + 'underline': [4, 24], + 'inverse': [7, 27], + 'white': [37, 39], + 'grey': [90, 39], + 'black': [30, 39], + 'blue': [34, 39], + 'cyan': [36, 39], + 'green': [32, 39], + 'magenta': [35, 39], + 'red': [31, 39], + 'yellow': [33, 39] +}; + +// Don't use 'blue' not visible on cmd.exe +inspect.styles = { + 'special': 'cyan', + 'number': 'yellow', + 'boolean': 'yellow', + 'undefined': 'grey', + 'null': 'bold', + 'string': 'green', + 'date': 'magenta', + // "name": intentionally not styling + 'regexp': 'red' +}; + +function stylizeWithColor(str, styleType) { + var style = inspect.styles[styleType]; + + if (style) { + return '\u001b[' + inspect.colors[style][0] + 'm' + str + '\u001b[' + inspect.colors[style][1] + 'm'; + } else { + return str; + } +} + +function stylizeNoColor(str, styleType) { + return str; +} + +function arrayToHash(array) { + var hash = {}; + + array.forEach(function (val, idx) { + hash[val] = true; + }); + + return hash; +} + +function formatValue(ctx, value, recurseTimes) { + // Provide a hook for user-specified inspect functions. + // Check that value is an object with an inspect function on it + if (ctx.customInspect && value && isFunction(value.inspect) && + // Filter out the util module, it's inspect function is special + value.inspect !== exports.inspect && + // Also filter out any prototype objects using the circular check. + !(value.constructor && value.constructor.prototype === value)) { + var ret = value.inspect(recurseTimes, ctx); + if (!isString(ret)) { + ret = formatValue(ctx, ret, recurseTimes); + } + return ret; + } + + // Primitive types cannot have properties + var primitive = formatPrimitive(ctx, value); + if (primitive) { + return primitive; + } + + // Look up the keys of the object. + var keys = Object.keys(value); + var visibleKeys = arrayToHash(keys); + + if (ctx.showHidden) { + keys = Object.getOwnPropertyNames(value); + } + + // IE doesn't make error fields non-enumerable + // http://msdn.microsoft.com/en-us/library/ie/dww52sbt(v=vs.94).aspx + if (isError(value) && (keys.indexOf('message') >= 0 || keys.indexOf('description') >= 0)) { + return formatError(value); + } + + // Some type of object without properties can be shortcutted. + if (keys.length === 0) { + if (isFunction(value)) { + var name = value.name ? ': ' + value.name : ''; + return ctx.stylize('[Function' + name + ']', 'special'); + } + if (isRegExp(value)) { + return ctx.stylize(RegExp.prototype.toString.call(value), 'regexp'); + } + if (isDate(value)) { + return ctx.stylize(Date.prototype.toString.call(value), 'date'); + } + if (isError(value)) { + return formatError(value); + } + } + + var base = '', + array = false, + braces = ['{', '}']; + + // Make Array say that they are Array + if (isArray(value)) { + array = true; + braces = ['[', ']']; + } + + // Make functions say that they are functions + if (isFunction(value)) { + var n = value.name ? ': ' + value.name : ''; + base = ' [Function' + n + ']'; + } + + // Make RegExps say that they are RegExps + if (isRegExp(value)) { + base = ' ' + RegExp.prototype.toString.call(value); + } + + // Make dates with properties first say the date + if (isDate(value)) { + base = ' ' + Date.prototype.toUTCString.call(value); + } + + // Make error with message first say the error + if (isError(value)) { + base = ' ' + formatError(value); + } + + if (keys.length === 0 && (!array || value.length == 0)) { + return braces[0] + base + braces[1]; + } + + if (recurseTimes < 0) { + if (isRegExp(value)) { + return ctx.stylize(RegExp.prototype.toString.call(value), 'regexp'); + } else { + return ctx.stylize('[Object]', 'special'); + } + } + + ctx.seen.push(value); + + var output; + if (array) { + output = formatArray(ctx, value, recurseTimes, visibleKeys, keys); + } else { + output = keys.map(function (key) { + return formatProperty(ctx, value, recurseTimes, visibleKeys, key, array); + }); + } + + ctx.seen.pop(); + + return reduceToSingleString(output, base, braces); +} + +function formatPrimitive(ctx, value) { + if (isUndefined(value)) return ctx.stylize('undefined', 'undefined'); + if (isString(value)) { + var simple = '\'' + JSON.stringify(value).replace(/^"|"$/g, '').replace(/'/g, "\\'").replace(/\\"/g, '"') + '\''; + return ctx.stylize(simple, 'string'); + } + if (isNumber(value)) return ctx.stylize('' + value, 'number'); + if (isBoolean(value)) return ctx.stylize('' + value, 'boolean'); + // For some reason typeof null is "object", so special case here. + if (isNull(value)) return ctx.stylize('null', 'null'); +} + +function formatError(value) { + return '[' + Error.prototype.toString.call(value) + ']'; +} + +function formatArray(ctx, value, recurseTimes, visibleKeys, keys) { + var output = []; + for (var i = 0, l = value.length; i < l; ++i) { + if (hasOwnProperty(value, String(i))) { + output.push(formatProperty(ctx, value, recurseTimes, visibleKeys, String(i), true)); + } else { + output.push(''); + } + } + keys.forEach(function (key) { + if (!key.match(/^\d+$/)) { + output.push(formatProperty(ctx, value, recurseTimes, visibleKeys, key, true)); + } + }); + return output; +} + +function formatProperty(ctx, value, recurseTimes, visibleKeys, key, array) { + var name, str, desc; + desc = Object.getOwnPropertyDescriptor(value, key) || { value: value[key] }; + if (desc.get) { + if (desc.set) { + str = ctx.stylize('[Getter/Setter]', 'special'); + } else { + str = ctx.stylize('[Getter]', 'special'); + } + } else { + if (desc.set) { + str = ctx.stylize('[Setter]', 'special'); + } + } + if (!hasOwnProperty(visibleKeys, key)) { + name = '[' + key + ']'; + } + if (!str) { + if (ctx.seen.indexOf(desc.value) < 0) { + if (isNull(recurseTimes)) { + str = formatValue(ctx, desc.value, null); + } else { + str = formatValue(ctx, desc.value, recurseTimes - 1); + } + if (str.indexOf('\n') > -1) { + if (array) { + str = str.split('\n').map(function (line) { + return ' ' + line; + }).join('\n').substr(2); + } else { + str = '\n' + str.split('\n').map(function (line) { + return ' ' + line; + }).join('\n'); + } + } + } else { + str = ctx.stylize('[Circular]', 'special'); + } + } + if (isUndefined(name)) { + if (array && key.match(/^\d+$/)) { + return str; + } + name = JSON.stringify('' + key); + if (name.match(/^"([a-zA-Z_][a-zA-Z_0-9]*)"$/)) { + name = name.substr(1, name.length - 2); + name = ctx.stylize(name, 'name'); + } else { + name = name.replace(/'/g, "\\'").replace(/\\"/g, '"').replace(/(^"|"$)/g, "'"); + name = ctx.stylize(name, 'string'); + } + } + + return name + ': ' + str; +} + +function reduceToSingleString(output, base, braces) { + var numLinesEst = 0; + var length = output.reduce(function (prev, cur) { + numLinesEst++; + if (cur.indexOf('\n') >= 0) numLinesEst++; + return prev + cur.replace(/\u001b\[\d\d?m/g, '').length + 1; + }, 0); + + if (length > 60) { + return braces[0] + (base === '' ? '' : base + '\n ') + ' ' + output.join(',\n ') + ' ' + braces[1]; + } + + return braces[0] + base + ' ' + output.join(', ') + ' ' + braces[1]; +} + +// NOTE: These type checking functions intentionally don't use `instanceof` +// because it is fragile and can be easily faked with `Object.create()`. +function isArray(ar) { + return Array.isArray(ar); +} +exports.isArray = isArray; + +function isBoolean(arg) { + return typeof arg === 'boolean'; +} +exports.isBoolean = isBoolean; + +function isNull(arg) { + return arg === null; +} +exports.isNull = isNull; + +function isNullOrUndefined(arg) { + return arg == null; +} +exports.isNullOrUndefined = isNullOrUndefined; + +function isNumber(arg) { + return typeof arg === 'number'; +} +exports.isNumber = isNumber; + +function isString(arg) { + return typeof arg === 'string'; +} +exports.isString = isString; + +function isSymbol(arg) { + return (typeof arg === 'undefined' ? 'undefined' : _typeof(arg)) === 'symbol'; +} +exports.isSymbol = isSymbol; + +function isUndefined(arg) { + return arg === void 0; +} +exports.isUndefined = isUndefined; + +function isRegExp(re) { + return isObject(re) && objectToString(re) === '[object RegExp]'; +} +exports.isRegExp = isRegExp; + +function isObject(arg) { + return (typeof arg === 'undefined' ? 'undefined' : _typeof(arg)) === 'object' && arg !== null; +} +exports.isObject = isObject; + +function isDate(d) { + return isObject(d) && objectToString(d) === '[object Date]'; +} +exports.isDate = isDate; + +function isError(e) { + return isObject(e) && (objectToString(e) === '[object Error]' || e instanceof Error); +} +exports.isError = isError; + +function isFunction(arg) { + return typeof arg === 'function'; +} +exports.isFunction = isFunction; + +function isPrimitive(arg) { + return arg === null || typeof arg === 'boolean' || typeof arg === 'number' || typeof arg === 'string' || (typeof arg === 'undefined' ? 'undefined' : _typeof(arg)) === 'symbol' || // ES6 symbol + typeof arg === 'undefined'; +} +exports.isPrimitive = isPrimitive; + +exports.isBuffer = require('./support/isBuffer'); + +function objectToString(o) { + return Object.prototype.toString.call(o); +} + +function pad(n) { + return n < 10 ? '0' + n.toString(10) : n.toString(10); +} + +var months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']; + +// 26 Feb 16:19:34 +function timestamp() { + var d = new Date(); + var time = [pad(d.getHours()), pad(d.getMinutes()), pad(d.getSeconds())].join(':'); + return [d.getDate(), months[d.getMonth()], time].join(' '); +} + +// log is just a thin wrapper to console.log that prepends a timestamp +exports.log = function () { + console.log('%s - %s', timestamp(), exports.format.apply(exports, arguments)); +}; + +/** + * Inherit the prototype methods from one constructor into another. + * + * The Function.prototype.inherits from lang.js rewritten as a standalone + * function (not on Function.prototype). NOTE: If this file is to be loaded + * during bootstrapping this function needs to be rewritten using some native + * functions as prototype setup using normal JavaScript does not work as + * expected during bootstrapping (see mirror.js in r114903). + * + * @param {function} ctor Constructor function which needs to inherit the + * prototype. + * @param {function} superCtor Constructor function to inherit prototype from. + */ +exports.inherits = require('inherits'); + +exports._extend = function (origin, add) { + // Don't do anything if add isn't an object + if (!add || !isObject(add)) return origin; + + var keys = Object.keys(add); + var i = keys.length; + while (i--) { + origin[keys[i]] = add[keys[i]]; + } + return origin; +}; + +function hasOwnProperty(obj, prop) { + return Object.prototype.hasOwnProperty.call(obj, prop); +} + +}).call(this,require('_process'),typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {}) +},{"./support/isBuffer":12,"_process":9,"inherits":11}],14:[function(require,module,exports){ +"use strict"; + +var util = require('util'); + +function TokenizationError(message, input, line) { + Error.captureStackTrace(this, this.constructor); + this.name = this.constructor.name; + + this.message = message || ""; + this.input = input; + this.line = line; +} +util.inherits(TokenizationError, Error); + +function ParseError(message, input, line) { + Error.captureStackTrace(this, this.constructor); + this.name = this.constructor.name; + + this.message = message || ""; + this.input = input; + this.line = line; +} +util.inherits(ParseError, Error); + +module.exports = { + TokenizationError: TokenizationError, ParseError: ParseError +}; + +},{"util":13}],15:[function(require,module,exports){ +'use strict'; + +var syntax = require('./syntax.js'); +var Exp = require('./expression.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)) { + var a = lexical.parseLiteral(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 +}; + +},{"./expression.js":15,"./lexical.js":17,"./syntax.js":21}],16:[function(require,module,exports){ +'use strict'; + +var lexical = require('./lexical.js'); +var Exp = require('./expression.js'); + +var valueRE = new RegExp('' + lexical.value.source, 'g'); + +module.exports = function () { + var filters = {}; + + var _filterInstance = { + render: function render(output, scope) { + var args = this.args.map(function (arg) { + return Exp.evalValue(arg, scope); + }); + args.unshift(output); + return this.filter.apply(null, args); + }, + parse: function parse(str) { + var match = lexical.filterLine.exec(str); + if (!match) throw new Error('illegal filter: ' + str); + + var name = match[1], + argList = match[2] || '', + filter = filters[name]; + if (typeof filter !== 'function') { + return { + name: name, + error: new Error('undefined filter: ' + name) + }; + } + + var args = []; + while (match = valueRE.exec(argList.trim())) { + args.push(match[0]); + } + + this.name = name; + this.filter = filter; + this.args = args; + + return this; + } + }; + + function construct(str) { + var instance = Object.create(_filterInstance); + return instance.parse(str); + } + + function register(name, filter) { + filters[name] = filter; + } + + function clear() { + filters = {}; + } + + return { + construct: construct, register: register, clear: clear + }; +}; + +},{"./expression.js":15,"./lexical.js":17}],17:[function(require,module,exports){ +'use strict'; + +// quote related +var singleQuoted = /'[^']*'/; +var doubleQuoted = /"[^"]*"/; +var quoteBalanced = new RegExp('(?:' + singleQuoted.source + '|' + doubleQuoted.source + '|[^\'"])*'); + +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 + ')'); +var literal = new RegExp('(?:' + quoted.source + '|' + bool.source + '|' + number.source + ')'); +var variable = new RegExp(identifier.source + '(?:\\.' + identifier.source + '|' + subscript.source + ')*'); + +// range related +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 + ')'); + +// hash related +var hash = new RegExp('(?:' + identifier.source + ')\\s*:\\s*(?:' + value.source + ')'); +var hashCapture = new RegExp('(' + identifier.source + ')\\s*:\\s*(' + value.source + ')', 'g'); + +var tagLine = new RegExp('^\\s*(' + identifier.source + ')\\s*(.*)\\s*$'); +var literalLine = new RegExp('^' + literal.source + '$', 'i'); +var variableLine = new RegExp('^' + variable.source + '$'); +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 + '$'); + +// filter related +var valueList = new RegExp(value.source + '(\\s*,\\s*' + value.source + ')*'); +var filter = new RegExp(identifier.source + '(?:\\s*:\\s*' + valueList.source + ')?', 'g'); +var filterCapture = new RegExp('(' + identifier.source + ')(?:\\s*:\\s*(' + valueList.source + '))?'); +var filterLine = new RegExp('^' + filterCapture.source + '$'); + +var operators = [/\s+or\s+/, /\s+and\s+/, /==|!=|<=|>=|<|>|\s+contains\s+/]; + +function isLiteral(str) { + return literalLine.test(str); +} + +function isRange(str) { + return rangeLine.test(str); +} + +function isVariable(str) { + return variableLine.test(str); +} + +function parseLiteral(str) { + var res; + if (res = str.match(numberLine)) { + return Number(str); + } + if (res = str.match(boolLine)) { + return str.toLowerCase() === 'true'; + } + if (res = str.match(quotedLine)) { + return str.slice(1, -1); + } +} + +module.exports = { + quoted: quoted, number: number, bool: bool, literal: literal, filter: filter, + 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 +}; + +},{}],18:[function(require,module,exports){ +'use strict'; + +var lexical = require('./lexical.js'); +var ParseError = require('./error.js').ParseError; + +module.exports = function (Tag, Filter) { + + var stream = { + init: function init(tokens) { + this.tokens = tokens; + this.handlers = {}; + return this; + }, + on: function on(name, cb) { + this.handlers[name] = cb; + return this; + }, + trigger: function trigger(event, arg) { + var h = this.handlers[event]; + if (typeof h === 'function') { + h(arg); + return true; + } + }, + start: function start() { + this.trigger('start'); + while (!this.stopRequested && (token = this.tokens.shift())) { + if (this.trigger('token', token)) continue; + if (token.type == 'tag' && this.trigger('tag:' + token.name, token)) { + continue; + } + var template = parseToken(token, this.tokens); + this.trigger('template', template); + } + if (!this.stopRequested) this.trigger('end'); + return this; + }, + stop: function stop() { + this.stopRequested = true; + return this; + } + }; + + function parse(tokens) { + var token, + templates = []; + while (token = tokens.shift()) { + templates.push(parseToken(token, tokens)); + } + return templates; + } + + function parseToken(token, tokens) { + try { + switch (token.type) { + case 'tag': + return parseTag(token, tokens); + case 'output': + return parseOutput(token.value); + case 'html': + return token; + } + } catch (e) { + throw new ParseError(e.message, token.input, token.line); + } + } + + function parseTag(token, tokens) { + if (token.name === 'continue' || token.name === 'break') return token; + return Tag.construct(token, tokens); + } + + function parseOutput(str) { + var match = lexical.value.exec(str); + if (!match) throw new Error('illegal output string: ' + str); + + var initial = match[0]; + str = str.substr(match.index + match[0].length); + + var filters = []; + while (match = lexical.filter.exec(str)) { + filters.push([match[0].trim()]); + } + + return { + type: 'output', + initial: initial, + filters: filters.map(function (str) { + return Filter.construct(str); + }) + }; + } + + function parseStream(tokens) { + var s = Object.create(stream); + return s.init(tokens); + } + + return { + parse: parse, parseTag: parseTag, parseStream: parseStream, parseOutput: parseOutput + }; +}; + +},{"./error.js":14,"./lexical.js":17}],19:[function(require,module,exports){ +'use strict'; + +var error = require('./error.js'); +var Exp = require('./expression.js'); +var assert = require('assert'); +var Promise = require('any-promise'); + +var render = { + + renderTemplates: function renderTemplates(templates, scope, opts) { + var _this = this; + + assert(scope, 'unable to evalTemplates: scope undefined'); + opts = opts || {}; + opts.strict_filters = opts.strict_filters || false; + + var html = ''; + + // This executes an array of promises sequentially for every template in the templates array - http://webcache.googleusercontent.com/search?q=cache:rNbMUn9TPtkJ:joost.vunderink.net/blog/2014/12/15/processing-an-array-of-promises-sequentially-in-node-js/+&cd=5&hl=en&ct=clnk&gl=us + // 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) { + if (scope.safeGet('forloop.skip')) { + return Promise.resolve(''); + } + if (scope.safeGet('forloop.stop')) { + throw new Error('forloop.stop'); // this will stop/break the sequential promise chain and go to the catch + } + + var promiseLink = Promise.resolve(''); + switch (template.type) { + case 'tag': + // Add Promises to the chain + promiseLink = _this.renderTag(template, scope, _this.register).then(function (partial) { + if (partial === undefined) { + return true; // basically a noop (do nothing) + } + return html += partial; + }); + break; + case 'html': + promiseLink = Promise.resolve(template.value).then(function (partial) { + return html += partial; + }); + break; + case 'output': + var val = _this.evalOutput(template, scope, opts); + promiseLink = Promise.resolve(val === undefined ? '' : stringify(val)).then(function (partial) { + return html += partial; + }); + break; + } + + return promiseLink; + }).catch(function (error) { + if (error.message === 'forloop.skip') { + // the error is a controlled, purposeful stop. so just return the html that we have up to this point + return html; + } else { + // rethrow actual error + throw error; + } + }); + }, Promise.resolve('')); // start the reduce chain with a resolved Promise. After first run, the "promise" argument + // in our reduce callback will be the returned promise from our "then" above. In this + // case, that's the promise returned from this.renderTag or a resolved promise with raw html. + + return lastPromise.then(function (renderedHtml) { + return renderedHtml; + }).catch(function (error) { + throw error; + }); + }, + + renderTag: function renderTag(template, scope, register) { + if (template.name === 'continue') { + scope.set('forloop.skip', true); + return Promise.resolve(''); + } + if (template.name === 'break') { + scope.set('forloop.stop', true); + scope.set('forloop.skip', true); + return Promise.reject(new Error('forloop.stop')); // this will stop the sequential promise chain + } + return template.render(scope, register); + }, + + evalOutput: function evalOutput(template, scope, opts) { + assert(scope, 'unable to evalOutput: scope undefined'); + var val = Exp.evalExp(template.initial, scope); + template.filters.some(function (filter) { + if (filter.error) { + if (opts.strict_filters) { + throw filter.error; + } else { + // render as null + val = ''; + return true; + } + } + val = filter.render(val, scope); + }); + return val; + }, + + resetRegisters: function resetRegisters() { + return this.register = {}; + } +}; + +function factory() { + var instance = Object.create(render); + instance.register = {}; + return instance; +} + +function stringify(val) { + if (typeof val === 'string') return val; + return JSON.stringify(val); +} + +module.exports = factory; + +},{"./error.js":14,"./expression.js":15,"any-promise":3,"assert":6}],20:[function(require,module,exports){ +'use strict'; + +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(); + } +}; + +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]; +} + +exports.factory = function (_ctx, opts) { + opts = opts || {}; + opts.strict = opts.strict || false; + + var scope = Object.create(Scope); + scope.opts = opts; + scope.scopes = [_ctx || {}]; + return scope; +}; + +},{"./lexical.js":17}],21:[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; + +},{}],22:[function(require,module,exports){ +'use strict'; + +var lexical = require('./lexical.js'); +var Promise = require('any-promise'); +var Exp = require('./expression.js'); +var TokenizationError = require('./error.js').TokenizationError; + +function hash(markup, scope) { + var obj = {}; + 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; +} + +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 + }; +}; + +},{"./error.js":14,"./expression.js":15,"./lexical.js":17,"any-promise":3}],23:[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":14,"./lexical.js":17}],24:[function(require,module,exports){ +'use strict'; + +var Liquid = require('..'); +var Promise = require('any-promise'); +var lexical = Liquid.lexical; +var re = new RegExp('(' + lexical.identifier.source + ')\\s*=(.*)'); + +module.exports = function (liquid) { + + liquid.registerTag('assign', { + parse: function parse(token) { + var match = token.args.match(re); + if (!match) throw new Error('illegal token ' + token.raw); + this.key = match[1]; + this.value = match[2]; + }, + render: function render(scope, hash) { + scope.set(this.key, liquid.evalOutput(this.value, scope)); + return Promise.resolve(''); + } + }); +}; + +},{"..":2,"any-promise":3}],25:[function(require,module,exports){ +'use strict'; + +var Liquid = require('..'); +var lexical = Liquid.lexical; +var re = new RegExp('(' + lexical.identifier.source + ')'); + +module.exports = function (liquid) { + + liquid.registerTag('capture', { + parse: function parse(tagToken, remainTokens) { + var _this = this; + + var match = tagToken.args.match(re); + if (!match) throw new Error(tagToken.args + ' not valid identifier'); + + this.variable = match[1]; + this.templates = []; + + var stream = liquid.parser.parseStream(remainTokens); + stream.on('tag:endcapture', function (token) { + return stream.stop(); + }).on('template', function (tpl) { + return _this.templates.push(tpl); + }).on('end', function (x) { + throw new Error('tag ' + tagToken.raw + ' not closed'); + }); + stream.start(); + }, + render: function render(scope, hash) { + var _this2 = this; + + return liquid.renderer.renderTemplates(this.templates, scope).then(function (html) { + scope.set(_this2.variable, html); + }); + } + }); +}; + +},{"..":2}],26:[function(require,module,exports){ +'use strict'; + +var Liquid = require('..'); +var lexical = Liquid.lexical; + +module.exports = function (liquid) { + liquid.registerTag('case', { + + parse: function parse(tagToken, remainTokens) { + var _this = this; + + this.cond = tagToken.args; + this.cases = []; + this.elseTemplates = []; + + var p = [], + stream = liquid.parser.parseStream(remainTokens).on('tag:when', function (token) { + if (!_this.cases[token.args]) { + _this.cases.push({ + val: token.args, + templates: p = [] + }); + } + }).on('tag:else', function (token) { + return p = _this.elseTemplates; + }).on('tag:endcase', function (token) { + return stream.stop(); + }).on('template', function (tpl) { + return p.push(tpl); + }).on('end', function (x) { + throw new Error('tag ' + tagToken.raw + ' not closed'); + }); + + stream.start(); + }, + + render: function render(scope, hash) { + for (var i = 0; i < this.cases.length; i++) { + var branch = this.cases[i]; + var val = Liquid.evalExp(branch.val, scope); + var cond = Liquid.evalExp(this.cond, scope); + if (val === cond) { + return liquid.renderer.renderTemplates(branch.templates, scope); + } + } + return liquid.renderer.renderTemplates(this.elseTemplates, scope); + } + + }); +}; + +},{"..":2}],27:[function(require,module,exports){ +'use strict'; + +var Liquid = require('..'); +var lexical = Liquid.lexical; +var re = new RegExp('(' + lexical.identifier.source + ')'); + +module.exports = function (liquid) { + + liquid.registerTag('comment', { + parse: function parse(tagToken, remainTokens) { + var stream = liquid.parser.parseStream(remainTokens); + stream.on('token', function (token) { + if (token.name === 'endcomment') stream.stop(); + }).on('end', function (x) { + throw new Error('tag ' + tagToken.raw + ' not closed'); + }); + stream.start(); + } + }); +}; + +},{"..":2}],28:[function(require,module,exports){ +'use strict'; + +var Liquid = require('..'); +var Promise = require('any-promise'); +var lexical = Liquid.lexical; +var groupRE = new RegExp('^(?:(' + lexical.value.source + ')\\s*:\\s*)?(.*)$'); +var candidatesRE = new RegExp(lexical.value.source, 'g'); + +module.exports = function (liquid) { + liquid.registerTag('cycle', { + + parse: function parse(tagToken, remainTokens) { + var match = groupRE.exec(tagToken.args); + if (!match) throw new Error('illegal tag: ' + tagToken.raw); + + this.group = match[1] || ''; + var candidates = match[2]; + + this.candidates = []; + + while (match = candidatesRE.exec(candidates)) { + this.candidates.push(match[0]); + } + + if (!this.candidates.length) { + throw new Error('empty candidates: ' + tagToken.raw); + } + }, + + render: function render(scope, hash, register) { + var fingerprint = Liquid.evalValue(this.group, scope) + ':' + this.candidates.join(','); + var idx = register[fingerprint]; + + if (idx === undefined) { + idx = register[fingerprint] = 0; + } + + var candidate = this.candidates[idx]; + idx = (idx + 1) % this.candidates.length; + register[fingerprint] = idx; + + return Promise.resolve(Liquid.evalValue(candidate, scope)); + } + }); +}; + +},{"..":2,"any-promise":3}],29:[function(require,module,exports){ +'use strict'; + +var Liquid = require('..'); +var lexical = Liquid.lexical; +var error = Liquid.error; + +module.exports = function (liquid) { + + liquid.registerTag('decrement', { + parse: function parse(token) { + var match = token.args.match(lexical.identifier); + if (!match) throw new Error('illegal identifier ' + token.args); + this.variable = match[0]; + }, + render: function render(scope, hash) { + var v = scope.get(this.variable); + if (typeof v !== 'number') v = 0; + scope.set(this.variable, v - 1); + } + }); +}; + +},{"..":2}],30:[function(require,module,exports){ +'use strict'; + +var Liquid = require('..'); +var Promise = require('any-promise'); +var lexical = Liquid.lexical; +var re = new RegExp('^(' + lexical.identifier.source + ')\\s+in\\s+' + ('(' + lexical.value.source + ')') + ('(?:\\s+' + lexical.hash.source + ')*') + '(?:\\s+(reversed))?$'); + +module.exports = function (liquid) { + liquid.registerTag('for', { + + parse: function parse(tagToken, remainTokens) { + var _this = this; + + var match = re.exec(tagToken.args); + if (!match) throw new Error('illegal tag: ' + tagToken.raw); + this.variable = match[1]; + this.collection = match[2]; + this.reversed = !!match[3]; + + this.templates = []; + this.elseTemplates = []; + + var p, + stream = liquid.parser.parseStream(remainTokens).on('start', function (x) { + return p = _this.templates; + }).on('tag:else', function (token) { + return p = _this.elseTemplates; + }).on('tag:endfor', function (token) { + return stream.stop(); + }).on('template', function (tpl) { + return p.push(tpl); + }).on('end', function (x) { + throw new Error('tag ' + tagToken.raw + ' not closed'); + }); + + stream.start(); + }, + + render: function render(scope, hash) { + var _this2 = this; + + var collection = Liquid.evalExp(this.collection, scope); + if (Liquid.isFalsy(collection)) { + return liquid.renderer.renderTemplates(this.elseTemplates, scope); + } + + var html = ''; + var length = collection.length; + var offset = hash.offset || 0; + var limit = hash.limit === undefined ? collection.length : hash.limit; + + collection = collection.slice(offset, offset + limit); + if (this.reversed) collection.reverse(); + + // for needs to execute the promises sequentially, not just resolve them sequentially, due to break and continue. + // We can't just loop through executing everything then resolve them all sequentially like we do for render.renderTemplates + // First, we build the array of parameters we are going to use for each call to renderTemplates + var contexts = []; + collection.some(function (item, i) { + var ctx = {}; + ctx[_this2.variable] = item; + ctx.forloop = { + first: i === 0, + index: i + 1, + index0: i, + last: i === length - 1, + length: length, + rindex: length - i, + rindex0: length - i - 1, + stop: false, + skip: false + }; + // We are just putting together an array of the arguments we will be passing to our sequential promises + contexts.push(ctx); + }); + + // This is some pretty tricksy javascript, at least to me. + // This executes an array of promises sequentially for every argument in the contexts array - http://webcache.googleusercontent.com/search?q=cache:rNbMUn9TPtkJ:joost.vunderink.net/blog/2014/12/15/processing-an-array-of-promises-sequentially-in-node-js/+&cd=5&hl=en&ct=clnk&gl=us + // It's fundamentally equivalent to the following... + // emptyPromise.then(renderTemplates(args0).then(renderTemplates(args1).then(renderTemplates(args2)... + var lastPromise = contexts.reduce(function (promise, context) { + return promise.then(function (partial) { + if (scope.get('forloop.stop')) { + throw new Error('forloop.stop'); // this will stop the sequential promise chain + } + + return html += partial; + }).then(function (partial) { + // todo: Make sure our scope management is sound here. Create some tests that revolve around loops + // with sections that take differing amounts of time to complete. Make sure the order is maintained + // and scope doesn't bleed over into other renderTemplate calls. + scope.push(context); + return liquid.renderer.renderTemplates(_this2.templates, scope); + }).then(function (partial) { + scope.pop(context); + return partial; + }); + }, Promise.resolve('')); // start the reduce chain with a resolved Promise. After first run, the "promise" argument + // in our reduce callback will be the returned promise from our "then" above. In this + // case, the promise returned from liquid.renderer.renderTemplates. + + return lastPromise.then(function (partial) { + return html += partial; + }).catch(function (error) { + if (error.message === 'forloop.stop') { + // the error is a controlled, purposeful stop. so just return the html that we have up to this point + return html; + } else { + // rethrow actual error + throw error; + } + }); + } + }); +}; + +},{"..":2,"any-promise":3}],31:[function(require,module,exports){ +'use strict'; + +var Liquid = require('..'); +var lexical = Liquid.lexical; + +module.exports = function (liquid) { + liquid.registerTag('if', { + + parse: function parse(tagToken, remainTokens) { + var _this = this; + + this.branches = []; + this.elseTemplates = []; + + var p, + stream = liquid.parser.parseStream(remainTokens).on('start', function (x) { + return _this.branches.push({ + cond: tagToken.args, + templates: p = [] + }); + }).on('tag:elsif', function (token) { + if (!_this.branches[token.args]) { + _this.branches.push({ + cond: token.args, + templates: p = [] + }); + } + }).on('tag:else', function (token) { + return p = _this.elseTemplates; + }).on('tag:endif', function (token) { + return stream.stop(); + }).on('template', function (tpl) { + return p.push(tpl); + }).on('end', function (x) { + throw new Error('tag ' + tagToken.raw + ' not closed'); + }); + + stream.start(); + }, + + render: function render(scope, hash) { + for (var i = 0; i < this.branches.length; i++) { + var branch = this.branches[i]; + var cond = Liquid.evalExp(branch.cond, scope); + if (Liquid.isTruthy(cond)) { + return liquid.renderer.renderTemplates(branch.templates, scope); + } + } + return liquid.renderer.renderTemplates(this.elseTemplates, scope); + } + + }); +}; + +},{"..":2}],32:[function(require,module,exports){ +'use strict'; + +var Liquid = require('..'); +var Promise = require('any-promise'); +var lexical = Liquid.lexical; +var withRE = new RegExp('with\\s+(' + lexical.value.source + ')'); + +module.exports = function (liquid) { + + liquid.registerTag('include', { + parse: function parse(token) { + var match = lexical.value.exec(token.args); + if (!match) throw new Error('illegal token ' + token.raw); + this.value = match[0]; + + match = withRE.exec(token.args); + if (match) { + this.with = match[1]; + } + }, + render: function render(scope, hash) { + var filepath = Liquid.evalValue(this.value, scope); + if (this.with) { + hash[filepath] = Liquid.evalValue(this.with, scope); + } + return liquid.handleCache(filepath).then(function (templates) { + scope.push(hash); + return liquid.renderer.renderTemplates(templates, scope); + }).then(function (html) { + scope.pop(); + return html; + }); + } + }); +}; + +},{"..":2,"any-promise":3}],33:[function(require,module,exports){ +'use strict'; + +var Liquid = require('..'); +var lexical = Liquid.lexical; + +module.exports = function (liquid) { + + liquid.registerTag('increment', { + parse: function parse(token) { + var match = token.args.match(lexical.identifier); + if (!match) throw new Error('illegal identifier ' + token.args); + this.variable = match[0]; + }, + render: function render(scope, hash) { + var v = scope.get(this.variable); + if (typeof v !== 'number') v = 0; + scope.set(this.variable, v + 1); + } + }); +}; + +},{"..":2}],34:[function(require,module,exports){ +"use strict"; + +module.exports = function (engine) { + require("./assign.js")(engine); + require("./capture.js")(engine); + require("./case.js")(engine); + require("./comment.js")(engine); + require("./cycle.js")(engine); + require("./decrement.js")(engine); + require("./for.js")(engine); + require("./if.js")(engine); + require("./include.js")(engine); + require("./increment.js")(engine); + require("./layout.js")(engine); + require("./raw.js")(engine); + require("./tablerow.js")(engine); + require("./unless.js")(engine); +}; + +},{"./assign.js":24,"./capture.js":25,"./case.js":26,"./comment.js":27,"./cycle.js":28,"./decrement.js":29,"./for.js":30,"./if.js":31,"./include.js":32,"./increment.js":33,"./layout.js":35,"./raw.js":36,"./tablerow.js":37,"./unless.js":38}],35:[function(require,module,exports){ +'use strict'; + +var Liquid = require('..'); +var Promise = require('any-promise'); +var lexical = Liquid.lexical; +var withRE = new RegExp('with\\s+(' + lexical.value.source + ')'); + +module.exports = function (liquid) { + + liquid.registerTag('layout', { + parse: function parse(token, remainTokens) { + var match = lexical.value.exec(token.args); + if (!match) throw new Error('illegal token ' + token.raw); + + this.layout = match[0]; + this.tpls = liquid.parser.parse(remainTokens); + }, + render: function render(scope, hash) { + var layout = Liquid.evalValue(this.layout, scope); + + var html = ''; + scope.push({}); + // not sure if this first one is needed, since the results are ignored + return liquid.renderer.renderTemplates(this.tpls, scope).then(function (partial) { + html += partial; + return liquid.handleCache(layout); + }).then(function (templates) { + return liquid.renderer.renderTemplates(templates, scope); + }).then(function (partial) { + scope.pop(); + return partial; + }).catch(function (e) { + e.file = layout; + throw e; + }); + + // var tpl = liquid.handleCache(layout); + // + // scope.push({}); + // liquid.renderer.renderTemplates(this.tpls, scope); // what's the point of this line? + // var html = liquid.renderer.renderTemplates(tpl, scope); + // scope.pop(); + // return html; + } + }); + + liquid.registerTag('block', { + parse: function parse(token, remainTokens) { + var _this = this; + + var match = /\w+/.exec(token.args); + this.block = match ? match[0] : ''; + + this.tpls = []; + var p, + stream = liquid.parser.parseStream(remainTokens).on('tag:endblock', function (token) { + return stream.stop(); + }).on('template', function (tpl) { + return _this.tpls.push(tpl); + }).on('end', function (x) { + throw new Error('tag ' + token.raw + ' not closed'); + }); + stream.start(); + }, + render: function render(scope, hash) { + var _this2 = this; + + var html = scope.get('_liquid.blocks.' + this.block); + var promise = Promise.resolve(''); + if (html === undefined) { + promise = liquid.renderer.renderTemplates(this.tpls, scope).then(function (partial) { + scope.set('_liquid.blocks.' + _this2.block, partial); + return partial; + }); + } else { + scope.set('_liquid.blocks.' + this.block, html); + promise = Promise.resolve(html); + } + return promise; + + // if(html === undefined){ + // html = liquid.renderer.renderTemplates(this.tpls, scope); + // } + // scope.set(`_liquid.blocks.${this.block}`, html); + // return html; + } + }); +}; + +},{"..":2,"any-promise":3}],36:[function(require,module,exports){ +'use strict'; + +var Liquid = require('..'); +var Promise = require('any-promise'); +var lexical = Liquid.lexical; +var re = new RegExp('(' + lexical.identifier.source + ')'); + +module.exports = function (liquid) { + + liquid.registerTag('raw', { + parse: function parse(tagToken, remainTokens) { + var _this = this; + + this.tokens = []; + + var stream = liquid.parser.parseStream(remainTokens); + stream.on('token', function (token) { + if (token.name === 'endraw') stream.stop();else _this.tokens.push(token); + }).on('end', function (x) { + throw new Error('tag ' + tagToken.raw + ' not closed'); + }); + stream.start(); + }, + render: function render(scope, hash) { + var tokens = this.tokens.map(function (token) { + return token.raw; + }).join(''); + return Promise.resolve(tokens); + } + }); +}; + +},{"..":2,"any-promise":3}],37:[function(require,module,exports){ +'use strict'; + +var Liquid = require('..'); +var Promise = require('any-promise'); +var lexical = Liquid.lexical; +var re = new RegExp('^(' + lexical.identifier.source + ')\\s+in\\s+' + ('(' + lexical.value.source + ')') + ('(?:\\s+' + lexical.hash.source + ')*$')); + +module.exports = function (liquid) { + liquid.registerTag('tablerow', { + + parse: function parse(tagToken, remainTokens) { + var _this = this; + + var match = re.exec(tagToken.args); + if (!match) throw new Error('illegal tag: ' + tagToken.raw); + this.variable = match[1]; + this.collection = match[2]; + + this.templates = []; + + var p, + stream = liquid.parser.parseStream(remainTokens).on('start', function (x) { + return p = _this.templates; + }).on('tag:endtablerow', function (token) { + return stream.stop(); + }).on('template', function (tpl) { + return p.push(tpl); + }).on('end', function (x) { + throw new Error('tag ' + tagToken.raw + ' not closed'); + }); + + stream.start(); + }, + + render: function render(scope, hash) { + var _this2 = this; + + var collection = Liquid.evalExp(this.collection, scope) || []; + + var html = '', + promiseChain = Promise.resolve(''); // create an empty promise to begin the chain + length = collection.length; + var offset = hash.offset || 0; + var limit = hash.limit === undefined ? collection.length : hash.limit; + + var cols = hash.cols, + row, + col; + if (!cols) throw new Error('illegal cols: ' + cols); + + // build array of arguments to pass to sequential promises... + collection = collection.slice(offset, offset + limit); + var contexts = []; + collection.some(function (item, i) { + var ctx = {}; + ctx[_this2.variable] = item; + // We are just putting together an array of the arguments we will be passing to our sequential promises + contexts.push(ctx); + }); + + // This executes an array of promises sequentially for every argument in the contexts array - http://webcache.googleusercontent.com/search?q=cache:rNbMUn9TPtkJ:joost.vunderink.net/blog/2014/12/15/processing-an-array-of-promises-sequentially-in-node-js/+&cd=5&hl=en&ct=clnk&gl=us + // It's fundamentally equivalent to the following... + // emptyPromise.then(renderTemplates(args0).then(renderTemplates(args1).then(renderTemplates(args2)... + var lastPromise = contexts.reduce(function (promise, context, currentIndex) { + return promise.then(function (partial) { + row = Math.floor(currentIndex / cols) + 1; + col = currentIndex % cols + 1; + if (col === 1) { + if (row !== 1) { + html += ''; + } + html += ''; + } + + //ctx[this.variable] = context; + + return html += ''; + }); + }, Promise.resolve('')); // start the reduce chain with a resolved Promise. After first run, the "promise" argument + // in our reduce callback will be the returned promise from our "then" above. In this + // case, the promise returned from liquid.renderer.renderTemplates. + + return lastPromise.then(function () { + if (row > 0) { + html += ''; + } + html += '
'; + }).then(function (partial) { + scope.push(context); + return liquid.renderer.renderTemplates(_this2.templates, scope); + }).then(function (partial) { + scope.pop(context); + html += partial; + return html += '
'; + return html; + }).catch(function (error) { + throw error; + }); + } + }); +}; + +},{"..":2,"any-promise":3}],38:[function(require,module,exports){ +'use strict'; + +var Liquid = require('..'); +var lexical = Liquid.lexical; + +module.exports = function (liquid) { + liquid.registerTag('unless', { + parse: function parse(tagToken, remainTokens) { + var _this = this; + + this.templates = []; + this.elseTemplates = []; + var p, + stream = liquid.parser.parseStream(remainTokens).on('start', function (x) { + p = _this.templates; + _this.cond = tagToken.args; + }).on('tag:else', function (token) { + return p = _this.elseTemplates; + }).on('tag:endunless', function (token) { + return stream.stop(); + }).on('template', function (tpl) { + return p.push(tpl); + }).on('end', function (x) { + throw new Error('tag ' + tagToken.raw + ' not closed'); + }); + + stream.start(); + }, + + render: function render(scope, hash) { + var cond = Liquid.evalExp(this.cond, scope); + return Liquid.isFalsy(cond) ? liquid.renderer.renderTemplates(this.templates, scope) : liquid.renderer.renderTemplates(this.elseTemplates, scope); + } + }); +}; + +},{"..":2}]},{},[2])(2) +}); \ No newline at end of file diff --git a/dist/shopify-liquid.min.js b/dist/shopify-liquid.min.js new file mode 100644 index 000000000..7d3117b52 --- /dev/null +++ b/dist/shopify-liquid.min.js @@ -0,0 +1,4 @@ +(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":">",'"':""","'":"'"};function escape(str){return(str||"").replace(/&|<|>|"|'/g,function(m){return escapeMap[m]})}liquid.registerFilter("escape",escape);var unescapeMap={"&":"&","<":"<",">":">",""":'"',"'":"'"};function unescape(str){return(str||"").replace(/&(amp|lt|gt|#34|#39);/g,function(m){return unescapeMap[m]})}liquid.registerFilter("escape_once",function(str){return escape(unescape(str))});liquid.registerFilter("first",function(v){return v[0]});liquid.registerFilter("floor",function(v){return Math.floor(v)});liquid.registerFilter("join",function(v,arg){return v.join(arg)});liquid.registerFilter("last",function(v){return v[v.length-1]});liquid.registerFilter("lstrip",function(v){return(v||"").replace(/^\s+/,"")});liquid.registerFilter("map",function(arr,arg){return arr.map(function(v){return v[arg]})});liquid.registerFilter("minus",bindFixed(function(v,arg){return v-arg}));liquid.registerFilter("modulo",bindFixed(function(v,arg){return v%arg}));liquid.registerFilter("newline_to_br",function(v){return v.replace(/\n/g,"
")});liquid.registerFilter("plus",bindFixed(function(v,arg){return v+arg}));liquid.registerFilter("prepend",function(v,arg){return arg+v});liquid.registerFilter("remove",function(v,arg){return v.split(arg).join("")});liquid.registerFilter("remove_first",function(v,l){return v.replace(l,"")});liquid.registerFilter("replace",function(v,pattern,replacement){return(v||"").split(pattern).join(replacement)});liquid.registerFilter("replace_first",function(v,arg1,arg2){return(v||"").replace(arg1,arg2)});liquid.registerFilter("reverse",function(v){return(v||"").reverse()});liquid.registerFilter("round",function(v,arg){var amp=Math.pow(10,arg||0);return Math.round(v*amp,arg)/amp});liquid.registerFilter("rstrip",function(str){return(str||"").replace(/\s+$/,"")});liquid.registerFilter("size",function(v){return v.length});liquid.registerFilter("slice",function(v,begin,length){return v.substr(begin,length===undefined?1:length)});liquid.registerFilter("sort",function(v,arg){return(v||"").sort(arg)});liquid.registerFilter("split",function(v,arg){return(v||"").split(arg)});liquid.registerFilter("strip",function(v){return(v||"").trim()});liquid.registerFilter("strip_html",function(v){return(v||"").replace(/<\/?\s*\w+\s*\/?>/g,"")});liquid.registerFilter("strip_newlines",function(v){return(v||"").replace(/\n/g,"")});liquid.registerFilter("times",function(v,arg){return v*arg});liquid.registerFilter("truncate",function(v,l,o){v=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){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){var u={};return(arr||[]).filter(function(val){if(u.hasOwnProperty(val)){return false}u[val]=true;return true})});liquid.registerFilter("upcase",function(str){return(str||"").toUpperCase()});liquid.registerFilter("url_encode",encodeURIComponent)};function getFixed(v){var p=(v+"").split(".");return p.length>1?p[1].length:0}function getMaxFixed(l,r){return Math.max(getFixed(l),getFixed(r))}function bindFixed(cb){return function(l,r){var f=getMaxFixed(l,r);return cb(l,r).toFixed(f)}}},{strftime:10}],2:[function(require,module,exports){"use strict";var Scope=require("./src/scope");var assert=require("assert");var tokenizer=require("./src/tokenizer.js");var Render=require("./src/render.js");var lexical=require("./src/lexical.js");var path=require("path");var fs=require("fs");var Tag=require("./src/tag.js");var Filter=require("./src/filter.js");var Template=require("./src/parser");var Expression=require("./src/expression.js");var tags=require("./tags");var filters=require("./filters");var Promise=require("any-promise");var _engine={init:function init(tag,filter,options){if(options.cache){this.cache={}}this.options=options;this.tag=tag;this.filter=filter;this.parser=Template(tag,filter);this.renderer=Render();tags(this);filters(this);return this},parse:function parse(html){var tokens=tokenizer.parse(html);return this.parser.parse(tokens)},render:function render(tpl,ctx,opts){opts=opts||{};opts.strict_variables=opts.strict_variables||false;opts.strict_filters=opts.strict_filters||false;this.renderer.resetRegisters();var scope=Scope.factory(ctx,{strict:opts.strict_variables});return this.renderer.renderTemplates(tpl,scope,opts)},parseAndRender:function parseAndRender(html,ctx,opts){try{var tpl=this.parse(html);return this.render(tpl,ctx,opts)}catch(error){return Promise.reject(error)}},renderFile:function renderFile(filepath,ctx,opts){var _this=this;return this.handleCache(filepath).then(function(templates){return _this.render(templates,ctx,opts)}).catch(function(e){e.file=filepath;throw e})},evalOutput:function evalOutput(str,scope){var tpl=this.parser.parseOutput(str.trim());return this.renderer.evalOutput(tpl,scope)},registerFilter:function registerFilter(name,filter){return this.filter.register(name,filter)},registerTag:function registerTag(name,tag){return this.tag.register(name,tag)},handleCache:function handleCache(filepath){var _this2=this;assert(filepath,"filepath cannot be null");filepath=path.resolve(this.options.root,filepath);if(path.extname(filepath)===""){filepath+=this.options.extname}return this.getTemplate(filepath).then(function(html){var tpl=_this2.options.cache&&_this2.cache[filepath]||_this2.parse(html);return _this2.options.cache?_this2.cache[filepath]=tpl:tpl})},getTemplate:function getTemplate(filepath){return new Promise(function(resolve,reject){fs.readFile(filepath,"utf8",function(err,html){err?reject(err):resolve(html)})})},express:function express(){var _this3=this;return function(filePath,options,callback){_this3.renderFile(filePath,options).then(function(html){return callback(null,html)}).catch(function(e){return callback(e)})}}};function factory(options){options=options||{};options.root=options.root||"";options.extname=options.extname||".liquid";var engine=Object.create(_engine);engine.init(Tag(),Filter(),options);return engine}factory.lexical=lexical;factory.isTruthy=Expression.isTruthy;factory.isFalsy=Expression.isFalsy;factory.evalExp=Expression.evalExp;factory.evalValue=Expression.evalValue;module.exports=factory},{"./filters":1,"./src/expression.js":15,"./src/filter.js":16,"./src/lexical.js":17,"./src/parser":18,"./src/render.js":19,"./src/scope":20,"./src/tag.js":22,"./src/tokenizer.js":23,"./tags":34,"any-promise":3,assert:6,fs:7,path:8}],3:[function(require,module,exports){"use strict";module.exports=require("./register")().Promise},{"./register":5}],4:[function(require,module,exports){"use strict";var REGISTRATION_KEY="@@any-promise/REGISTRATION",registered=null;module.exports=function(root,loadImplementation){return function register(implementation,opts){implementation=implementation||null;opts=opts||{};var registerGlobal=opts.global!==false;if(registered===null&®isterGlobal){registered=root[REGISTRATION_KEY]||null}if(registered!==null&&implementation!==null&®istered.implementation!==implementation){throw new Error('any-promise already defined as "'+registered.implementation+'". You can only register an implementation before the first '+' call to require("any-promise") and an implementation cannot be changed')}if(registered===null){if(implementation!==null&&typeof opts.Promise!=="undefined"){registered={Promise:opts.Promise,implementation:implementation}}else{registered=loadImplementation(implementation)}if(registerGlobal){root[REGISTRATION_KEY]=registered}}return registered}}},{}],5:[function(require,module,exports){"use strict";module.exports=require("./loader")(window,loadImplementation);function loadImplementation(){if(typeof window.Promise==="undefined"){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"}}},{"./loader":4}],6:[function(require,module,exports){"use strict";var util=require("util/");var pSlice=Array.prototype.slice;var hasOwn=Object.prototype.hasOwnProperty;var assert=module.exports=ok;assert.AssertionError=function AssertionError(options){this.name="AssertionError";this.actual=options.actual;this.expected=options.expected;this.operator=options.operator;if(options.message){this.message=options.message;this.generatedMessage=false}else{this.message=getMessage(this);this.generatedMessage=true}var stackStartFunction=options.stackStartFunction||fail;if(Error.captureStackTrace){Error.captureStackTrace(this,stackStartFunction)}else{var err=new Error;if(err.stack){var out=err.stack;var fn_name=stackStartFunction.name;var idx=out.indexOf("\n"+fn_name);if(idx>=0){var next_line=out.indexOf("\n",idx+1);out=out.substring(next_line+1)}this.stack=out}}};util.inherits(assert.AssertionError,Error);function replacer(key,value){if(util.isUndefined(value)){return""+value}if(util.isNumber(value)&&!isFinite(value)){return value.toString()}if(util.isFunction(value)||util.isRegExp(value)){return value.toString()}return value}function truncate(s,n){if(util.isString(s)){return s.length=0;i--){if(ka[i]!=kb[i])return false}for(i=ka.length-1;i>=0;i--){key=ka[i];if(!_deepEqual(a[key],b[key]))return false}return true}assert.notDeepEqual=function notDeepEqual(actual,expected,message){if(_deepEqual(actual,expected)){fail(actual,expected,message,"notDeepEqual",assert.notDeepEqual)}};assert.strictEqual=function strictEqual(actual,expected,message){if(actual!==expected){fail(actual,expected,message,"===",assert.strictEqual)}};assert.notStrictEqual=function notStrictEqual(actual,expected,message){if(actual===expected){fail(actual,expected,message,"!==",assert.notStrictEqual)}};function expectedException(actual,expected){if(!actual||!expected){return false}if(Object.prototype.toString.call(expected)=="[object RegExp]"){return expected.test(actual)}else if(actual instanceof expected){return true}else if(expected.call({},actual)===true){return true}return false}function _throws(shouldThrow,block,expected,message){var actual;if(util.isString(expected)){message=expected;expected=null}try{block()}catch(e){actual=e}message=(expected&&expected.name?" ("+expected.name+").":".")+(message?" "+message:".");if(shouldThrow&&!actual){fail(actual,expected,"Missing expected exception"+message)}if(!shouldThrow&&expectedException(actual,expected)){fail(actual,expected,"Got unwanted exception"+message)}if(shouldThrow&&actual&&expected&&!expectedException(actual,expected)||!shouldThrow&&actual){throw actual}}assert.throws=function(block,error,message){_throws.apply(this,[true].concat(pSlice.call(arguments)))};assert.doesNotThrow=function(block,message){_throws.apply(this,[false].concat(pSlice.call(arguments)))};assert.ifError=function(err){if(err){throw err}};var objectKeys=Object.keys||function(obj){var keys=[];for(var key in obj){if(hasOwn.call(obj,key))keys.push(key)}return keys}},{"util/":13}],7:[function(require,module,exports){"use strict"},{}],8:[function(require,module,exports){(function(process){"use strict";function normalizeArray(parts,allowAboveRoot){var up=0;for(var i=parts.length-1;i>=0;i--){var last=parts[i];if(last==="."){parts.splice(i,1)}else if(last===".."){parts.splice(i,1);up++}else if(up){parts.splice(i,1);up--}}if(allowAboveRoot){for(;up--;up){parts.unshift("..")}}return parts}var splitPathRe=/^(\/?|)([\s\S]*?)((?:\.{1,2}|[^\/]+?|)(\.[^.\/]*|))(?:[\/]*)$/;var splitPath=function splitPath(filename){return splitPathRe.exec(filename).slice(1)};exports.resolve=function(){var resolvedPath="",resolvedAbsolute=false;for(var i=arguments.length-1;i>=-1&&!resolvedAbsolute;i--){var path=i>=0?arguments[i]:process.cwd();if(typeof path!=="string"){throw new TypeError("Arguments to path.resolve must be strings")}else if(!path){continue}resolvedPath=path+"/"+resolvedPath;resolvedAbsolute=path.charAt(0)==="/"}resolvedPath=normalizeArray(filter(resolvedPath.split("/"),function(p){return!!p}),!resolvedAbsolute).join("/");return(resolvedAbsolute?"/":"")+resolvedPath||"."};exports.normalize=function(path){var isAbsolute=exports.isAbsolute(path),trailingSlash=substr(path,-1)==="/";path=normalizeArray(filter(path.split("/"),function(p){return!!p}),!isAbsolute).join("/");if(!path&&!isAbsolute){path="."}if(path&&trailingSlash){path+="/"}return(isAbsolute?"/":"")+path};exports.isAbsolute=function(path){return path.charAt(0)==="/"};exports.join=function(){var paths=Array.prototype.slice.call(arguments,0);return exports.normalize(filter(paths,function(p,index){if(typeof p!=="string"){throw new TypeError("Arguments to path.join must be strings")}return p}).join("/"))};exports.relative=function(from,to){from=exports.resolve(from).substr(1);to=exports.resolve(to).substr(1);function trim(arr){var start=0;for(;start=0;end--){if(arr[end]!=="")break}if(start>end)return[];return arr.slice(start,end-start+1)}var fromParts=trim(from.split("/"));var toParts=trim(to.split("/"));var length=Math.min(fromParts.length,toParts.length);var samePartsLength=length;for(var i=0;i1){for(var i=1;i_cachedDateTimestamp){_cachedDateTimestamp=currentTimestamp;_cachedDate=new Date(_cachedDateTimestamp);timestamp=_cachedDateTimestamp;if(_useUtcBasedDate){_cachedDate=new Date(_cachedDateTimestamp+getTimestampToUtcOffsetFor(_cachedDate)+_customTimezoneOffset)}}else{timestamp=_cachedDateTimestamp}date=_cachedDate}else{timestamp=date.getTime();if(_useUtcBasedDate){date=new Date(date.getTime()+getTimestampToUtcOffsetFor(date)+_customTimezoneOffset)}}return _processFormat(format,date,_locale,timestamp)}function _processFormat(format,date,locale,timestamp){var resultString="",padding=null,isInScope=false,length=format.length,extendedTZ=false;for(var i=0;i9){return numberToPad}if(paddingChar==null){paddingChar="0"}return paddingChar+numberToPad}function padTill3(numberToPad){if(numberToPad>99){return numberToPad}if(numberToPad>9){return"0"+numberToPad}return"00"+numberToPad}function hours12(hour){if(hour===0){return 12}else if(hour>12){return hour-12}return hour}function weekNumber(date,firstWeekday){firstWeekday=firstWeekday||"sunday";var weekday=date.getDay();if(firstWeekday==="monday"){if(weekday===0)weekday=6;else weekday--}var firstDayOfYearUtc=Date.UTC(date.getFullYear(),0,1),dateUtc=Date.UTC(date.getFullYear(),date.getMonth(),date.getDate()),yday=Math.floor((dateUtc-firstDayOfYearUtc)/864e5),weekNum=(yday+7-weekday)/7;return Math.floor(weekNum)}function ordinal(number){var i=number%10;var ii=number%100;if(ii>=11&&ii<=13||i===0||i>=4){return"th"}switch(i){case 1:return"st";case 2:return"nd";case 3:return"rd"}}function getTimestampToUtcOffsetFor(date){return(date.getTimezoneOffset()||0)*6e4}})()},{}],11:[function(require,module,exports){"use strict";if(typeof Object.create==="function"){module.exports=function inherits(ctor,superCtor){ctor.super_=superCtor;ctor.prototype=Object.create(superCtor.prototype,{constructor:{value:ctor,enumerable:false,writable:true,configurable:true}})}}else{module.exports=function inherits(ctor,superCtor){ctor.super_=superCtor;var TempCtor=function TempCtor(){};TempCtor.prototype=superCtor.prototype;ctor.prototype=new TempCtor;ctor.prototype.constructor=ctor}}},{}],12:[function(require,module,exports){"use strict";var _typeof=typeof Symbol==="function"&&typeof Symbol.iterator==="symbol"?function(obj){ +return typeof obj}:function(obj){return obj&&typeof Symbol==="function"&&obj.constructor===Symbol?"symbol":typeof obj};module.exports=function isBuffer(arg){return arg&&(typeof arg==="undefined"?"undefined":_typeof(arg))==="object"&&typeof arg.copy==="function"&&typeof arg.fill==="function"&&typeof arg.readUInt8==="function"}},{}],13:[function(require,module,exports){(function(process,global){"use strict";var _typeof=typeof Symbol==="function"&&typeof Symbol.iterator==="symbol"?function(obj){return typeof obj}:function(obj){return obj&&typeof Symbol==="function"&&obj.constructor===Symbol?"symbol":typeof obj};var formatRegExp=/%[sdj%]/g;exports.format=function(f){if(!isString(f)){var objects=[];for(var i=0;i=len)return x;switch(x){case"%s":return String(args[i++]);case"%d":return Number(args[i++]);case"%j":try{return JSON.stringify(args[i++])}catch(_){return"[Circular]"}default:return x}});for(var x=args[i];i=3)ctx.depth=arguments[2];if(arguments.length>=4)ctx.colors=arguments[3];if(isBoolean(opts)){ctx.showHidden=opts}else if(opts){exports._extend(ctx,opts)}if(isUndefined(ctx.showHidden))ctx.showHidden=false;if(isUndefined(ctx.depth))ctx.depth=2;if(isUndefined(ctx.colors))ctx.colors=false;if(isUndefined(ctx.customInspect))ctx.customInspect=true;if(ctx.colors)ctx.stylize=stylizeWithColor;return formatValue(ctx,obj,ctx.depth)}exports.inspect=inspect;inspect.colors={bold:[1,22],italic:[3,23],underline:[4,24],inverse:[7,27],white:[37,39],grey:[90,39],black:[30,39],blue:[34,39],cyan:[36,39],green:[32,39],magenta:[35,39],red:[31,39],yellow:[33,39]};inspect.styles={special:"cyan",number:"yellow","boolean":"yellow",undefined:"grey","null":"bold",string:"green",date:"magenta",regexp:"red"};function stylizeWithColor(str,styleType){var style=inspect.styles[styleType];if(style){return"["+inspect.colors[style][0]+"m"+str+"["+inspect.colors[style][1]+"m"}else{return str}}function stylizeNoColor(str,styleType){return str}function arrayToHash(array){var hash={};array.forEach(function(val,idx){hash[val]=true});return hash}function formatValue(ctx,value,recurseTimes){if(ctx.customInspect&&value&&isFunction(value.inspect)&&value.inspect!==exports.inspect&&!(value.constructor&&value.constructor.prototype===value)){var ret=value.inspect(recurseTimes,ctx);if(!isString(ret)){ret=formatValue(ctx,ret,recurseTimes)}return ret}var primitive=formatPrimitive(ctx,value);if(primitive){return primitive}var keys=Object.keys(value);var visibleKeys=arrayToHash(keys);if(ctx.showHidden){keys=Object.getOwnPropertyNames(value)}if(isError(value)&&(keys.indexOf("message")>=0||keys.indexOf("description")>=0)){return formatError(value)}if(keys.length===0){if(isFunction(value)){var name=value.name?": "+value.name:"";return ctx.stylize("[Function"+name+"]","special")}if(isRegExp(value)){return ctx.stylize(RegExp.prototype.toString.call(value),"regexp")}if(isDate(value)){return ctx.stylize(Date.prototype.toString.call(value),"date")}if(isError(value)){return formatError(value)}}var base="",array=false,braces=["{","}"];if(isArray(value)){array=true;braces=["[","]"]}if(isFunction(value)){var n=value.name?": "+value.name:"";base=" [Function"+n+"]"}if(isRegExp(value)){base=" "+RegExp.prototype.toString.call(value)}if(isDate(value)){base=" "+Date.prototype.toUTCString.call(value)}if(isError(value)){base=" "+formatError(value)}if(keys.length===0&&(!array||value.length==0)){return braces[0]+base+braces[1]}if(recurseTimes<0){if(isRegExp(value)){return ctx.stylize(RegExp.prototype.toString.call(value),"regexp")}else{return ctx.stylize("[Object]","special")}}ctx.seen.push(value);var output;if(array){output=formatArray(ctx,value,recurseTimes,visibleKeys,keys)}else{output=keys.map(function(key){return formatProperty(ctx,value,recurseTimes,visibleKeys,key,array)})}ctx.seen.pop();return reduceToSingleString(output,base,braces)}function formatPrimitive(ctx,value){if(isUndefined(value))return ctx.stylize("undefined","undefined");if(isString(value)){var simple="'"+JSON.stringify(value).replace(/^"|"$/g,"").replace(/'/g,"\\'").replace(/\\"/g,'"')+"'";return ctx.stylize(simple,"string")}if(isNumber(value))return ctx.stylize(""+value,"number");if(isBoolean(value))return ctx.stylize(""+value,"boolean");if(isNull(value))return ctx.stylize("null","null")}function formatError(value){return"["+Error.prototype.toString.call(value)+"]"}function formatArray(ctx,value,recurseTimes,visibleKeys,keys){var output=[];for(var i=0,l=value.length;i-1){if(array){str=str.split("\n").map(function(line){return" "+line}).join("\n").substr(2)}else{str="\n"+str.split("\n").map(function(line){return" "+line}).join("\n")}}}else{str=ctx.stylize("[Circular]","special")}}if(isUndefined(name)){if(array&&key.match(/^\d+$/)){return str}name=JSON.stringify(""+key);if(name.match(/^"([a-zA-Z_][a-zA-Z_0-9]*)"$/)){name=name.substr(1,name.length-2);name=ctx.stylize(name,"name")}else{name=name.replace(/'/g,"\\'").replace(/\\"/g,'"').replace(/(^"|"$)/g,"'");name=ctx.stylize(name,"string")}}return name+": "+str}function reduceToSingleString(output,base,braces){var numLinesEst=0;var length=output.reduce(function(prev,cur){numLinesEst++;if(cur.indexOf("\n")>=0)numLinesEst++;return prev+cur.replace(/\u001b\[\d\d?m/g,"").length+1},0);if(length>60){return braces[0]+(base===""?"":base+"\n ")+" "+output.join(",\n ")+" "+braces[1]}return braces[0]+base+" "+output.join(", ")+" "+braces[1]}function isArray(ar){return Array.isArray(ar)}exports.isArray=isArray;function isBoolean(arg){return typeof arg==="boolean"}exports.isBoolean=isBoolean;function isNull(arg){return arg===null}exports.isNull=isNull;function isNullOrUndefined(arg){return arg==null}exports.isNullOrUndefined=isNullOrUndefined;function isNumber(arg){return typeof arg==="number"}exports.isNumber=isNumber;function isString(arg){return typeof arg==="string"}exports.isString=isString;function isSymbol(arg){return(typeof arg==="undefined"?"undefined":_typeof(arg))==="symbol"}exports.isSymbol=isSymbol;function isUndefined(arg){return arg===void 0}exports.isUndefined=isUndefined;function isRegExp(re){return isObject(re)&&objectToString(re)==="[object RegExp]"}exports.isRegExp=isRegExp;function isObject(arg){return(typeof arg==="undefined"?"undefined":_typeof(arg))==="object"&&arg!==null}exports.isObject=isObject;function isDate(d){return isObject(d)&&objectToString(d)==="[object Date]"}exports.isDate=isDate;function isError(e){return isObject(e)&&(objectToString(e)==="[object Error]"||e instanceof Error)}exports.isError=isError;function isFunction(arg){return typeof arg==="function"}exports.isFunction=isFunction;function isPrimitive(arg){return arg===null||typeof arg==="boolean"||typeof arg==="number"||typeof arg==="string"||(typeof arg==="undefined"?"undefined":_typeof(arg))==="symbol"||typeof arg==="undefined"}exports.isPrimitive=isPrimitive;exports.isBuffer=require("./support/isBuffer");function objectToString(o){return Object.prototype.toString.call(o)}function pad(n){return n<10?"0"+n.toString(10):n.toString(10)}var months=["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"];function timestamp(){var d=new Date;var time=[pad(d.getHours()),pad(d.getMinutes()),pad(d.getSeconds())].join(":");return[d.getDate(),months[d.getMonth()],time].join(" ")}exports.log=function(){console.log("%s - %s",timestamp(),exports.format.apply(exports,arguments))};exports.inherits=require("inherits");exports._extend=function(origin,add){if(!add||!isObject(add))return origin;var keys=Object.keys(add);var i=keys.length;while(i--){origin[keys[i]]=add[keys[i]]}return origin};function hasOwnProperty(obj,prop){return Object.prototype.hasOwnProperty.call(obj,prop)}}).call(this,require("_process"),typeof global!=="undefined"?global:typeof self!=="undefined"?self:typeof window!=="undefined"?window:{})},{"./support/isBuffer":12,_process:9,inherits:11}],14:[function(require,module,exports){"use strict";var util=require("util");function TokenizationError(message,input,line){Error.captureStackTrace(this,this.constructor);this.name=this.constructor.name;this.message=message||"";this.input=input;this.line=line}util.inherits(TokenizationError,Error);function ParseError(message,input,line){Error.captureStackTrace(this,this.constructor);this.name=this.constructor.name;this.message=message||"";this.input=input;this.line=line}util.inherits(ParseError,Error);module.exports={TokenizationError:TokenizationError,ParseError:ParseError}},{util:13}],15:[function(require,module,exports){"use strict";var syntax=require("./syntax.js");var Exp=require("./expression.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=|<|>|\s+contains\s+/];function isLiteral(str){return literalLine.test(str)}function isRange(str){return rangeLine.test(str)}function isVariable(str){return variableLine.test(str)}function parseLiteral(str){var res;if(res=str.match(numberLine)){return Number(str)}if(res=str.match(boolLine)){return str.toLowerCase()==="true"}if(res=str.match(quotedLine)){return str.slice(1,-1)}}module.exports={quoted:quoted,number:number,bool:bool,literal:literal,filter:filter,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}},{}],18:[function(require,module,exports){"use strict";var lexical=require("./lexical.js");var ParseError=require("./error.js").ParseError;module.exports=function(Tag,Filter){var stream={init:function init(tokens){this.tokens=tokens;this.handlers={};return this},on:function on(name,cb){this.handlers[name]=cb;return this},trigger:function trigger(event,arg){var h=this.handlers[event];if(typeof h==="function"){h(arg);return true}},start:function start(){this.trigger("start");while(!this.stopRequested&&(token=this.tokens.shift())){if(this.trigger("token",token))continue;if(token.type=="tag"&&this.trigger("tag:"+token.name,token)){continue}var template=parseToken(token,this.tokens);this.trigger("template",template)}if(!this.stopRequested)this.trigger("end");return this},stop:function stop(){this.stopRequested=true;return this}};function parse(tokens){var token,templates=[];while(token=tokens.shift()){templates.push(parseToken(token,tokens))}return templates}function parseToken(token,tokens){try{switch(token.type){case"tag":return parseTag(token,tokens);case"output":return parseOutput(token.value);case"html":return token}}catch(e){throw new ParseError(e.message,token.input,token.line)}}function parseTag(token,tokens){if(token.name==="continue"||token.name==="break")return token;return Tag.construct(token,tokens)}function parseOutput(str){var match=lexical.value.exec(str);if(!match)throw new Error("illegal output string: "+str);var initial=match[0];str=str.substr(match.index+match[0].length);var filters=[];while(match=lexical.filter.exec(str)){filters.push([match[0].trim()])}return{type:"output",initial:initial,filters:filters.map(function(str){return Filter.construct(str)})}}function parseStream(tokens){var s=Object.create(stream);return s.init(tokens)}return{parse:parse,parseTag:parseTag,parseStream:parseStream,parseOutput:parseOutput}}},{"./error.js":14,"./lexical.js":17}],19:[function(require,module,exports){"use strict";var error=require("./error.js");var Exp=require("./expression.js");var assert=require("assert");var Promise=require("any-promise");var render={renderTemplates:function renderTemplates(templates,scope,opts){var _this=this;assert(scope,"unable to evalTemplates: scope undefined");opts=opts||{};opts.strict_filters=opts.strict_filters||false;var html="";var lastPromise=templates.reduce(function(promise,template){return promise.then(function(partial){if(scope.safeGet("forloop.skip")){return Promise.resolve("")}if(scope.safeGet("forloop.stop")){throw new Error("forloop.stop")}var promiseLink=Promise.resolve("");switch(template.type){case"tag":promiseLink=_this.renderTag(template,scope,_this.register).then(function(partial){if(partial===undefined){return true}return html+=partial});break;case"html":promiseLink=Promise.resolve(template.value).then(function(partial){return html+=partial});break;case"output":var val=_this.evalOutput(template,scope,opts);promiseLink=Promise.resolve(val===undefined?"":stringify(val)).then(function(partial){return html+=partial});break}return promiseLink}).catch(function(error){if(error.message==="forloop.skip"){return html}else{throw error}})},Promise.resolve(""));return lastPromise.then(function(renderedHtml){return renderedHtml}).catch(function(error){throw error})},renderTag:function renderTag(template,scope,register){if(template.name==="continue"){scope.set("forloop.skip",true);return Promise.resolve("")}if(template.name==="break"){scope.set("forloop.stop",true);scope.set("forloop.skip",true);return Promise.reject(new Error("forloop.stop"))}return template.render(scope,register)},evalOutput:function evalOutput(template,scope,opts){assert(scope,"unable to evalOutput: scope undefined");var val=Exp.evalExp(template.initial,scope);template.filters.some(function(filter){if(filter.error){if(opts.strict_filters){throw filter.error}else{val="";return true}}val=filter.render(val,scope)});return val},resetRegisters:function resetRegisters(){return this.register={}}};function factory(){var instance=Object.create(render);instance.register={};return instance}function stringify(val){if(typeof val==="string")return val;return JSON.stringify(val)}module.exports=factory},{"./error.js":14,"./expression.js":15,"any-promise":3,assert:6}],20:[function(require,module,exports){"use strict";var lexical=require("./lexical.js");var Scope={safeGet:function safeGet(str){var i;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}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()}};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":function _(l,r){return l>r},"<":function _(l,r){return l=":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},{}],22:[function(require,module,exports){"use strict";var lexical=require("./lexical.js");var Promise=require("any-promise");var Exp=require("./expression.js");var TokenizationError=require("./error.js").TokenizationError;function hash(markup,scope){var obj={};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}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}}},{"./error.js":14,"./expression.js":15,"./lexical.js":17,"any-promise":3}],23:[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){if(result.index>lastMatchEnd){htmlFragment=html.slice(lastMatchEnd,result.index);tokens.push({type:"html",raw:htmlFragment,value:htmlFragment})}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)}else{token=factory("output",3,result);tokens.push(token)}lastMatchEnd=syntax.lastIndex}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":14,"./lexical.js":17}],24:[function(require,module,exports){"use strict";var Liquid=require("..");var Promise=require("any-promise");var lexical=Liquid.lexical;var re=new RegExp("("+lexical.identifier.source+")\\s*=(.*)");module.exports=function(liquid){liquid.registerTag("assign",{parse:function parse(token){var match=token.args.match(re);if(!match)throw new Error("illegal token "+token.raw);this.key=match[1];this.value=match[2]},render:function render(scope,hash){scope.set(this.key,liquid.evalOutput(this.value,scope));return Promise.resolve("")}})}},{"..":2,"any-promise":3}],25:[function(require,module,exports){"use strict";var Liquid=require("..");var lexical=Liquid.lexical;var re=new RegExp("("+lexical.identifier.source+")");module.exports=function(liquid){liquid.registerTag("capture",{parse:function parse(tagToken,remainTokens){var _this=this;var match=tagToken.args.match(re);if(!match)throw new Error(tagToken.args+" not valid identifier");this.variable=match[1];this.templates=[];var stream=liquid.parser.parseStream(remainTokens);stream.on("tag:endcapture",function(token){return stream.stop()}).on("template",function(tpl){return _this.templates.push(tpl)}).on("end",function(x){throw new Error("tag "+tagToken.raw+" not closed")});stream.start()},render:function render(scope,hash){var _this2=this;return liquid.renderer.renderTemplates(this.templates,scope).then(function(html){scope.set(_this2.variable,html)})}})}},{"..":2}],26:[function(require,module,exports){"use strict";var Liquid=require("..");var lexical=Liquid.lexical;module.exports=function(liquid){liquid.registerTag("case",{parse:function parse(tagToken,remainTokens){var _this=this;this.cond=tagToken.args;this.cases=[];this.elseTemplates=[];var p=[],stream=liquid.parser.parseStream(remainTokens).on("tag:when",function(token){if(!_this.cases[token.args]){_this.cases.push({val:token.args,templates:p=[]})}}).on("tag:else",function(token){return p=_this.elseTemplates}).on("tag:endcase",function(token){return stream.stop()}).on("template",function(tpl){return p.push(tpl)}).on("end",function(x){throw new Error("tag "+tagToken.raw+" not closed")});stream.start()},render:function render(scope,hash){for(var i=0;i"}html+=''}return html+=''}).then(function(partial){scope.push(context);return liquid.renderer.renderTemplates(_this2.templates,scope)}).then(function(partial){scope.pop(context);html+=partial;return html+=""})},Promise.resolve(""));return lastPromise.then(function(){if(row>0){html+=""}html+="";return html}).catch(function(error){throw error})}})}},{"..":2,"any-promise":3}],38:[function(require,module,exports){"use strict";var Liquid=require("..");var lexical=Liquid.lexical;module.exports=function(liquid){liquid.registerTag("unless",{parse:function parse(tagToken,remainTokens){var _this=this;this.templates=[];this.elseTemplates=[];var p,stream=liquid.parser.parseStream(remainTokens).on("start",function(x){p=_this.templates;_this.cond=tagToken.args}).on("tag:else",function(token){return p=_this.elseTemplates}).on("tag:endunless",function(token){return stream.stop()}).on("template",function(tpl){return p.push(tpl)}).on("end",function(x){throw new Error("tag "+tagToken.raw+" not closed")});stream.start()},render:function render(scope,hash){var cond=Liquid.evalExp(this.cond,scope);return Liquid.isFalsy(cond)?liquid.renderer.renderTemplates(this.templates,scope):liquid.renderer.renderTemplates(this.elseTemplates,scope)}})}},{"..":2}]},{},[2])(2)}); \ No newline at end of file diff --git a/filters.js b/filters.js index 05552acd9..6a603185f 100644 --- a/filters.js +++ b/filters.js @@ -1,23 +1,49 @@ -const _ = require('lodash'); const strftime = require('strftime').timezone(-(new Date()).getTimezoneOffset()); module.exports = function(liquid) { liquid.registerFilter('abs', v => Math.abs(v)); liquid.registerFilter('append', (v, arg) => v + arg); - liquid.registerFilter('capitalize', v => _.capitalize(v)); + liquid.registerFilter('capitalize', str => + (str || '').charAt(0).toUpperCase() + str.slice(1)); liquid.registerFilter('ceil', v => Math.ceil(v)); + liquid.registerFilter('date', (v, arg) => strftime(arg, v)); + liquid.registerFilter('default', (v, arg) => arg || v); liquid.registerFilter('divided_by', (v, arg) => Math.floor(v / arg)); liquid.registerFilter('downcase', v => v.toLowerCase()); - liquid.registerFilter('escape', v => _.escape(v)); - liquid.registerFilter('escape_once', v => _.escape(_.unescape(v))); + + var escapeMap = { + '&': '&', + '<': '<', + '>': '>', + '"': '"', + "'": ''', + }; + + function escape(str) { + return (str || '').replace(/&|<|>|"|'/g, m => escapeMap[m]); + } + liquid.registerFilter('escape', escape); + + var unescapeMap = { + '&': '&', + '<': '<', + '>': '>', + '"': '"', + ''': "'", + }; + + function unescape(str) { + return (str || '').replace(/&(amp|lt|gt|#34|#39);/g, m => unescapeMap[m]); + } + liquid.registerFilter('escape_once', str => escape(unescape(str))); liquid.registerFilter('first', v => v[0]); liquid.registerFilter('floor', v => Math.floor(v)); - liquid.registerFilter('join', _.join); + liquid.registerFilter('join', (v, arg) => v.join(arg)); liquid.registerFilter('last', v => v[v.length - 1]); - liquid.registerFilter('lstrip', v => _.trimStart(v)); - liquid.registerFilter('map', (v, arg) => _.map(v, arg)); + liquid.registerFilter('lstrip', v => (v || '').replace(/^\s+/, '')); + liquid.registerFilter('map', (arr, arg) => arr.map(v => v[arg])); liquid.registerFilter('minus', bindFixed((v, arg) => v - arg)); liquid.registerFilter('modulo', bindFixed((v, arg) => v % arg)); liquid.registerFilter('newline_to_br', v => v.replace(/\n/g, '
')); @@ -25,25 +51,31 @@ module.exports = function(liquid) { liquid.registerFilter('prepend', (v, arg) => arg + v); liquid.registerFilter('remove', (v, arg) => v.split(arg).join('')); liquid.registerFilter('remove_first', (v, l) => v.replace(l, '')); - liquid.registerFilter('replace', (v, pattern, replacement) => - _.replace(v, new RegExp(_.escapeRegExp(pattern), 'g'), replacement)); - liquid.registerFilter('replace_first', _.replace); - liquid.registerFilter('reverse', _.reverse); - liquid.registerFilter('round', _.round); - liquid.registerFilter('rstrip', _.trimEnd); - liquid.registerFilter('size', _.size); + liquid.registerFilter('replace', (v, pattern, replacement) => + (v || '').split(pattern).join(replacement)); + liquid.registerFilter('replace_first', (v, arg1, arg2) => (v || '').replace(arg1, arg2)); + liquid.registerFilter('reverse', v => (v || '').reverse()); + liquid.registerFilter('round', (v, arg) => { + var amp = Math.pow(10, arg || 0); + return Math.round(v * amp, arg) / amp; + }); + liquid.registerFilter('rstrip', str => (str || '').replace(/\s+$/, '')); + liquid.registerFilter('size', v => v.length); liquid.registerFilter('slice', (v, begin, length) => v.substr(begin, length === undefined ? 1 : length)); - liquid.registerFilter('sort', _.sortBy); - liquid.registerFilter('split', _.split); - liquid.registerFilter('strip', _.trim); - liquid.registerFilter('strip_html', v => _.replace(v, /<\/?\s*\w+\s*\/?>/g, '')); - liquid.registerFilter('strip_newlines', v => _.replace(v, /\n/g, '')); + liquid.registerFilter('sort', (v, arg) => (v || '').sort(arg)); + liquid.registerFilter('split', (v, arg) => (v || '').split(arg)); + liquid.registerFilter('strip', (v) => (v || '').trim()); + liquid.registerFilter('strip_html', v => (v || '').replace(/<\/?\s*\w+\s*\/?>/g, '')); + liquid.registerFilter('strip_newlines', v => (v || '').replace(/\n/g, '')); liquid.registerFilter('times', (v, arg) => v * arg); - liquid.registerFilter('truncate', (v, l, o) => _.truncate(v, { - length: l, - omission: o === undefined ? '...' : o - })); + liquid.registerFilter('truncate', (v, l, o) => { + v = 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', (v, l, o) => { if (o === undefined) o = '...'; var arr = v.split(' '); @@ -51,8 +83,17 @@ module.exports = function(liquid) { if (arr.length > l) ret += o; return ret; }); - liquid.registerFilter('uniq', _.uniq); - liquid.registerFilter('upcase', _.toUpper); + liquid.registerFilter('uniq', function(arr) { + var u = {}; + return (arr || []).filter(val => { + if (u.hasOwnProperty(val)) { + return false; + } + u[val] = true; + return true; + }); + }); + liquid.registerFilter('upcase', str => (str || '').toUpperCase()); liquid.registerFilter('url_encode', encodeURIComponent); }; diff --git a/index.js b/index.js index 850e69db4..227e42705 100644 --- a/index.js +++ b/index.js @@ -1,6 +1,5 @@ const Scope = require('./src/scope'); const assert = require('assert'); -const _ = require('lodash'); const tokenizer = require('./src/tokenizer.js'); const Render = require('./src/render.js'); const lexical = require('./src/lexical.js'); @@ -35,10 +34,10 @@ var _engine = { return this.parser.parse(tokens); }, render: function(tpl, ctx, opts) { - opts = _.defaults(opts, { - strict_variables: false, - strict_filters: false - }); + opts = opts || {}; + opts.strict_variables = opts.strict_variables || false; + opts.strict_filters = opts.strict_filters || false; + this.renderer.resetRegisters(); var scope = Scope.factory(ctx, { strict: opts.strict_variables, @@ -105,10 +104,10 @@ var _engine = { }; function factory(options) { - options = _.defaults(options || { - root: '', - extname: '.liquid' - }); + options = options || {}; + options.root = options.root || ''; + options.extname = options.extname || '.liquid'; + var engine = Object.create(_engine); engine.init(Tag(), Filter(), options); diff --git a/makefile b/makefile new file mode 100644 index 000000000..9dfb4c7f8 --- /dev/null +++ b/makefile @@ -0,0 +1,17 @@ +MINIFY = ./node_modules/.bin/uglifyjs +BROWSERIFY = ./node_modules/.bin/browserify + +.PHONY: dist default clean + +default: + +dist: + [ -d dist/ ] || mkdir dist/ + $(BROWSERIFY) index.js -s Liquid \ + -t [ babelify --global true --presets [ es2015 ] ] \ + > dist/shopify-liquid.js + $(MINIFY) dist/shopify-liquid.js --output dist/shopify-liquid.min.js + ls -lh dist/ + +clean: + rm -rf dist/ diff --git a/package.json b/package.json index e5d1d519e..0456174ef 100644 --- a/package.json +++ b/package.json @@ -1,10 +1,11 @@ { "name": "shopify-liquid", - "version": "1.1.15", + "version": "1.2.0", "description": "Liquid template engine in Node.js (Shopify compliant)", "main": "index.js", "scripts": { - "test": "mocha --recursive" + "test": "mocha --recursive", + "dist": "make dist" }, "repository": { "type": "git", @@ -24,10 +25,12 @@ "homepage": "https://github.com/harttle/shopify-liquid#readme", "dependencies": { "any-promise": "^1.3.0", - "lodash": "^4.13.1", "strftime": "^0.9.2" }, "devDependencies": { + "babel-preset-es2015": "^6.14.0", + "babelify": "^7.3.0", + "browserify": "^13.1.0", "chai": "^3.5.0", "chai-as-promised": "^5.3.0", "coveralls": "^2.11.9", @@ -37,6 +40,7 @@ "mock-fs": "^3.9.0", "sinon": "^1.17.4", "sinon-chai": "^2.8.0", - "supertest": "^1.2.0" + "supertest": "^1.2.0", + "uglifyjs": "^2.4.10" } } diff --git a/src/expression.js b/src/expression.js index 30aa96792..ebe77a6fd 100644 --- a/src/expression.js +++ b/src/expression.js @@ -1,7 +1,6 @@ const syntax = require('./syntax.js'); const Exp = require('./expression.js'); const lexical = require('./lexical.js'); -const _ = require('lodash'); function evalExp(exp, scope) { if (!scope) throw new Error('unable to evalExp: scope undefined'); @@ -20,8 +19,12 @@ function evalExp(exp, scope) { if (match = exp.match(lexical.rangeLine)) { var low = evalValue(match[1], scope), - high = evalValue(match[2], scope) + 1; - return _.range(low, high); + high = evalValue(match[2], scope); + var range = []; + for (var j = low; j <= high; j++) { + range.push(j); + } + return range; } return evalValue(exp, scope); diff --git a/src/filter.js b/src/filter.js index d336d9d91..f5995780b 100644 --- a/src/filter.js +++ b/src/filter.js @@ -1,5 +1,4 @@ const lexical = require('./lexical.js'); -const _ = require('lodash'); const Exp = require('./expression.js'); var valueRE = new RegExp(`${lexical.value.source}`, 'g'); diff --git a/src/lexical.js b/src/lexical.js index 7a981883d..c91cc5639 100644 --- a/src/lexical.js +++ b/src/lexical.js @@ -1,5 +1,3 @@ -const _ = require('lodash'); - // quote related var singleQuoted = /'[^']*'/; var doubleQuoted = /"[^"]*"/; diff --git a/src/render.js b/src/render.js index 74dafdb9b..612fce1d5 100644 --- a/src/render.js +++ b/src/render.js @@ -2,15 +2,13 @@ const error = require('./error.js'); const Exp = require('./expression.js'); const assert = require('assert'); const Promise = require('any-promise'); -const _ = require('lodash'); var render = { renderTemplates: function(templates, scope, opts) { assert(scope, 'unable to evalTemplates: scope undefined'); - opts = _.defaults(opts, { - strict_filters: false - }); + opts = opts || {}; + opts.strict_filters = opts.strict_filters || false; var html = ''; diff --git a/src/scope.js b/src/scope.js index 129f7a128..3db4bcf8d 100644 --- a/src/scope.js +++ b/src/scope.js @@ -1,4 +1,3 @@ -const _ = require('lodash'); const lexical = require('./lexical.js'); var Scope = { @@ -8,17 +7,22 @@ var Scope = { if (str === undefined) { var ctx = {}; for (i = this.scopes.length - 1; i >= 0; i--) { - _.merge(ctx, this.scopes[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 = _.get(this.scopes[i], str); + var v = getPropertyByPath(this.scopes[i], str); if (v !== undefined) return v; } }, - get: function(str){ + get: function(str) { var val = this.safeGet(str); if (val === undefined && this.opts.strict) { throw new Error(`[strict_variables] undefined variable: ${str}`); @@ -26,7 +30,7 @@ var Scope = { return val; }, set: function(k, v) { - _.set(this.scopes[this.scopes.length - 1], k, v); + setPropertyByPath(this.scopes[this.scopes.length - 1], k, v); return this; }, push: function(ctx) { @@ -38,10 +42,35 @@ var Scope = { } }; +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(p => obj = obj && obj[p]); + return obj; + } + return obj[path]; +} + exports.factory = function(_ctx, opts) { - opts = _.defaults(opts, { - strict: false - }); + opts = opts || {}; + opts.strict = opts.strict || false; var scope = Object.create(Scope); scope.opts = opts; diff --git a/tags/for.js b/tags/for.js index 7dec260de..124fa731b 100644 --- a/tags/for.js +++ b/tags/for.js @@ -1,6 +1,5 @@ var Liquid = require('..'); var Promise = require('any-promise'); -var _ = require('lodash'); var lexical = Liquid.lexical; var re = new RegExp(`^(${lexical.identifier.source})\\s+in\\s+` + `(${lexical.value.source})` + diff --git a/test/filters.js b/test/filters.js index cd1a181f5..c0bafb1cb 100644 --- a/test/filters.js +++ b/test/filters.js @@ -24,32 +24,32 @@ function test(src, dst) { } describe('filters', function() { - it('should support abs 1', function() { return test('{{ -3 | abs }}', '3'); }); - it('should support abs 2', function() { return test('{{ arr[0] | abs }}', '2'); }); + it('should support abs 1', () => test('{{ -3 | abs }}', '3')); + it('should support abs 2', () => test('{{ arr[0] | abs }}', '2')); - it('should support append 1', function() { return test('{{ -3 | append: "abc" }}', '-3abc'); }); - it('should support append 2', function() { return test('{{ "a" | append: foo }}', 'abar');; }); + it('should support append 1', () => test('{{ -3 | append: "abc" }}', '-3abc')); + it('should support append 2', () => test('{{ "a" | append: foo }}', 'abar')); - it('should support capitalize', function() { return test('{{ "i am good" | capitalize }}', 'I am good'); }); + it('should support capitalize', () => test('{{ "i am good" | capitalize }}', 'I am good')); - it('should support ceil 1', function() { return test('{{ 1.2 | ceil }}', '2'); }); - it('should support ceil 2', function() { return test('{{ 2.0 | ceil }}', '2'); }); - it('should support ceil 3', function() { return test('{{ "3.5" | ceil }}', '4'); }); - it('should support ceil 4', function() { return test('{{ 183.357 | ceil }}', '184'); }); + it('should support ceil 1', () => test('{{ 1.2 | ceil }}', '2')); + it('should support ceil 2', () => test('{{ 2.0 | ceil }}', '2')); + it('should support ceil 3', () => test('{{ "3.5" | ceil }}', '4')); + it('should support ceil 4', () => test('{{ 183.357 | ceil }}', '184')); it('should support date', function() { str = ctx.date.toDateString(); return test('{{ date | date:"%a %b %d %Y"}}', str); }); - it('should support default', function() { return test('{{false |default: "a"}}', 'a'); }); + it('should support default', () => test('{{false |default: "a"}}', 'a')); - it('should support divided_by 1', function() { return test('{{4 | divided_by: 2}}', '2'); }); - it('should support divided_by 2', function() { return test('{{16 | divided_by: 4}}', '4'); }); - it('should support divided_by 3', function() { return test('{{5 | divided_by: 3}}', '1'); }); + it('should support divided_by 1', () => test('{{4 | divided_by: 2}}', '2')); + it('should support divided_by 2', () => test('{{16 | divided_by: 4}}', '4')); + it('should support divided_by 3', () => test('{{5 | divided_by: 3}}', '1')); - it('should support downcase 1', function() { return test('{{ "Parker Moore" | downcase }}', 'parker moore'); }); - it('should support downcase 2', function() { return test('{{ "apple" | downcase }}', 'apple'); }); + it('should support downcase 1', () => test('{{ "Parker Moore" | downcase }}', 'parker moore')); + it('should support downcase 2', () => test('{{ "apple" | downcase }}', 'apple')); it('should support escape 1', function() { return test('{{ "Have you read \'James & the Giant Peach\'?" | escape }}', @@ -59,8 +59,8 @@ describe('filters', function() { return test('{{ "Tetsuro Takara" | escape }}', 'Tetsuro Takara'); }); - it('should support escape_once 1', function() { return test('{{ "1 < 2 & 3" | escape_once }}', '1 < 2 & 3'); }); - it('should support escape_once 2', function() { return test('{{ "1 < 2 & 3" | escape_once }}', '1 < 2 & 3'); }); + it('should support escape_once 1', () => test('{{ "1 < 2 & 3" | escape_once }}', '1 < 2 & 3')); + it('should support escape_once 2', () => test('{{ "1 < 2 & 3" | escape_once }}', '1 < 2 & 3')); it('should support split/first', function() { src = '{% assign my_array = "apples, oranges, peaches, plums" | split: ", " %}' + @@ -68,10 +68,10 @@ describe('filters', function() { return test(src, 'apples'); }); - it('should support floor 1', function() { return test('{{ 1.2 | floor }}', '1'); }); - it('should support floor 2', function() { return test('{{ 2.0 | floor }}', '2'); }); - it('should support floor 3', function() { return test('{{ 183.357 | floor }}', '183'); }); - it('should support floor 4', function() { return test('{{ "3.5" | floor }}', '3'); }); + it('should support floor 1', () => test('{{ 1.2 | floor }}', '1')); + it('should support floor 2', () => test('{{ 2.0 | floor }}', '2')); + it('should support floor 3', () => test('{{ 183.357 | floor }}', '183')); + it('should support floor 4', () => test('{{ "3.5" | floor }}', '3')); it('should support join', function() { src = '{% assign beatles = "John, Paul, George, Ringo" | split: ", " %}' + @@ -94,13 +94,13 @@ describe('filters', function() { return test('{{posts | map: "category"}}', '["foo","bar"]'); }); - it('should support minus 1', function() { return test('{{ 4 | minus: 2 }}', '2'); }); - it('should support minus 2', function() { return test('{{ 16 | minus: 4 }}', '12'); }); - it('should support minus 3', function() { return test('{{ 183.357 | minus: 12 }}', '171.357'); }); + it('should support minus 1', () => test('{{ 4 | minus: 2 }}', '2')); + it('should support minus 2', () => test('{{ 16 | minus: 4 }}', '12')); + it('should support minus 3', () => test('{{ 183.357 | minus: 12 }}', '171.357')); - it('should support modulo 1', function() { return test('{{ 3 | modulo: 2 }}', '1'); }); - it('should support modulo 2', function() { return test('{{ 24 | modulo: 7 }}', '3'); }); - it('should support modulo 3', function() { return test('{{ 183.357 | modulo: 12 }}', '3.357'); }); + it('should support modulo 1', () => test('{{ 3 | modulo: 2 }}', '1')); + it('should support modulo 2', () => test('{{ 24 | modulo: 7 }}', '3')); + it('should support modulo 3', () => test('{{ 183.357 | modulo: 12 }}', '3.357')); it('should support string_with_newlines', function() { src = '{% capture string_with_newlines %}\n' + @@ -114,9 +114,9 @@ describe('filters', function() { return test(src, dst); }); - it('should support plus 1', function() { return test('{{ 4 | plus: 2 }}', '6'); }); - it('should support plus 2', function() { return test('{{ 16 | plus: 4 }}', '20'); }); - it('should support plus 3', function() { return test('{{ 183.357 | plus: 12 }}', '195.357'); }); + it('should support plus 1', () => test('{{ 4 | plus: 2 }}', '6')); + it('should support plus 2', () => test('{{ 16 | plus: 4 }}', '20')); + it('should support plus 3', () => test('{{ 183.357 | plus: 12 }}', '195.357')); it('should support prepend', function() { return test('{% assign url = "liquidmarkup.com" %}' + @@ -150,26 +150,26 @@ describe('filters', function() { '.moT rojaM ot lortnoc dnuorG'); }); - it('should support round 1', function() { return test('{{1.2|round}}', '1'); }); - it('should support round 2', function() { return test('{{2.7|round}}', '3'); }); - it('should support round 3', function() { return test('{{183.357|round: 2}}', '183.36'); }); + it('should support round 1', () => test('{{1.2|round}}', '1')); + it('should support round 2', () => test('{{2.7|round}}', '3')); + it('should support round 3', () => test('{{183.357|round: 2}}', '183.36')); it('should support rstrip', function() { return test('{{ " So much room for activities! " | rstrip }}', ' So much room for activities!'); }); - it('should support size 1', function() { return test('{{ "Ground control to Major Tom." | size }}', '28'); }); + it('should support size 1', () => test('{{ "Ground control to Major Tom." | size }}', '28')); it('should support size 2', function() { return test('{% assign my_array = "apples, oranges, peaches, plums"' + ' | split: ", " %}{{ my_array | size }}', '4'); }); - it('should support slice 1', function() { return test('{{ "Liquid" | slice: 0 }}', 'L'); }); - it('should support slice 2', function() { return test('{{ "Liquid" | slice: 2 }}', 'q'); }); - it('should support slice 3', function() { return test('{{ "Liquid" | slice: 2, 5 }}', 'quid'); }); - it('should support slice 4', function() { return test('{{ "Liquid" | slice: -3, 2 }}', 'ui'); }); + it('should support slice 1', () => test('{{ "Liquid" | slice: 0 }}', 'L')); + it('should support slice 2', () => test('{{ "Liquid" | slice: 2 }}', 'q')); + it('should support slice 3', () => test('{{ "Liquid" | slice: 2, 5 }}', 'quid')); + it('should support slice 4', () => test('{{ "Liquid" | slice: -3, 2 }}', 'ui')); it('should support sort', function() { return test('{% assign my_array = "zebra, octopus, giraffe, Sally Snake"' + @@ -206,9 +206,9 @@ describe('filters', function() { 'Hellothere'); }); - it('should support times 1', function() { return test('{{ 3 | times: 2 }}', '6'); }); - it('should support times 2', function() { return test('{{ 24 | times: 7 }}', '168'); }); - it('should support times 3', function() { return test('{{ 183.357 | times: 12 }}', '2200.284'); }); + it('should support times 1', () => test('{{ 3 | times: 2 }}', '6')); + it('should support times 2', () => test('{{ 24 | times: 7 }}', '168')); + it('should support times 3', () => test('{{ 183.357 | times: 12 }}', '2200.284')); it('should support truncate 1', function() { return test('{{ "Ground control to Major Tom." | truncate: 20 }}', @@ -250,10 +250,8 @@ describe('filters', function() { 'ants, bugs, bees'); }); - it('should support upcase', function() { - return test('{{ "Parker Moore" | upcase }}', 'PARKER MOORE'); - }); + it('should support upcase', () => test('{{ "Parker Moore" | upcase }}', 'PARKER MOORE')); - it('should support url_encode 1', function() { return test('{{ "john@liquid.com" | url_encode }}', 'john%40liquid.com'); }); - it('should support url_encode 2', function() { return test('{{ "Tetsuro Takara" | url_encode }}', 'Tetsuro%20Takara'); }); + it('should support url_encode 1', () => test('{{ "john@liquid.com" | url_encode }}', 'john%40liquid.com')); + it('should support url_encode 2', () => test('{{ "Tetsuro Takara" | url_encode }}', 'Tetsuro%20Takara')); }); diff --git a/test/scope.js b/test/scope.js index 3fa1ee7b7..b3a11acdc 100644 --- a/test/scope.js +++ b/test/scope.js @@ -24,6 +24,8 @@ describe('scope', function() { } 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); }); it('should throw undefined in strict mode', function() { @@ -38,8 +40,6 @@ describe('scope', function() { it('should get all property', function() { scope.get().should.deep.equal(ctx); - expect(scope.get('')).to.equal(undefined); - expect(scope.get(false)).to.equal(undefined); }); it('should set property', function() {