'use strict'; var typeName = require('type-name'); var forEach = require('core-js/library/fn/array/for-each'); var arrayFilter = require('core-js/library/fn/array/filter'); var reduceRight = require('core-js/library/fn/array/reduce-right'); var indexOf = require('core-js/library/fn/array/index-of'); var slice = Array.prototype.slice; var END = {}; var ITERATE = {}; // arguments should end with end or iterate function compose () { var filters = slice.apply(arguments); return reduceRight(filters, function(right, left) { return left(right); }); } // skip children function end () { return function (acc, x) { acc.context.keys = []; return END; }; } // iterate children function iterate () { return function (acc, x) { return ITERATE; }; } function filter (predicate) { return function (next) { return function (acc, x) { var toBeIterated; var isIteratingArray = (typeName(x) === 'Array'); if (typeName(predicate) === 'function') { toBeIterated = []; forEach(acc.context.keys, function (key) { var indexOrKey = isIteratingArray ? parseInt(key, 10) : key; var kvp = { key: indexOrKey, value: x[key] }; var decision = predicate(kvp); if (decision) { toBeIterated.push(key); } if (typeName(decision) === 'number') { truncateByKey(decision, key, acc); } if (typeName(decision) === 'function') { customizeStrategyForKey(decision, key, acc); } }); acc.context.keys = toBeIterated; } return next(acc, x); }; }; } function customizeStrategyForKey (strategy, key, acc) { acc.handlers[currentPath(key, acc)] = strategy; } function truncateByKey (size, key, acc) { acc.handlers[currentPath(key, acc)] = size; } function currentPath (key, acc) { var pathToCurrentNode = [''].concat(acc.context.path); if (typeName(key) !== 'undefined') { pathToCurrentNode.push(key); } return pathToCurrentNode.join('/'); } function allowedKeys (orderedWhiteList) { return function (next) { return function (acc, x) { var isIteratingArray = (typeName(x) === 'Array'); if (!isIteratingArray && typeName(orderedWhiteList) === 'Array') { acc.context.keys = arrayFilter(orderedWhiteList, function (propKey) { return x.hasOwnProperty(propKey); }); } return next(acc, x); }; }; } function safeKeys () { return function (next) { return function (acc, x) { if (typeName(x) !== 'Array') { acc.context.keys = arrayFilter(acc.context.keys, function (propKey) { // Error handling for unsafe property access. // For example, on PhantomJS, // accessing HTMLInputElement.selectionEnd causes TypeError try { var val = x[propKey]; return true; } catch (e) { // skip unsafe key return false; } }); } return next(acc, x); }; }; } function arrayIndicesToKeys () { return function (next) { return function (acc, x) { if (typeName(x) === 'Array' && 0 < x.length) { var indices = Array(x.length); for(var i = 0; i < x.length; i += 1) { indices[i] = String(i); // traverse uses strings as keys } acc.context.keys = indices; } return next(acc, x); }; }; } function when (guard, then) { return function (next) { return function (acc, x) { var kvp = { key: acc.context.key, value: x }; if (guard(kvp, acc)) { return then(acc, x); } return next(acc, x); }; }; } function truncate (size) { return function (next) { return function (acc, x) { var orig = acc.push; var ret; acc.push = function (str) { var savings = str.length - size; var truncated; if (savings <= size) { orig.call(acc, str); } else { truncated = str.substring(0, size); orig.call(acc, truncated + acc.options.snip); } }; ret = next(acc, x); acc.push = orig; return ret; }; }; } function constructorName () { return function (next) { return function (acc, x) { var name = acc.options.typeFun(x); if (name === '') { name = acc.options.anonymous; } acc.push(name); return next(acc, x); }; }; } function always (str) { return function (next) { return function (acc, x) { acc.push(str); return next(acc, x); }; }; } function optionValue (key) { return function (next) { return function (acc, x) { acc.push(acc.options[key]); return next(acc, x); }; }; } function json (replacer) { return function (next) { return function (acc, x) { acc.push(JSON.stringify(x, replacer)); return next(acc, x); }; }; } function toStr () { return function (next) { return function (acc, x) { acc.push(x.toString()); return next(acc, x); }; }; } function decorateArray () { return function (next) { return function (acc, x) { acc.context.before(function (node) { acc.push('['); }); acc.context.after(function (node) { afterAllChildren(this, acc.push, acc.options); acc.push(']'); }); acc.context.pre(function (val, key) { beforeEachChild(this, acc.push, acc.options); }); acc.context.post(function (childContext) { afterEachChild(childContext, acc.push); }); return next(acc, x); }; }; } function decorateObject () { return function (next) { return function (acc, x) { acc.context.before(function (node) { acc.push('{'); }); acc.context.after(function (node) { afterAllChildren(this, acc.push, acc.options); acc.push('}'); }); acc.context.pre(function (val, key) { beforeEachChild(this, acc.push, acc.options); acc.push(sanitizeKey(key) + (acc.options.indent ? ': ' : ':')); }); acc.context.post(function (childContext) { afterEachChild(childContext, acc.push); }); return next(acc, x); }; }; } function sanitizeKey (key) { return /^[A-Za-z_]+$/.test(key) ? key : JSON.stringify(key); } function afterAllChildren (context, push, options) { if (options.indent && 0 < context.keys.length) { push(options.lineSeparator); for(var i = 0; i < context.level; i += 1) { // indent level - 1 push(options.indent); } } } function beforeEachChild (context, push, options) { if (options.indent) { push(options.lineSeparator); for(var i = 0; i <= context.level; i += 1) { push(options.indent); } } } function afterEachChild (childContext, push) { if (!childContext.isLast) { push(','); } } function nan (kvp, acc) { return kvp.value !== kvp.value; } function positiveInfinity (kvp, acc) { return !isFinite(kvp.value) && kvp.value === Infinity; } function negativeInfinity (kvp, acc) { return !isFinite(kvp.value) && kvp.value !== Infinity; } function circular (kvp, acc) { return acc.context.circular; } function maxDepth (kvp, acc) { return (acc.options.maxDepth && acc.options.maxDepth <= acc.context.level); } var prune = compose( always('#'), constructorName(), always('#'), end() ); var omitNaN = when(nan, compose( always('NaN'), end() )); var omitPositiveInfinity = when(positiveInfinity, compose( always('Infinity'), end() )); var omitNegativeInfinity = when(negativeInfinity, compose( always('-Infinity'), end() )); var omitCircular = when(circular, compose( optionValue('circular'), end() )); var omitMaxDepth = when(maxDepth, prune); module.exports = { filters: { always: always, optionValue: optionValue, constructorName: constructorName, json: json, toStr: toStr, prune: prune, truncate: truncate, decorateArray: decorateArray, decorateObject: decorateObject }, flow: { compose: compose, when: when, allowedKeys: allowedKeys, safeKeys: safeKeys, arrayIndicesToKeys: arrayIndicesToKeys, filter: filter, iterate: iterate, end: end }, symbols: { END: END, ITERATE: ITERATE }, always: function (str) { return compose(always(str), end()); }, json: function () { return compose(json(), end()); }, toStr: function () { return compose(toStr(), end()); }, prune: function () { return prune; }, number: function () { return compose( omitNaN, omitPositiveInfinity, omitNegativeInfinity, json(), end() ); }, newLike: function () { return compose( always('new '), constructorName(), always('('), json(), always(')'), end() ); }, array: function (predicate) { return compose( omitCircular, omitMaxDepth, decorateArray(), arrayIndicesToKeys(), filter(predicate), iterate() ); }, object: function (predicate, orderedWhiteList) { return compose( omitCircular, omitMaxDepth, constructorName(), decorateObject(), allowedKeys(orderedWhiteList), safeKeys(), filter(predicate), iterate() ); } };