UNPKG

21.7 kBJavaScriptView Raw
1'use strict';
2
3const crypto = require('crypto');
4
5//////////
6
7const ansiPattern = [
8 '[\\u001B\\u009B][[\\]()#;?]*(?:(?:(?:[a-zA-Z\\d]*(?:;[-a-zA-Z\\d\\/#&.:=?%@~_]*)*)?\\u0007)',
9 '(?:(?:\\d{1,4}(?:;\\d{0,4})*)?[\\dA-PR-TZcf-ntqry=><~]))',
10].join('|');
11
12const ansiRegExp = new RegExp(ansiPattern, 'g');
13
14const arrayPartRegExp = /([^]+)\[(\d+)\]$/;
15
16const noop = () => undefined;
17
18//////////
19
20function callback (next, nextTick = false) {
21 if (typeof next === 'function') {
22 if (nextTick) {
23 return function (...args) {
24 process.nextTick(() => {
25 next(...args);
26 });
27 };
28 }
29 return next;
30 }
31 return noop;
32}
33
34function camelize (string) {
35 return string.replace(/^.*?:+/, '').
36 replace(/[-:]/g, ' ').
37 replace(/(?:^\w|[A-Z]|\b\w|\s+)/g, (match, index) => {
38 if (Number(match) === 0) {
39 return '';
40 }
41 return index === 0 ? match.toLowerCase() : match.toUpperCase();
42 });
43}
44
45function debounce (func, wait) {
46 let timeout;
47
48 return (...args) => {
49 if (timeout) {
50 clearTimeout(timeout);
51 }
52
53 timeout = setTimeout(() => {
54 func(...args);
55 }, wait || 500);
56 };
57}
58
59function decrypt (text, secret, algorithm = 'aes-256-cbc') {
60 let decrypted = null;
61 secret = secret.replace(/-/g, '').substring(0, 32);
62 const textParts = text.split(':');
63 const iv = Buffer.from(textParts.shift(), 'hex');
64 const encryptedText = Buffer.from(textParts.join(':'), 'hex');
65 const decipher = crypto.createDecipheriv(algorithm, secret, iv);
66 decrypted = decipher.update(encryptedText);
67
68 decrypted = Buffer.concat([ decrypted, decipher.final() ]).toString();
69
70 return decrypted;
71}
72
73function deepClone (object, seen = new WeakMap()) {
74 // Primitives (treat Functions as primitives)
75 if (Object(object) !== object || object instanceof Function) {
76 return object;
77 }
78
79 // Cyclic references
80 if (seen.has(object)) {
81 return seen.get(object);
82 }
83
84 let result;
85 if (object instanceof Buffer) {
86 result = Buffer.from(object);
87 } else if (object instanceof Date) {
88 result = new Date(object);
89 } else if (object instanceof RegExp) {
90 result = new RegExp(object.source, object.flags);
91 } else if (object.constructor) {
92 result = new object.constructor();
93 } else {
94 result = Object.create(null);
95 }
96
97 seen.set(object, result);
98
99 if (object instanceof Buffer) {
100 return result;
101 } else if (object instanceof Map) {
102 object.forEach((value, key) => result.set(key, deepClone(value, seen)));
103 } else if (object instanceof Set) {
104 object.forEach(value => result.add(deepClone(value, seen)));
105 } else {
106 for (const key in object) {
107 result[key] = deepClone(object[key], seen);
108 }
109 }
110
111 return result;
112}
113
114function deepEqual (actual, expected) {
115 if (actual === null || actual === undefined ||
116 expected === null || expected === undefined) {
117 return actual === expected;
118 }
119
120 if (actual.constructor !== expected.constructor) {
121 return false;
122 }
123
124 if (actual instanceof Function) {
125 return actual === expected;
126 }
127
128 if (actual instanceof RegExp) {
129 return actual === expected;
130 }
131
132 if (actual instanceof Set) {
133 if (actual.size !== expected.size) {
134 return false;
135 }
136
137 for (const entry of actual) {
138 if (!expected.has(entry)) {
139 return false;
140 }
141 }
142
143 return true;
144 }
145
146 if (actual instanceof Map) {
147 if (actual.size !== expected.size) {
148 return false;
149 }
150
151 for (const [ key, value ] of actual) {
152 if (!expected.has(key) &&
153 deepEqual(value, expected.get(key))) {
154 return false;
155 }
156 }
157
158 return true;
159 }
160
161 if (actual === expected || actual.valueOf() === expected.valueOf()) {
162 return true;
163 }
164
165 if (Array.isArray(actual) && actual.length !== expected.length) {
166 return false;
167 }
168
169 if (actual instanceof Date) {
170 return false;
171 }
172
173 if (!(actual instanceof Object)) {
174 return false;
175 }
176
177 if (!(expected instanceof Object)) {
178 return false;
179 }
180
181 const properties = Object.keys(actual);
182 return Object.keys(expected).every((i) => properties.indexOf(i) !== -1) && properties.every((i) => deepEqual(actual[i], expected[i]));
183}
184
185function distinct (array, selector) {
186 if (typeof selector !== 'function') {
187 return Array.from(new Set(array));
188 }
189
190 const items = new Set();
191
192 return array.filter(item => {
193 if (items.has(selector(item))) {
194 return false;
195 }
196 items.add(selector(item));
197 return true;
198 });
199}
200
201function dividePath (path, delimiter = /[.]/) {
202 const parts = [];
203
204 for (const part of path.trim().split(delimiter)) {
205 if (arrayPartRegExp.test(part)) {
206 const match = part.match(arrayPartRegExp);
207 const subpart = match[1];
208 const index = Number(match[2]);
209 parts.push(subpart, index);
210 } else {
211 parts.push(part);
212 }
213 }
214
215 return parts;
216}
217
218function duration (diff, {
219 units = 'd h m', separator = ', ', empty = 'less than a minute', brief = false,
220} = {}) {
221 const days = Math.floor(diff / 86400000);
222 diff = diff % 86400000;
223 const hours = Math.floor(diff / 3600000);
224 diff = diff % 3600000;
225 const minutes = Math.floor(diff / 60000);
226 diff = diff % 60000;
227 const seconds = Math.floor(diff / 1000);
228 const millis = diff % 1000;
229
230 if (typeof units === 'string') {
231 units = units.split(/[\s,]/);
232 }
233
234 const parts = [];
235 if (days > 0 && units.includes('d')) {
236 if (brief) {
237 parts.push(`${ days }d`);
238 } else if (days === 1) {
239 parts.push(`${ days } day`);
240 } else {
241 parts.push(`${ days } days`);
242 }
243 }
244 if (hours > 0 && (units.includes('h') ||
245 units.includes('h?') && parts.length === 0)) {
246 if (brief) {
247 parts.push(`${ hours }h`);
248 } else if (hours === 1) {
249 parts.push(`${ hours } hour`);
250 } else {
251 parts.push(`${ hours } hours`);
252 }
253 }
254 if (minutes > 0 && (units.includes('m') ||
255 units.includes('m?') && parts.length === 0)) {
256 if (brief) {
257 parts.push(`${ minutes }m`);
258 } else if (minutes === 1) {
259 parts.push(`${ minutes } minute`);
260 } else {
261 parts.push(`${ minutes } minutes`);
262 }
263 }
264
265 if (seconds > 0 && (units.includes('s') ||
266 units.includes('s?') && parts.length === 0)) {
267 if (brief) {
268 parts.push(`${ seconds }s`);
269 } else if (seconds === 1) {
270 parts.push(`${ seconds } second`);
271 } else {
272 parts.push(`${ seconds } seconds`);
273 }
274 }
275
276 if (millis > 0 && (units.includes('ms') ||
277 units.includes('ms?') && parts.length === 0)) {
278 if (brief) {
279 parts.push(`${ millis }ms`);
280 } else if (millis === 1) {
281 parts.push(`${ millis } millisecond`);
282 } else {
283 parts.push(`${ millis } milliseconds`);
284 }
285 }
286
287 if (parts.length) {
288 return parts.join(separator);
289 }
290 return empty;
291}
292
293function encrypt (text, secret, algorithm = 'aes-256-cbc', ivLength = 16) {
294 let encrypted = null;
295 secret = secret.replace(/-/g, '').substring(0, 32);
296 const iv = crypto.randomBytes(ivLength);
297 const cipher = crypto.createCipheriv(algorithm, secret, iv);
298 encrypted = cipher.update(text);
299 encrypted = Buffer.concat([ encrypted, cipher.final() ]);
300 encrypted = `${ iv.toString('hex') }:${ encrypted.toString('hex') }`;
301
302 return encrypted;
303}
304
305function expand (container, object = {}) {
306 for (const key in container) {
307 const parts = key.split(/\./);
308 const property = parts.pop();
309
310 let chunk = object;
311 for (const part of parts) {
312 if (!chunk[part]) {
313 chunk[part] = {};
314 }
315
316 chunk = chunk[part];
317 }
318
319 if (property.endsWith('$type')) {
320 const name = property.replace(/\$type$/, '');
321 if (container[key] === 'Object') {
322 chunk[name] = {};
323 } else if (container[key] === 'Array') {
324 chunk[name] = [];
325 } else {
326 // Unknown type
327 }
328 } else {
329 chunk[property] = container[key];
330 }
331 }
332 return object;
333}
334
335function filter (object, check, include = true, path) {
336 if (typeof object !== 'object') {
337 return object;
338 }
339
340 function test (fullpath) {
341 if (Array.isArray(check)) {
342 if (check.includes(fullpath)) {
343 return include;
344 }
345 return !include;
346 } else if (check instanceof Map || check instanceof Set ||
347 check instanceof WeakMap || check instanceof WeakSet) {
348 if (check.has(fullpath)) {
349 return include;
350 }
351 return !include;
352 } else if (typeof check === 'function') {
353 if (check(fullpath)) {
354 return include;
355 } return !include;
356 } else if (check instanceof RegExp) {
357 if (check.test(fullpath)) {
358 return include;
359 }
360 return !include;
361 } else if (typeof check === 'object') {
362 if (resolve(check, fullpath)) {
363 return include;
364 }
365 return !include;
366 }
367 return !include;
368 }
369
370 include = Boolean(include);
371
372 const clone = Array.isArray(object) ? [ ] : { };
373 for (const prop in object) {
374 const fullpath = path ? `${ path }.${ prop }` : prop;
375
376 let value = object[prop];
377
378 if (test(fullpath)) {
379 if (typeof value === 'object') {
380 value = filter(value, check, include, fullpath);
381 if (Array.isArray(value) && value.length !== 0 ||
382 Object.keys(value).length !== 0) {
383 clone[prop] = value;
384 }
385 } else {
386 clone[prop] = value;
387 }
388 }
389 }
390
391 return clone;
392}
393
394function flatten (object, {
395 container = {}, delimiter = '.', prefix = '', types = true,
396} = {}) {
397 if (typeof object !== 'object') {
398 container[prefix] = object;
399 return container;
400 }
401
402 if (prefix.length && prefix !== delimiter) {
403 prefix += delimiter;
404 }
405
406 for (const key in object) {
407 const pathKey = prefix + key;
408
409 if (Array.isArray(object[key])) {
410 if (types) {
411 container[`${ pathKey }$type`] = 'Array';
412 }
413
414 const array = object[key];
415 for (let i = 0; i < array.length; i++) {
416 flatten(array[i], {
417 container,
418 delimiter,
419 prefix: `${ pathKey }${ delimiter }${ i }`,
420 types,
421 });
422 }
423 } else if (typeof object[key] === 'object' && object[key] !== null) {
424 if (types) {
425 container[`${ pathKey }$type`] = 'Object';
426 }
427 flatten(object[key], {
428 container,
429 delimiter,
430 prefix: pathKey,
431 types,
432 });
433 } else {
434 container[ pathKey ] = object[key];
435 }
436 }
437 return container;
438}
439
440function formatBytes (bytes, decimals = 2) {
441 if (bytes === 0) {
442 return '0 Bytes';
443 }
444 const kilobyte = 1024;
445 const sizes = [ 'Bytes', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB' ];
446 const index = Math.floor(Math.log(bytes) / Math.log(kilobyte));
447 return `${ parseFloat((bytes / Math.pow(kilobyte, index)).toFixed(decimals)) } ${ sizes[index] }`;
448}
449
450function formatNumber (value, { numeral = false } = {}) {
451 value = Number(value);
452 if (Number.isNaN(value) || Number === Infinity) {
453 return value.toString();
454 }
455
456 const words = [
457 'zero', 'one', 'two', 'three', 'four',
458 'five', 'six', 'seven', 'eight', 'nine', 'ten',
459 ];
460
461 if (Number.isInteger(value)) {
462 if (words[value] && !numeral) {
463 return words[value];
464 }
465 } else {
466 value = precisionRound(value);
467 }
468
469 return value.toString().
470 split(/(?=(?:\d{3})+(?:\.|$))/g).
471 join( ',' );
472}
473
474function functionType (func) {
475 const flags = {
476 function: func instanceof Function,
477 name: undefined,
478 native: false,
479 bound: false,
480 plain: false,
481 arrow: false,
482 };
483
484 if (flags.function) {
485 flags.name = func.name || '(anonymous)';
486 flags.native = func.toString().trim().
487 endsWith('() { [native code] }');
488 flags.bound = flags.native && flags.name.startsWith('bound ');
489 flags.plain = !flags.native && func.hasOwnProperty('prototype');
490 flags.arrow = !(flags.native || flags.plain);
491 }
492
493 return flags;
494}
495
496const reRegExpChar = /[\\^$.*+?()[\]{}|]/g;
497const isNativeRegExp = RegExp(`^${
498 Function.prototype.toString.call(Object.prototype.hasOwnProperty).
499 replace(reRegExpChar, '\\$&').
500 replace(/hasOwnProperty|(function).*?(?=\\\()| for .+?(?=\\\])/g, '$1.*?')
501}$`);
502
503function isNative (value) {
504 return isObject(value) && isNativeRegExp.test(value);
505}
506
507function isNumber (value) {
508 if (typeof value === 'number') {
509 return value - value === 0;
510 }
511
512 if (typeof value === 'string' && value.trim() !== '') {
513 return Number.isFinite(Number(value));
514 }
515
516 return false;
517}
518
519function isObject (value) {
520 const type = typeof value;
521 return value !== null && (type === 'object' || type === 'function');
522}
523
524function isPrimitive (value) {
525 if (typeof value === 'object') {
526 return value === null;
527 }
528 return typeof value !== 'function';
529}
530
531function merge (objectA, objectB, createNew = false, seen) {
532 if (createNew) {
533 objectA = deepClone(objectA);
534 }
535
536 seen = new Set(seen);
537
538 const keys = Object.getOwnPropertyNames(objectB);
539 for (const key of keys) {
540 if (objectB[key] === null || Array.isArray(objectB[key]) || objectB[key] instanceof Buffer ||
541 objectB[key] instanceof Date || objectB[key] instanceof Map || objectB[key] instanceof Set ||
542 objectB[key] instanceof RegExp) {
543 if (createNew) {
544 objectA[key] = deepClone(objectB[key]);
545 } else {
546 objectA[key] = objectB[key];
547 }
548 } else if (typeof objectB[key] === 'object' && !seen.has(objectB[key])) {
549 if (typeof objectA[key] === 'object') {
550 objectA[key] = merge(objectA[key], objectB[key], createNew, seen);
551 } else if (createNew) {
552 objectA[key] = deepClone(objectB[key]);
553 } else {
554 objectA[key] = objectB[key];
555 }
556
557 seen.add(objectB[key]);
558 } else if (createNew) {
559 objectA[key] = deepClone(objectB[key]);
560 } else {
561 objectA[key] = objectB[key];
562 }
563 }
564 return objectA;
565}
566
567function milliseconds (value) {
568 if (typeof value === 'number') {
569 return value;
570 } else if (typeof value === 'string') {
571 let millis = 0;
572 value.replace(/(\d+\.?\d*)\s*([mshd]+)/g, (match, time, unit) => {
573 time = Number(time) || 0;
574 if (unit === 'ms') {
575 time *= 1;
576 } else if (unit === 's') {
577 time *= 1000;
578 } else if (unit === 'm') {
579 time *= 1000 * 60;
580 } else if (unit === 'h') {
581 time *= 1000 * 60 * 60;
582 } else if (unit === 'd') {
583 time *= 1000 * 60 * 60 * 24;
584 }
585
586 millis += time;
587 });
588
589 return Math.ceil(millis);
590 }
591 return 0;
592}
593
594function once (func) {
595 if (typeof func !== 'function') {
596 return noop;
597 }
598
599 let invoked = false;
600
601 return (...args) => {
602 if (!invoked) {
603 invoked = true;
604 return func(...args);
605 }
606 return false;
607 };
608}
609
610function ordinal (value) {
611 value = Number(value);
612
613 const tens = value % 10;
614 const hundreds = value % 100;
615
616 if (tens === 1 && hundreds !== 11) {
617 return `${ value }st`;
618 } else if (tens === 2 && hundreds !== 12) {
619 return `${ value }nd`;
620 } else if (tens === 3 && hundreds !== 13) {
621 return `${ value }rd`;
622 }
623 return `${ value }th`;
624}
625
626async function poll (fn, options = {}, done) {
627 const interval = typeof options === 'number' ? options : options.interval || 1000;
628 const retries = typeof options.retries === 'number' ? options.retries : Infinity;
629 const validate = typeof options.validate === 'function' ? options.validate : (x) => Boolean(x);
630
631 if (fn.length === 1) { // function takes a callback
632 const originalFn = fn;
633
634 fn = new Promise((resolve, reject) => {
635 originalFn((error, result) => {
636 if (error) {
637 return reject(error);
638 }
639 return resolve(result);
640 });
641 });
642 }
643
644 let attempts = 0;
645 const poller = async (resolve, reject) => {
646 const result = await fn();
647 attempts++;
648
649 if (validate(result)) {
650 return resolve(result);
651 } else if (attempts > retries) {
652 return reject(new Error('Exceeded max retries'));
653 }
654 return setTimeout(poller, interval, resolve, reject);
655 };
656
657 if (typeof done === 'function') {
658 return new Promise(poller).
659 then((result) => done(null, result)).
660 catch((error) => done(error));
661 }
662 return new Promise(poller);
663}
664
665function precisionRound (number, precision = 2) {
666 const factor = Math.pow(10, precision);
667 return Math.round(number * factor) / factor;
668}
669
670function project (object, projection) {
671 let sum = 0;
672 for (const key in projection) {
673 sum += projection[key] ? 1 : 0;
674 }
675
676 if (sum === 0) { // selective removal
677 const result = deepClone(object);
678 for (const key in projection) {
679 remove(result, key, true);
680 }
681 return result;
682 }
683 // selective inclusion
684 const result = {};
685
686 for (const key in projection) {
687 if (projection[key]) {
688 if (typeof projection[key] === 'string') { // key change
689 set(result, projection[key], resolve(object, key));
690 } else if (typeof projection[key] === 'function') { // value transform
691 set(result, key, projection[key](resolve(object, key)));
692 } else {
693 set(result, key, resolve(object, key)); // simple projection
694 }
695 }
696 }
697 return result;
698}
699
700function range (start, end, increment = 1) {
701 if (end < start) {
702 increment *= -1;
703 }
704
705 const result = [ ];
706
707 for (let i = start; i <= end; i += increment) {
708 result.push(i);
709 }
710
711 return result;
712}
713
714function remove (object, propertyPath, removeEmptyContainer = false) {
715 if (Array.isArray(propertyPath)) {
716 for (const path of propertyPath) {
717 remove(object, path, removeEmptyContainer);
718 }
719 return true;
720 }
721
722 const parts = propertyPath.trim().split(/\./);
723 const key = parts.pop();
724
725 let parent;
726 let parentKey;
727
728 for (const part of parts) {
729 parent = object;
730 parentKey = part;
731
732 object = object[part];
733
734 if (!object) {
735 return false;
736 }
737 }
738
739 delete object[key];
740
741 if (removeEmptyContainer && size(object) === 0) {
742 delete parent[parentKey];
743 }
744
745 return true;
746}
747
748function resolve (object, path = '', delimiter) {
749 if (!object || !path) {
750 return undefined;
751 }
752
753 const parts = dividePath(path, delimiter);
754
755 for (const part of parts) {
756 object = object[part];
757 if (!object) {
758 return object;
759 }
760 }
761 return object;
762}
763
764function resolves (object, path = '', delimiter) {
765 if (!object || !path) {
766 return false;
767 }
768
769 const parts = dividePath(path, delimiter);
770
771 for (const part of parts) {
772 object = object[part];
773 if (!object) {
774 return false;
775 }
776 }
777 return true;
778}
779
780function set (object, path, value, delimiter) {
781 if (!object || !path) {
782 return false;
783 }
784
785 const parts = dividePath(path, delimiter);
786 const key = parts.pop();
787
788 for (const part of parts) {
789 if (object[part] === undefined) {
790 if (typeof part === 'number') {
791 object[part] = [];
792 } else {
793 object[part] = {};
794 }
795 }
796
797 object = object[part];
798
799 if (!object) {
800 return false;
801 }
802 }
803
804 object[key] = value;
805
806 return true;
807}
808
809function setTypes (object) {
810 for (const key in object) {
811 const value = object[key];
812
813 if (parseInt(value, 10).toString() === value) {
814 object[key] = parseInt(value, 10);
815 } else if (parseFloat(value, 10).toString() === value) {
816 object[key] = parseFloat(value, 10);
817 } else if (value === 'true') {
818 object[key] = true;
819 } else if (value === 'false') {
820 object[key] = false;
821 }
822 }
823 return object;
824}
825
826function sha1 (input) {
827 if (typeof input !== 'string') {
828 input = JSON.stringify(input);
829 }
830 return crypto.createHash('sha1').update(input).
831 digest('hex');
832}
833
834function sha256 (input) {
835 if (typeof input !== 'string') {
836 input = JSON.stringify(input);
837 }
838 return crypto.createHash('sha256').update(input).
839 digest('hex');
840}
841
842function size (object) {
843 if (typeof object === 'object') {
844 return Object.getOwnPropertyNames(object).length;
845 }
846 return 0;
847}
848
849function stripAnsi (string) {
850 return string.replace(ansiRegExp, '');
851}
852
853function times (count = 1, func, ...args) {
854 for (let i = 0; i < count; i++) {
855 func(...args);
856 }
857}
858
859function timestamp (date) {
860 if (date) {
861 return new Date(date).getTime();
862 }
863 return Date.now();
864}
865
866function toBoolean (value) {
867 if (value instanceof Map || value instanceof Set) {
868 return value.size > 0;
869 } else if (Array.isArray(value)) {
870 return value.length > 0;
871 } else if (typeof value === 'object' && value !== null) {
872 return Object.keys(value).length > 0;
873 } else if (typeof value === 'string') {
874 value = value.toLowerCase();
875
876 if (value === 'true' || value === 'yes' || value === '1') {
877 return true;
878 } else if (value === 'false' || value === 'no' || value === '0') {
879 return false;
880 }
881 }
882
883 return Boolean(value);
884}
885
886const regExpPattern = /^\/(.*?)\/([gim]*)$/;
887const escapePattern = /[|\\{}()[\]^$+*?.]/g;
888
889function toRegExp (string) {
890 const parts = string.match(regExpPattern);
891 if (parts) {
892 return new RegExp(parts[1], parts[2]);
893 }
894 return new RegExp(`^${ string.replace(escapePattern, '\\$&') }$`);
895}
896
897function unique (array) {
898 return Array.from(new Set(array));
899}
900
901module.exports = {
902 ansiRegExp,
903 callback,
904 camelize,
905 debounce,
906 decrypt,
907 deepClone,
908 deepEqual,
909 dividePath,
910 distinct,
911 duration,
912 encrypt,
913 expand,
914 filter,
915 flatten,
916 formatBytes,
917 formatNumber,
918 functionType,
919 isNative,
920 isNumber,
921 isObject,
922 isPrimitive,
923 merge,
924 milliseconds,
925 noop,
926 nop: noop,
927 once,
928 ordinal,
929 poll,
930 precisionRound,
931 project,
932 range,
933 remove,
934 resolve,
935 resolves,
936 set,
937 setTypes,
938 sha1,
939 sha256,
940 size,
941 stripAnsi,
942 times,
943 timestamp,
944 toBoolean,
945 toRegExp,
946 unique,
947};