strategies.js 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413
  1. 'use strict';
  2. var typeName = require('type-name');
  3. var forEach = require('core-js/library/fn/array/for-each');
  4. var arrayFilter = require('core-js/library/fn/array/filter');
  5. var reduceRight = require('core-js/library/fn/array/reduce-right');
  6. var indexOf = require('core-js/library/fn/array/index-of');
  7. var slice = Array.prototype.slice;
  8. var END = {};
  9. var ITERATE = {};
  10. // arguments should end with end or iterate
  11. function compose () {
  12. var filters = slice.apply(arguments);
  13. return reduceRight(filters, function(right, left) {
  14. return left(right);
  15. });
  16. }
  17. // skip children
  18. function end () {
  19. return function (acc, x) {
  20. acc.context.keys = [];
  21. return END;
  22. };
  23. }
  24. // iterate children
  25. function iterate () {
  26. return function (acc, x) {
  27. return ITERATE;
  28. };
  29. }
  30. function filter (predicate) {
  31. return function (next) {
  32. return function (acc, x) {
  33. var toBeIterated;
  34. var isIteratingArray = (typeName(x) === 'Array');
  35. if (typeName(predicate) === 'function') {
  36. toBeIterated = [];
  37. forEach(acc.context.keys, function (key) {
  38. var indexOrKey = isIteratingArray ? parseInt(key, 10) : key;
  39. var kvp = {
  40. key: indexOrKey,
  41. value: x[key]
  42. };
  43. var decision = predicate(kvp);
  44. if (decision) {
  45. toBeIterated.push(key);
  46. }
  47. if (typeName(decision) === 'number') {
  48. truncateByKey(decision, key, acc);
  49. }
  50. if (typeName(decision) === 'function') {
  51. customizeStrategyForKey(decision, key, acc);
  52. }
  53. });
  54. acc.context.keys = toBeIterated;
  55. }
  56. return next(acc, x);
  57. };
  58. };
  59. }
  60. function customizeStrategyForKey (strategy, key, acc) {
  61. acc.handlers[currentPath(key, acc)] = strategy;
  62. }
  63. function truncateByKey (size, key, acc) {
  64. acc.handlers[currentPath(key, acc)] = size;
  65. }
  66. function currentPath (key, acc) {
  67. var pathToCurrentNode = [''].concat(acc.context.path);
  68. if (typeName(key) !== 'undefined') {
  69. pathToCurrentNode.push(key);
  70. }
  71. return pathToCurrentNode.join('/');
  72. }
  73. function allowedKeys (orderedWhiteList) {
  74. return function (next) {
  75. return function (acc, x) {
  76. var isIteratingArray = (typeName(x) === 'Array');
  77. if (!isIteratingArray && typeName(orderedWhiteList) === 'Array') {
  78. acc.context.keys = arrayFilter(orderedWhiteList, function (propKey) {
  79. return x.hasOwnProperty(propKey);
  80. });
  81. }
  82. return next(acc, x);
  83. };
  84. };
  85. }
  86. function safeKeys () {
  87. return function (next) {
  88. return function (acc, x) {
  89. if (typeName(x) !== 'Array') {
  90. acc.context.keys = arrayFilter(acc.context.keys, function (propKey) {
  91. // Error handling for unsafe property access.
  92. // For example, on PhantomJS,
  93. // accessing HTMLInputElement.selectionEnd causes TypeError
  94. try {
  95. var val = x[propKey];
  96. return true;
  97. } catch (e) {
  98. // skip unsafe key
  99. return false;
  100. }
  101. });
  102. }
  103. return next(acc, x);
  104. };
  105. };
  106. }
  107. function arrayIndicesToKeys () {
  108. return function (next) {
  109. return function (acc, x) {
  110. if (typeName(x) === 'Array' && 0 < x.length) {
  111. var indices = Array(x.length);
  112. for(var i = 0; i < x.length; i += 1) {
  113. indices[i] = String(i); // traverse uses strings as keys
  114. }
  115. acc.context.keys = indices;
  116. }
  117. return next(acc, x);
  118. };
  119. };
  120. }
  121. function when (guard, then) {
  122. return function (next) {
  123. return function (acc, x) {
  124. var kvp = {
  125. key: acc.context.key,
  126. value: x
  127. };
  128. if (guard(kvp, acc)) {
  129. return then(acc, x);
  130. }
  131. return next(acc, x);
  132. };
  133. };
  134. }
  135. function truncate (size) {
  136. return function (next) {
  137. return function (acc, x) {
  138. var orig = acc.push;
  139. var ret;
  140. acc.push = function (str) {
  141. var savings = str.length - size;
  142. var truncated;
  143. if (savings <= size) {
  144. orig.call(acc, str);
  145. } else {
  146. truncated = str.substring(0, size);
  147. orig.call(acc, truncated + acc.options.snip);
  148. }
  149. };
  150. ret = next(acc, x);
  151. acc.push = orig;
  152. return ret;
  153. };
  154. };
  155. }
  156. function constructorName () {
  157. return function (next) {
  158. return function (acc, x) {
  159. var name = acc.options.typeFun(x);
  160. if (name === '') {
  161. name = acc.options.anonymous;
  162. }
  163. acc.push(name);
  164. return next(acc, x);
  165. };
  166. };
  167. }
  168. function always (str) {
  169. return function (next) {
  170. return function (acc, x) {
  171. acc.push(str);
  172. return next(acc, x);
  173. };
  174. };
  175. }
  176. function optionValue (key) {
  177. return function (next) {
  178. return function (acc, x) {
  179. acc.push(acc.options[key]);
  180. return next(acc, x);
  181. };
  182. };
  183. }
  184. function json (replacer) {
  185. return function (next) {
  186. return function (acc, x) {
  187. acc.push(JSON.stringify(x, replacer));
  188. return next(acc, x);
  189. };
  190. };
  191. }
  192. function toStr () {
  193. return function (next) {
  194. return function (acc, x) {
  195. acc.push(x.toString());
  196. return next(acc, x);
  197. };
  198. };
  199. }
  200. function decorateArray () {
  201. return function (next) {
  202. return function (acc, x) {
  203. acc.context.before(function (node) {
  204. acc.push('[');
  205. });
  206. acc.context.after(function (node) {
  207. afterAllChildren(this, acc.push, acc.options);
  208. acc.push(']');
  209. });
  210. acc.context.pre(function (val, key) {
  211. beforeEachChild(this, acc.push, acc.options);
  212. });
  213. acc.context.post(function (childContext) {
  214. afterEachChild(childContext, acc.push);
  215. });
  216. return next(acc, x);
  217. };
  218. };
  219. }
  220. function decorateObject () {
  221. return function (next) {
  222. return function (acc, x) {
  223. acc.context.before(function (node) {
  224. acc.push('{');
  225. });
  226. acc.context.after(function (node) {
  227. afterAllChildren(this, acc.push, acc.options);
  228. acc.push('}');
  229. });
  230. acc.context.pre(function (val, key) {
  231. beforeEachChild(this, acc.push, acc.options);
  232. acc.push(sanitizeKey(key) + (acc.options.indent ? ': ' : ':'));
  233. });
  234. acc.context.post(function (childContext) {
  235. afterEachChild(childContext, acc.push);
  236. });
  237. return next(acc, x);
  238. };
  239. };
  240. }
  241. function sanitizeKey (key) {
  242. return /^[A-Za-z_]+$/.test(key) ? key : JSON.stringify(key);
  243. }
  244. function afterAllChildren (context, push, options) {
  245. if (options.indent && 0 < context.keys.length) {
  246. push(options.lineSeparator);
  247. for(var i = 0; i < context.level; i += 1) { // indent level - 1
  248. push(options.indent);
  249. }
  250. }
  251. }
  252. function beforeEachChild (context, push, options) {
  253. if (options.indent) {
  254. push(options.lineSeparator);
  255. for(var i = 0; i <= context.level; i += 1) {
  256. push(options.indent);
  257. }
  258. }
  259. }
  260. function afterEachChild (childContext, push) {
  261. if (!childContext.isLast) {
  262. push(',');
  263. }
  264. }
  265. function nan (kvp, acc) {
  266. return kvp.value !== kvp.value;
  267. }
  268. function positiveInfinity (kvp, acc) {
  269. return !isFinite(kvp.value) && kvp.value === Infinity;
  270. }
  271. function negativeInfinity (kvp, acc) {
  272. return !isFinite(kvp.value) && kvp.value !== Infinity;
  273. }
  274. function circular (kvp, acc) {
  275. return acc.context.circular;
  276. }
  277. function maxDepth (kvp, acc) {
  278. return (acc.options.maxDepth && acc.options.maxDepth <= acc.context.level);
  279. }
  280. var prune = compose(
  281. always('#'),
  282. constructorName(),
  283. always('#'),
  284. end()
  285. );
  286. var omitNaN = when(nan, compose(
  287. always('NaN'),
  288. end()
  289. ));
  290. var omitPositiveInfinity = when(positiveInfinity, compose(
  291. always('Infinity'),
  292. end()
  293. ));
  294. var omitNegativeInfinity = when(negativeInfinity, compose(
  295. always('-Infinity'),
  296. end()
  297. ));
  298. var omitCircular = when(circular, compose(
  299. optionValue('circular'),
  300. end()
  301. ));
  302. var omitMaxDepth = when(maxDepth, prune);
  303. module.exports = {
  304. filters: {
  305. always: always,
  306. optionValue: optionValue,
  307. constructorName: constructorName,
  308. json: json,
  309. toStr: toStr,
  310. prune: prune,
  311. truncate: truncate,
  312. decorateArray: decorateArray,
  313. decorateObject: decorateObject
  314. },
  315. flow: {
  316. compose: compose,
  317. when: when,
  318. allowedKeys: allowedKeys,
  319. safeKeys: safeKeys,
  320. arrayIndicesToKeys: arrayIndicesToKeys,
  321. filter: filter,
  322. iterate: iterate,
  323. end: end
  324. },
  325. symbols: {
  326. END: END,
  327. ITERATE: ITERATE
  328. },
  329. always: function (str) {
  330. return compose(always(str), end());
  331. },
  332. json: function () {
  333. return compose(json(), end());
  334. },
  335. toStr: function () {
  336. return compose(toStr(), end());
  337. },
  338. prune: function () {
  339. return prune;
  340. },
  341. number: function () {
  342. return compose(
  343. omitNaN,
  344. omitPositiveInfinity,
  345. omitNegativeInfinity,
  346. json(),
  347. end()
  348. );
  349. },
  350. newLike: function () {
  351. return compose(
  352. always('new '),
  353. constructorName(),
  354. always('('),
  355. json(),
  356. always(')'),
  357. end()
  358. );
  359. },
  360. array: function (predicate) {
  361. return compose(
  362. omitCircular,
  363. omitMaxDepth,
  364. decorateArray(),
  365. arrayIndicesToKeys(),
  366. filter(predicate),
  367. iterate()
  368. );
  369. },
  370. object: function (predicate, orderedWhiteList) {
  371. return compose(
  372. omitCircular,
  373. omitMaxDepth,
  374. constructorName(),
  375. decorateObject(),
  376. allowedKeys(orderedWhiteList),
  377. safeKeys(),
  378. filter(predicate),
  379. iterate()
  380. );
  381. }
  382. };