From 4075254c9e38be6741527e1fb535eed444e6ad08 Mon Sep 17 00:00:00 2001 From: "(quasar) nebula" Date: Sun, 26 Jun 2022 16:41:09 -0300 Subject: initial prettier/eslint commit --- src/util/sugar.js | 609 ++++++++++++++++++++++++++++-------------------------- 1 file changed, 320 insertions(+), 289 deletions(-) (limited to 'src/util/sugar.js') diff --git a/src/util/sugar.js b/src/util/sugar.js index 99f706f..70672bf 100644 --- a/src/util/sugar.js +++ b/src/util/sugar.js @@ -6,69 +6,81 @@ // It will likely only do exactly what I want it to, and only in the cases I // decided were relevant enough to 8other handling. -import { color } from './cli.js'; +import { color } from "./cli.js"; // 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. export function* splitArray(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; + 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; + } +} -export const mapInPlace = (array, fn) => array.splice(0, array.length, ...array.map(fn)); +export const mapInPlace = (array, fn) => + array.splice(0, array.length, ...array.map(fn)); -export const filterEmptyLines = string => string.split('\n').filter(line => line.trim()).join('\n'); +export const filterEmptyLines = (string) => + string + .split("\n") + .filter((line) => line.trim()) + .join("\n"); -export const unique = arr => Array.from(new Set(arr)); +export const unique = (arr) => Array.from(new Set(arr)); -export const compareArrays = (arr1, arr2, {checkOrder = true} = {}) => ( - arr1.length === arr2.length && (checkOrder - ? (arr1.every((x, i) => arr2[i] === x)) - : (arr1.every(x => arr2.includes(x))))); +export const compareArrays = (arr1, arr2, { checkOrder = true } = {}) => + arr1.length === arr2.length && + (checkOrder + ? arr1.every((x, i) => arr2[i] === x) + : arr1.every((x) => arr2.includes(x))); // Stolen from jq! Which pro8a8ly stole the concept from other places. Nice. -export const withEntries = (obj, fn) => Object.fromEntries(fn(Object.entries(obj))); +export const withEntries = (obj, fn) => + Object.fromEntries(fn(Object.entries(obj))); export function queue(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) => { + 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); + 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()(); - } + for (let i = 0; i < max && begin.length; i++) { + begin.shift()(); + } - return ret; + return ret; } export function delay(ms) { - return new Promise(res => setTimeout(res, ms)); + return new Promise((res) => setTimeout(res, ms)); } // Stolen from here: https://stackoverflow.com/a/3561711 @@ -76,22 +88,22 @@ export function delay(ms) { // There's a proposal for a native JS function like this, 8ut it's not even // past stage 1 yet: https://github.com/tc39/proposal-regex-escaping export function escapeRegex(string) { - return string.replace(/[-\/\\^$*+?.()|[\]{}]/g, '\\$&'); + return string.replace(/[-\/\\^$*+?.()|[\]{}]/g, "\\$&"); } export function bindOpts(fn, bind) { - const bindIndex = bind[bindOpts.bindIndex] ?? 1; + const bindIndex = bind[bindOpts.bindIndex] ?? 1; - const bound = function(...args) { - const opts = args[bindIndex] ?? {}; - return fn(...args.slice(0, bindIndex), {...bind, ...opts}); - }; + const bound = function (...args) { + const opts = args[bindIndex] ?? {}; + return fn(...args.slice(0, bindIndex), { ...bind, ...opts }); + }; - Object.defineProperty(bound, 'name', { - value: (fn.name ? `(options-bound) ${fn.name}` : `(options-bound)`) - }); + Object.defineProperty(bound, "name", { + value: fn.name ? `(options-bound) ${fn.name}` : `(options-bound)`, + }); - return bound; + return bound; } bindOpts.bindIndex = Symbol(); @@ -108,103 +120,108 @@ bindOpts.bindIndex = Symbol(); // object containing all caught errors (or doesn't throw anything if there were // no errors). export function openAggregate({ - // Constructor to use, defaulting to the builtin AggregateError class. - // Anything passed here should probably extend from that! May be used for - // letting callers programatically distinguish between multiple aggregate - // errors. - // - // This should be provided using the aggregateThrows utility function. - [openAggregate.errorClassSymbol]: errorClass = AggregateError, - - // Optional human-readable message to describe the aggregate error, if - // constructed. - message = '', - - // Value to return when a provided function throws an error. If this is a - // function, it will be called with the arguments given to the function. - // (This is primarily useful when wrapping a function and then providing it - // to another utility, e.g. array.map().) - returnOnFail = null + // Constructor to use, defaulting to the builtin AggregateError class. + // Anything passed here should probably extend from that! May be used for + // letting callers programatically distinguish between multiple aggregate + // errors. + // + // This should be provided using the aggregateThrows utility function. + [openAggregate.errorClassSymbol]: errorClass = AggregateError, + + // Optional human-readable message to describe the aggregate error, if + // constructed. + message = "", + + // Value to return when a provided function throws an error. If this is a + // function, it will be called with the arguments given to the function. + // (This is primarily useful when wrapping a function and then providing it + // to another utility, e.g. array.map().) + returnOnFail = null, } = {}) { - const errors = []; - - const aggregate = {}; - - aggregate.wrap = fn => (...args) => { - try { - return fn(...args); - } catch (error) { - errors.push(error); - return (typeof returnOnFail === 'function' - ? returnOnFail(...args) - : returnOnFail); - } - }; - - aggregate.wrapAsync = fn => (...args) => { - return fn(...args).then( - value => value, - error => { - errors.push(error); - return (typeof returnOnFail === 'function' - ? returnOnFail(...args) - : returnOnFail); - }); - }; - - aggregate.call = (fn, ...args) => { - return aggregate.wrap(fn)(...args); - }; - - aggregate.callAsync = (fn, ...args) => { - return aggregate.wrapAsync(fn)(...args); - }; - - aggregate.nest = (...args) => { - return aggregate.call(() => withAggregate(...args)); + const errors = []; + + const aggregate = {}; + + aggregate.wrap = + (fn) => + (...args) => { + try { + return fn(...args); + } catch (error) { + errors.push(error); + return typeof returnOnFail === "function" + ? returnOnFail(...args) + : returnOnFail; + } }; - aggregate.nestAsync = (...args) => { - return aggregate.callAsync(() => withAggregateAsync(...args)); - }; - - aggregate.map = (...args) => { - const parent = aggregate; - const { result, aggregate: child } = mapAggregate(...args); - parent.call(child.close); - return result; - }; - - aggregate.mapAsync = async (...args) => { - const parent = aggregate; - const { result, aggregate: child } = await mapAggregateAsync(...args); - parent.call(child.close); - return result; - }; - - aggregate.filter = (...args) => { - const parent = aggregate; - const { result, aggregate: child } = filterAggregate(...args); - parent.call(child.close); - return result; - }; - - aggregate.throws = aggregateThrows; - - aggregate.close = () => { - if (errors.length) { - throw Reflect.construct(errorClass, [errors, message]); + aggregate.wrapAsync = + (fn) => + (...args) => { + return fn(...args).then( + (value) => value, + (error) => { + errors.push(error); + return typeof returnOnFail === "function" + ? returnOnFail(...args) + : returnOnFail; } + ); }; - return aggregate; + aggregate.call = (fn, ...args) => { + return aggregate.wrap(fn)(...args); + }; + + aggregate.callAsync = (fn, ...args) => { + return aggregate.wrapAsync(fn)(...args); + }; + + aggregate.nest = (...args) => { + return aggregate.call(() => withAggregate(...args)); + }; + + aggregate.nestAsync = (...args) => { + return aggregate.callAsync(() => withAggregateAsync(...args)); + }; + + aggregate.map = (...args) => { + const parent = aggregate; + const { result, aggregate: child } = mapAggregate(...args); + parent.call(child.close); + return result; + }; + + aggregate.mapAsync = async (...args) => { + const parent = aggregate; + const { result, aggregate: child } = await mapAggregateAsync(...args); + parent.call(child.close); + return result; + }; + + aggregate.filter = (...args) => { + const parent = aggregate; + const { result, aggregate: child } = filterAggregate(...args); + parent.call(child.close); + return result; + }; + + aggregate.throws = aggregateThrows; + + aggregate.close = () => { + if (errors.length) { + throw Reflect.construct(errorClass, [errors, message]); + } + }; + + return aggregate; } -openAggregate.errorClassSymbol = Symbol('error class'); +openAggregate.errorClassSymbol = Symbol("error class"); // Utility function for providing {errorClass} parameter to aggregate functions. export function aggregateThrows(errorClass) { - return {[openAggregate.errorClassSymbol]: errorClass}; + return { [openAggregate.errorClassSymbol]: errorClass }; } // Performs an ordinary array map with the given function, collating into a @@ -217,36 +234,38 @@ export function aggregateThrows(errorClass) { // use aggregate.close() to throw the error. (This aggregate may be passed to a // parent aggregate: `parent.call(aggregate.close)`!) export function mapAggregate(array, fn, aggregateOpts) { - return _mapAggregate('sync', null, array, fn, aggregateOpts); + return _mapAggregate("sync", null, array, fn, aggregateOpts); } -export function mapAggregateAsync(array, fn, { - promiseAll = Promise.all.bind(Promise), - ...aggregateOpts -} = {}) { - return _mapAggregate('async', promiseAll, array, fn, aggregateOpts); +export function mapAggregateAsync( + array, + fn, + { promiseAll = Promise.all.bind(Promise), ...aggregateOpts } = {} +) { + return _mapAggregate("async", promiseAll, array, fn, aggregateOpts); } // Helper function for mapAggregate which holds code common between sync and // async versions. export function _mapAggregate(mode, promiseAll, array, fn, aggregateOpts) { - const failureSymbol = Symbol(); - - const aggregate = openAggregate({ - returnOnFail: failureSymbol, - ...aggregateOpts + const failureSymbol = Symbol(); + + const aggregate = openAggregate({ + returnOnFail: failureSymbol, + ...aggregateOpts, + }); + + if (mode === "sync") { + const result = array + .map(aggregate.wrap(fn)) + .filter((value) => value !== failureSymbol); + return { result, aggregate }; + } else { + return promiseAll(array.map(aggregate.wrapAsync(fn))).then((values) => { + const result = values.filter((value) => value !== failureSymbol); + return { result, aggregate }; }); - - if (mode === 'sync') { - const result = array.map(aggregate.wrap(fn)) - .filter(value => value !== failureSymbol); - return {result, aggregate}; - } else { - return promiseAll(array.map(aggregate.wrapAsync(fn))).then(values => { - const result = values.filter(value => value !== failureSymbol); - return {result, aggregate}; - }); - } + } } // Performs an ordinary array filter with the given function, collating into a @@ -257,162 +276,174 @@ export function _mapAggregate(mode, promiseAll, array, fn, aggregateOpts) { // // As with mapAggregate, the returned aggregate property is not yet closed. export function filterAggregate(array, fn, aggregateOpts) { - return _filterAggregate('sync', null, array, fn, aggregateOpts); + return _filterAggregate("sync", null, array, fn, aggregateOpts); } -export async function filterAggregateAsync(array, fn, { - promiseAll = Promise.all.bind(Promise), - ...aggregateOpts -} = {}) { - return _filterAggregate('async', promiseAll, array, fn, aggregateOpts); +export async function filterAggregateAsync( + array, + fn, + { promiseAll = Promise.all.bind(Promise), ...aggregateOpts } = {} +) { + return _filterAggregate("async", promiseAll, array, fn, aggregateOpts); } // Helper function for filterAggregate which holds code common between sync and // async versions. function _filterAggregate(mode, promiseAll, array, fn, aggregateOpts) { - const failureSymbol = Symbol(); - - const aggregate = openAggregate({ - returnOnFail: failureSymbol, - ...aggregateOpts + const failureSymbol = Symbol(); + + const aggregate = openAggregate({ + returnOnFail: failureSymbol, + ...aggregateOpts, + }); + + function filterFunction(value) { + // Filter out results which match the failureSymbol, i.e. errored + // inputs. + if (value === failureSymbol) return false; + + // Always keep results which match the overridden returnOnFail + // value, if provided. + if (value === aggregateOpts.returnOnFail) return true; + + // Otherwise, filter according to the returned value of the wrapped + // function. + return value.output; + } + + function mapFunction(value) { + // Then turn the results back into their corresponding input, or, if + // provided, the overridden returnOnFail value. + return value === aggregateOpts.returnOnFail ? value : value.input; + } + + function wrapperFunction(x, ...rest) { + return { + input: x, + output: fn(x, ...rest), + }; + } + + if (mode === "sync") { + const result = array + .map( + aggregate.wrap((input, index, array) => { + const output = fn(input, index, array); + return { input, output }; + }) + ) + .filter(filterFunction) + .map(mapFunction); + + return { result, aggregate }; + } else { + return promiseAll( + array.map( + aggregate.wrapAsync(async (input, index, array) => { + const output = await fn(input, index, array); + return { input, output }; + }) + ) + ).then((values) => { + const result = values.filter(filterFunction).map(mapFunction); + + return { result, aggregate }; }); - - function filterFunction(value) { - // Filter out results which match the failureSymbol, i.e. errored - // inputs. - if (value === failureSymbol) return false; - - // Always keep results which match the overridden returnOnFail - // value, if provided. - if (value === aggregateOpts.returnOnFail) return true; - - // Otherwise, filter according to the returned value of the wrapped - // function. - return value.output; - } - - function mapFunction(value) { - // Then turn the results back into their corresponding input, or, if - // provided, the overridden returnOnFail value. - return (value === aggregateOpts.returnOnFail - ? value - : value.input); - } - - function wrapperFunction(x, ...rest) { - return { - input: x, - output: fn(x, ...rest) - }; - } - - if (mode === 'sync') { - const result = array - .map(aggregate.wrap((input, index, array) => { - const output = fn(input, index, array); - return {input, output}; - })) - .filter(filterFunction) - .map(mapFunction); - - return {result, aggregate}; - } else { - return promiseAll(array.map(aggregate.wrapAsync(async (input, index, array) => { - const output = await fn(input, index, array); - return {input, output}; - }))).then(values => { - const result = values - .filter(filterFunction) - .map(mapFunction); - - return {result, aggregate}; - }); - } + } } // Totally sugar function for opening an aggregate, running the provided // function with it, then closing the function and returning the result (if // there's no throw). export function withAggregate(aggregateOpts, fn) { - return _withAggregate('sync', aggregateOpts, fn); + return _withAggregate("sync", aggregateOpts, fn); } export function withAggregateAsync(aggregateOpts, fn) { - return _withAggregate('async', aggregateOpts, fn); + return _withAggregate("async", aggregateOpts, fn); } export function _withAggregate(mode, aggregateOpts, fn) { - if (typeof aggregateOpts === 'function') { - fn = aggregateOpts; - aggregateOpts = {}; - } - - const aggregate = openAggregate(aggregateOpts); + if (typeof aggregateOpts === "function") { + fn = aggregateOpts; + aggregateOpts = {}; + } + + const aggregate = openAggregate(aggregateOpts); + + if (mode === "sync") { + const result = fn(aggregate); + aggregate.close(); + return result; + } else { + return fn(aggregate).then((result) => { + aggregate.close(); + return result; + }); + } +} - if (mode === 'sync') { - const result = fn(aggregate); - aggregate.close(); - return result; +export function showAggregate( + topError, + { pathToFile = (p) => p, showTraces = true } = {} +) { + const recursive = (error, { level }) => { + let header = showTraces + ? `[${error.constructor.name || "unnamed"}] ${ + error.message || "(no message)" + }` + : error instanceof AggregateError + ? `[${error.message || "(no message)"}]` + : error.message || "(no message)"; + if (showTraces) { + const stackLines = error.stack?.split("\n"); + const stackLine = stackLines?.find( + (line) => + line.trim().startsWith("at") && + !line.includes("sugar") && + !line.includes("node:") && + !line.includes("") + ); + const tracePart = stackLine + ? "- " + + stackLine + .trim() + .replace(/file:\/\/(.*\.js)/, (match, pathname) => + pathToFile(pathname) + ) + : "(no stack trace)"; + header += ` ${color.dim(tracePart)}`; + } + const bar = level % 2 === 0 ? "\u2502" : color.dim("\u254e"); + const head = level % 2 === 0 ? "\u257f" : color.dim("\u257f"); + + if (error instanceof AggregateError) { + return ( + header + + "\n" + + error.errors + .map((error) => recursive(error, { level: level + 1 })) + .flatMap((str) => str.split("\n")) + .map((line, i, lines) => + i === 0 ? ` ${head} ${line}` : ` ${bar} ${line}` + ) + .join("\n") + ); } else { - return fn(aggregate).then(result => { - aggregate.close(); - return result; - }); + return header; } -} + }; -export function showAggregate(topError, { - pathToFile = p => p, - showTraces = true -} = {}) { - const recursive = (error, {level}) => { - let header = (showTraces - ? `[${error.constructor.name || 'unnamed'}] ${error.message || '(no message)'}` - : (error instanceof AggregateError - ? `[${error.message || '(no message)'}]` - : error.message || '(no message)')); - if (showTraces) { - const stackLines = error.stack?.split('\n'); - const stackLine = stackLines?.find(line => - line.trim().startsWith('at') - && !line.includes('sugar') - && !line.includes('node:') - && !line.includes('')); - const tracePart = (stackLine - ? '- ' + stackLine.trim().replace(/file:\/\/(.*\.js)/, (match, pathname) => pathToFile(pathname)) - : '(no stack trace)'); - header += ` ${color.dim(tracePart)}`; - } - const bar = (level % 2 === 0 - ? '\u2502' - : color.dim('\u254e')); - const head = (level % 2 === 0 - ? '\u257f' - : color.dim('\u257f')); - - if (error instanceof AggregateError) { - return header + '\n' + (error.errors - .map(error => recursive(error, {level: level + 1})) - .flatMap(str => str.split('\n')) - .map((line, i, lines) => (i === 0 - ? ` ${head} ${line}` - : ` ${bar} ${line}`)) - .join('\n')); - } else { - return header; - } - }; - - console.error(recursive(topError, {level: 0})); + console.error(recursive(topError, { level: 0 })); } export function decorateErrorWithIndex(fn) { - return (x, index, array) => { - try { - return fn(x, index, array); - } catch (error) { - error.message = `(${color.yellow(`#${index + 1}`)}) ${error.message}`; - throw error; - } + return (x, index, array) => { + try { + return fn(x, index, array); + } catch (error) { + error.message = `(${color.yellow(`#${index + 1}`)}) ${error.message}`; + throw error; } + }; } -- cgit 1.3.0-6-gf8a5