123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299 |
- // This is used by upd8.js! It's part of the 8ackend. Read the notes there if
- // you're curious.
- //
- // Friendly(!) disclaimer: these utility functions haven't 8een tested all that
- // much. Do not assume it will do exactly what you want it to do in all cases.
- // It will likely only do exactly what I want it to, and only in the cases I
- // decided were relevant enough to 8other handling.
- 'use strict';
- // Apparently JavaScript doesn't come with a function to split an array into
- // chunks! Weird. Anyway, this is an awesome place to use a generator, even
- // though we don't really make use of the 8enefits of generators any time we
- // actually use this. 8ut it's still awesome, 8ecause I say so.
- module.exports.splitArray = function*(array, fn) {
- let lastIndex = 0;
- while (lastIndex < array.length) {
- let nextIndex = array.findIndex((item, index) => index >= lastIndex && fn(item));
- if (nextIndex === -1) {
- nextIndex = array.length;
- }
- yield array.slice(lastIndex, nextIndex);
- // Plus one because we don't want to include the dividing line in the
- // next array we yield.
- lastIndex = nextIndex + 1;
- }
- };
- // This function's name is a joke. Jokes! Hahahahahahahaha. Funny.
- module.exports.joinNoOxford = function(array, plural = 'and') {
- if (array.length === 0) {
- // ????????
- return '';
- }
- if (array.length === 1) {
- return array[0];
- }
- if (array.length === 2) {
- return `${array[0]} ${plural} ${array[1]}`;
- }
- return `${array.slice(0, -1).join(', ')} ${plural} ${array[array.length - 1]}`;
- };
- module.exports.progressPromiseAll = function (msg, array) {
- let done = 0, total = array.length;
- process.stdout.write(`\r${msg} [0/${total}]`);
- const start = Date.now();
- return Promise.all(array.map(promise => promise.then(val => {
- done++;
- // const pc = `${done}/${total}`;
- const pc = (Math.round(done / total * 1000) / 10 + '%').padEnd('99.9%'.length, ' ');
- if (done === total) {
- const time = Date.now() - start;
- process.stdout.write(`\r\x1b[2m${msg} [${pc}] \x1b[0;32mDone! \x1b[0;2m(${time} ms) \x1b[0m\n`)
- } else {
- process.stdout.write(`\r${msg} [${pc}] `);
- }
- return val;
- })));
- };
- module.exports.queue = function (array, max = 50) {
- if (max === 0) {
- return array.map(fn => fn());
- }
- const begin = [];
- let current = 0;
- const ret = array.map(fn => new Promise((resolve, reject) => {
- begin.push(() => {
- current++;
- Promise.resolve(fn()).then(value => {
- current--;
- if (current < max && begin.length) {
- begin.shift()();
- }
- resolve(value);
- }, reject);
- });
- }));
- for (let i = 0; i < max && begin.length; i++) {
- begin.shift()();
- }
- return ret;
- };
- module.exports.th = function (n) {
- if (n % 10 === 1 && n !== 11) {
- return n + 'st';
- } else if (n % 10 === 2 && n !== 12) {
- return n + 'nd';
- } else if (n % 10 === 3 && n !== 13) {
- return n + 'rd';
- } else {
- return n + 'th';
- }
- };
- // My function names just keep getting 8etter.
- module.exports.s = function (n, word) {
- return `${n} ${word}` + (n === 1 ? '' : 's');
- };
- // Hey, did you know I apparently put a space 8efore the parameters in function
- // names? 8ut only in function expressions, not declar8tions? I mean, I guess
- // you did. You're pro8a8ly more familiar with my code than I am 8y this
- // point. I haven't messed with any of this code in ages. Yay!!!!!!!!
- //
- // This function only does anything on o8jects you're going to 8e reusing.
- // Argua8ly I could use a WeakMap here, 8ut since the o8ject needs to 8e
- // reused to 8e useful anyway, I just store the result with a symbol.
- // Sorry if it's 8een frozen I guess??
- module.exports.cacheOneArg = function (fn) {
- const symbol = Symbol('Cache');
- return arg => {
- if (!arg[symbol]) {
- arg[symbol] = fn(arg);
- }
- return arg[symbol];
- };
- };
- const decorateTime = function (functionToBeWrapped) {
- const fn = function(...args) {
- const start = Date.now();
- const ret = functionToBeWrapped(...args);
- const end = Date.now();
- fn.timeSpent += end - start;
- fn.timesCalled++;
- return ret;
- };
- fn.wrappedName = functionToBeWrapped.name;
- fn.timeSpent = 0;
- fn.timesCalled = 0;
- fn.displayTime = function() {
- const averageTime = fn.timeSpent / fn.timesCalled;
- console.log(`\x1b[1m${fn.wrappedName}(...):\x1b[0m ${fn.timeSpent} ms / ${fn.timesCalled} calls \x1b[2m(avg: ${averageTime} ms)\x1b[0m`);
- };
- decorateTime.decoratedFunctions.push(fn);
- return fn;
- };
- decorateTime.decoratedFunctions = [];
- decorateTime.displayTime = function() {
- if (decorateTime.decoratedFunctions.length) {
- console.log(`\x1b[1mdecorateTime results: ` + '-'.repeat(40) + '\x1b[0m');
- for (const fn of decorateTime.decoratedFunctions) {
- fn.displayTime();
- }
- }
- };
- module.exports.decorateTime = decorateTime;
- // Stolen as #@CK from mtui!
- const parseOptions = async function(options, optionDescriptorMap) {
- // This function is sorely lacking in comments, but the basic usage is
- // as such:
- //
- // options is the array of options you want to process;
- // optionDescriptorMap is a mapping of option names to objects that describe
- // the expected value for their corresponding options.
- // Returned is a mapping of any specified option names to their values, or
- // a process.exit(1) and error message if there were any issues.
- //
- // Here are examples of optionDescriptorMap to cover all the things you can
- // do with it:
- //
- // optionDescriptorMap: {
- // 'telnet-server': {type: 'flag'},
- // 't': {alias: 'telnet-server'}
- // }
- //
- // options: ['t'] -> result: {'telnet-server': true}
- //
- // optionDescriptorMap: {
- // 'directory': {
- // type: 'value',
- // validate(name) {
- // // const whitelistedDirectories = ['apple', 'banana']
- // if (whitelistedDirectories.includes(name)) {
- // return true
- // } else {
- // return 'a whitelisted directory'
- // }
- // }
- // },
- // 'files': {type: 'series'}
- // }
- //
- // ['--directory', 'apple'] -> {'directory': 'apple'}
- // ['--directory', 'artichoke'] -> (error)
- // ['--files', 'a', 'b', 'c', ';'] -> {'files': ['a', 'b', 'c']}
- //
- // TODO: Be able to validate the values in a series option.
- const handleDashless = optionDescriptorMap[parseOptions.handleDashless];
- const handleUnknown = optionDescriptorMap[parseOptions.handleUnknown];
- const result = Object.create(null);
- for (let i = 0; i < options.length; i++) {
- const option = options[i];
- if (option.startsWith('--')) {
- // --x can be a flag or expect a value or series of values
- let name = option.slice(2).split('=')[0]; // '--x'.split('=') = ['--x']
- let descriptor = optionDescriptorMap[name];
- if (!descriptor) {
- if (handleUnknown) {
- handleUnknown(option);
- } else {
- console.error(`Unknown option name: ${name}`);
- process.exit(1);
- }
- continue;
- }
- if (descriptor.alias) {
- name = descriptor.alias;
- descriptor = optionDescriptorMap[name];
- }
- if (descriptor.type === 'flag') {
- result[name] = true;
- } else if (descriptor.type === 'value') {
- let value = option.slice(2).split('=')[1];
- if (!value) {
- value = options[++i];
- if (!value || value.startsWith('-')) {
- value = null;
- }
- }
- if (!value) {
- console.error(`Expected a value for --${name}`);
- process.exit(1);
- }
- result[name] = value;
- } else if (descriptor.type === 'series') {
- if (!options.slice(i).includes(';')) {
- console.error(`Expected a series of values concluding with ; (\\;) for --${name}`);
- process.exit(1);
- }
- const endIndex = i + options.slice(i).indexOf(';');
- result[name] = options.slice(i + 1, endIndex);
- i = endIndex;
- }
- if (descriptor.validate) {
- const validation = await descriptor.validate(result[name]);
- if (validation !== true) {
- console.error(`Expected ${validation} for --${name}`);
- process.exit(1);
- }
- }
- } else if (option.startsWith('-')) {
- // mtui doesn't use any -x=y or -x y format optionuments
- // -x will always just be a flag
- let name = option.slice(1);
- let descriptor = optionDescriptorMap[name];
- if (!descriptor) {
- if (handleUnknown) {
- handleUnknown(option);
- } else {
- console.error(`Unknown option name: ${name}`);
- process.exit(1);
- }
- continue;
- }
- if (descriptor.alias) {
- name = descriptor.alias;
- descriptor = optionDescriptorMap[name];
- }
- if (descriptor.type === 'flag') {
- result[name] = true;
- } else {
- console.error(`Use --${name} (value) to specify ${name}`);
- process.exit(1);
- }
- } else if (handleDashless) {
- handleDashless(option);
- }
- }
- return result;
- }
- parseOptions.handleDashless = Symbol();
- parseOptions.handleUnknown = Symbol();
- module.exports.parseOptions = parseOptions;
- // Cheap FP for a cheap dyke!
- // I have no idea if this is what curry actually means.
- module.exports.curry = f => x => (...args) => f(x, ...args);
- module.exports.mapInPlace = (array, fn) => array.splice(0, array.length, ...array.map(fn));
|