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 += '| ';
+ }).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('')); // 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 += '
';
+ 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() {