UNPKG

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