UNPKG

21.7 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 (typeof Buffer !== 'undefined' && 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 (typeof Buffer !== 'undefined' && 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 naturalCompare (a, b) {
502 let i; let codeA
503 ; let codeB = 1
504 ; let posA = 0
505 ; let posB = 0
506 ; const alphabet = String.alphabet;
507
508 function getCode (str, pos, code) {
509 if (code) {
510 for (i = pos; getCode(str, i) < 76 && getCode(str, i) > 65;) { ++i; }
511 return Number(str.slice(pos - 1, i));
512 }
513 code = alphabet && alphabet.indexOf(str.charAt(pos));
514 code = code > -1 ? code + 76 : (code = str.charCodeAt(pos) || 0, code < 45 || code > 127) ? code :
515 code < 46 ? 65 : // -
516 code < 48 ? code - 1 :
517 code < 58 ? code + 18 : // 0-9
518 code < 65 ? code - 11 :
519 code < 91 ? code + 11 : // A-Z
520 code < 97 ? code - 37 :
521 code < 123 ? code + 5 : // a-z
522 code - 63;
523 return code;
524 }
525
526 if ((a = String(a)) !== (b = String(b))) {
527 for (;codeB;) {
528 codeA = getCode(a, posA++);
529 codeB = getCode(b, posB++);
530
531 if (codeA < 76 && codeB < 76 && codeA > 66 && codeB > 66) {
532 codeA = getCode(a, posA, posA);
533 codeB = getCode(b, posB, posA = i);
534 posB = i;
535 }
536
537 if (codeA !== codeB) { return codeA < codeB ? -1 : 1; }
538 }
539 }
540 return 0;
541}
542
543function merge (objectA, objectB, createNew = false, seen) {
544 if (createNew) {
545 objectA = deepClone(objectA);
546 }
547
548 seen = new Set(seen);
549
550 const keys = Object.getOwnPropertyNames(objectB);
551 for (const key of keys) {
552 if (objectB[key] === null || Array.isArray(objectB[key]) ||
553 objectB[key] instanceof Date || objectB[key] instanceof Map ||
554 objectB[key] instanceof Set || objectB[key] instanceof RegExp ||
555 typeof Buffer !== 'undefined' && objectB[key] instanceof Buffer) {
556 if (createNew) {
557 objectA[key] = deepClone(objectB[key]);
558 } else {
559 objectA[key] = objectB[key];
560 }
561 } else if (typeof objectB[key] === 'object' && !seen.has(objectB[key])) {
562 if (typeof objectA[key] === 'object') {
563 objectA[key] = merge(objectA[key], objectB[key], createNew, seen);
564 } else if (createNew) {
565 objectA[key] = deepClone(objectB[key]);
566 } else {
567 objectA[key] = objectB[key];
568 }
569
570 seen.add(objectB[key]);
571 } else if (createNew) {
572 objectA[key] = deepClone(objectB[key]);
573 } else {
574 objectA[key] = objectB[key];
575 }
576 }
577 return objectA;
578}
579
580function milliseconds (value) {
581 if (typeof value === 'number') {
582 return value;
583 } else if (typeof value === 'string') {
584 let millis = 0;
585 value.replace(/(\d+\.?\d*)\s*([mshd]+)/g, (match, time, unit) => {
586 time = Number(time) || 0;
587 if (unit === 'ms') {
588 time *= 1;
589 } else if (unit === 's') {
590 time *= 1000;
591 } else if (unit === 'm') {
592 time *= 1000 * 60;
593 } else if (unit === 'h') {
594 time *= 1000 * 60 * 60;
595 } else if (unit === 'd') {
596 time *= 1000 * 60 * 60 * 24;
597 }
598
599 millis += time;
600 });
601
602 return Math.ceil(millis);
603 }
604 return 0;
605}
606
607function once (func) {
608 if (typeof func !== 'function') {
609 return noop;
610 }
611
612 let invoked = false;
613
614 return (...args) => {
615 if (!invoked) {
616 invoked = true;
617 return func(...args);
618 }
619 return false;
620 };
621}
622
623function ordinal (value) {
624 value = Number(value);
625
626 const tens = value % 10;
627 const hundreds = value % 100;
628
629 if (tens === 1 && hundreds !== 11) {
630 return `${ value }st`;
631 } else if (tens === 2 && hundreds !== 12) {
632 return `${ value }nd`;
633 } else if (tens === 3 && hundreds !== 13) {
634 return `${ value }rd`;
635 }
636 return `${ value }th`;
637}
638
639async function poll (fn, options = {}, done) {
640 const interval = typeof options === 'number' ? options : options.interval || 1000;
641 const retries = typeof options.retries === 'number' ? options.retries : Infinity;
642 const validate = typeof options.validate === 'function' ? options.validate : (x) => Boolean(x);
643
644 if (fn.length === 1) { // function takes a callback
645 const originalFn = fn;
646
647 fn = new Promise((resolve, reject) => {
648 originalFn((error, result) => {
649 if (error) {
650 return reject(error);
651 }
652 return resolve(result);
653 });
654 });
655 }
656
657 let attempts = 0;
658 const poller = async (resolve, reject) => {
659 const result = await fn();
660 attempts++;
661
662 if (validate(result)) {
663 return resolve(result);
664 } else if (attempts > retries) {
665 return reject(new Error('Exceeded max retries'));
666 }
667 return setTimeout(poller, interval, resolve, reject);
668 };
669
670 if (typeof done === 'function') {
671 return new Promise(poller).
672 then((result) => done(null, result)).
673 catch((error) => done(error));
674 }
675 return new Promise(poller);
676}
677
678function precisionRound (number, precision = 2) {
679 const factor = Math.pow(10, precision);
680 return Math.round(number * factor) / factor;
681}
682
683function project (object, projection) {
684 let sum = 0;
685 for (const key in projection) {
686 sum += projection[key] ? 1 : 0;
687 }
688
689 if (sum === 0) { // selective removal
690 const result = deepClone(object);
691 for (const key in projection) {
692 remove(result, key, true);
693 }
694 return result;
695 }
696 // selective inclusion
697 const result = {};
698
699 for (const key in projection) {
700 if (projection[key]) {
701 if (typeof projection[key] === 'string') { // key change
702 set(result, projection[key], resolve(object, key));
703 } else if (typeof projection[key] === 'function') { // value transform
704 set(result, key, projection[key](resolve(object, key)));
705 } else {
706 set(result, key, resolve(object, key)); // simple projection
707 }
708 }
709 }
710 return result;
711}
712
713function range (start, end, increment = 1) {
714 if (end < start) {
715 increment *= -1;
716 }
717
718 const result = [ ];
719
720 for (let i = start; i <= end; i += increment) {
721 result.push(i);
722 }
723
724 return result;
725}
726
727function remove (object, propertyPath, removeEmptyContainer = false) {
728 if (Array.isArray(propertyPath)) {
729 for (const path of propertyPath) {
730 remove(object, path, removeEmptyContainer);
731 }
732 return true;
733 }
734
735 const parts = propertyPath.trim().split(/\./);
736 const key = parts.pop();
737
738 let parent;
739 let parentKey;
740
741 for (const part of parts) {
742 parent = object;
743 parentKey = part;
744
745 object = object[part];
746
747 if (!object) {
748 return false;
749 }
750 }
751
752 delete object[key];
753
754 if (removeEmptyContainer && size(object) === 0) {
755 delete parent[parentKey];
756 }
757
758 return true;
759}
760
761function resolve (object, path = '', delimiter) {
762 if (!object || !path) {
763 return undefined;
764 }
765
766 const parts = dividePath(path, delimiter);
767
768 for (const part of parts) {
769 object = object[part];
770 if (!object) {
771 return object;
772 }
773 }
774 return object;
775}
776
777function resolves (object, path = '', delimiter) {
778 if (!object || !path) {
779 return false;
780 }
781
782 const parts = dividePath(path, delimiter);
783
784 for (const part of parts) {
785 object = object[part];
786 if (!object) {
787 return false;
788 }
789 }
790 return true;
791}
792
793function set (object, path, value, delimiter) {
794 if (!object || !path) {
795 return false;
796 }
797
798 const parts = dividePath(path, delimiter);
799 const key = parts.pop();
800
801 for (const part of parts) {
802 if (object[part] === undefined) {
803 if (typeof part === 'number') {
804 object[part] = [];
805 } else {
806 object[part] = {};
807 }
808 }
809
810 object = object[part];
811
812 if (!object) {
813 return false;
814 }
815 }
816
817 object[key] = value;
818
819 return true;
820}
821
822function setTypes (object) {
823 for (const key in object) {
824 const value = object[key];
825
826 if (parseInt(value, 10).toString() === value) {
827 object[key] = parseInt(value, 10);
828 } else if (parseFloat(value, 10).toString() === value) {
829 object[key] = parseFloat(value, 10);
830 } else if (value === 'true') {
831 object[key] = true;
832 } else if (value === 'false') {
833 object[key] = false;
834 }
835 }
836 return object;
837}
838
839function size (object) {
840 if (typeof object === 'object') {
841 return Object.getOwnPropertyNames(object).length;
842 }
843 return 0;
844}
845
846function stripAnsi (string) {
847 return string.replace(ansiRegExp, '');
848}
849
850function times (count = 1, func, ...args) {
851 for (let i = 0; i < count; i++) {
852 func(...args);
853 }
854}
855
856function timestamp (date) {
857 if (date) {
858 return new Date(date).getTime();
859 }
860 return Date.now();
861}
862
863function toBoolean (value) {
864 if (value instanceof Map || value instanceof Set) {
865 return value.size > 0;
866 } else if (Array.isArray(value)) {
867 return value.length > 0;
868 } else if (typeof value === 'object' && value !== null) {
869 return Object.keys(value).length > 0;
870 } else if (typeof value === 'string') {
871 value = value.toLowerCase();
872
873 if (value === 'true' || value === 'yes' || value === '1') {
874 return true;
875 } else if (value === 'false' || value === 'no' || value === '0') {
876 return false;
877 }
878 }
879
880 return Boolean(value);
881}
882
883const regExpPattern = /^\/(.*?)\/([gim]*)$/;
884const escapePattern = /[|\\{}()[\]^$+*?.]/g;
885
886function toRegExp (string) {
887 const parts = string.match(regExpPattern);
888 if (parts) {
889 return new RegExp(parts[1], parts[2]);
890 }
891 return new RegExp(`^${ string.replace(escapePattern, '\\$&') }$`);
892}
893
894function unique (array) {
895 return Array.from(new Set(array));
896}
897
898module.exports = {
899 ansiRegExp,
900 callback,
901 camelize,
902 debounce,
903 deepClone,
904 deepEqual,
905 dividePath,
906 distinct,
907 duration,
908 expand,
909 filter,
910 flatten,
911 formatBytes,
912 formatNumber,
913 functionType,
914 isNative,
915 isNumber,
916 isObject,
917 isPrimitive,
918 merge,
919 naturalCompare,
920 milliseconds,
921 noop,
922 nop: noop,
923 once,
924 ordinal,
925 poll,
926 precisionRound,
927 project,
928 range,
929 remove,
930 resolve,
931 resolves,
932 set,
933 setTypes,
934 size,
935 stripAnsi,
936 times,
937 timestamp,
938 toBoolean,
939 toRegExp,
940 unique,
941};