« get me outta code hell

hsmusic-wiki - HSMusic - static wiki software cataloguing collaborative creation
about summary refs log tree commit diff
path: root/src/util/sugar.js
diff options
context:
space:
mode:
Diffstat (limited to 'src/util/sugar.js')
-rw-r--r--src/util/sugar.js418
1 files changed, 0 insertions, 418 deletions
diff --git a/src/util/sugar.js b/src/util/sugar.js
deleted file mode 100644
index 99f706f1..00000000
--- a/src/util/sugar.js
+++ /dev/null
@@ -1,418 +0,0 @@
-// Syntactic sugar! (Mostly.)
-// Generic functions - these are useful just a8out everywhere.
-//
-// 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.
-
-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;
-    }
-};
-
-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 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)))));
-
-// Stolen from jq! Which pro8a8ly stole the concept from other places. Nice.
-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) => {
-        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;
-}
-
-export function delay(ms) {
-    return new Promise(res => setTimeout(res, ms));
-}
-
-// Stolen from here: https://stackoverflow.com/a/3561711
-//
-// 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, '\\$&');
-}
-
-export function bindOpts(fn, bind) {
-    const bindIndex = bind[bindOpts.bindIndex] ?? 1;
-
-    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)`)
-    });
-
-    return bound;
-}
-
-bindOpts.bindIndex = Symbol();
-
-// Utility function for providing useful interfaces to the JS AggregateError
-// class.
-//
-// Generally, this works by returning a set of interfaces which operate on
-// functions: wrap() takes a function and returns a new function which passes
-// its arguments through and appends any resulting error to the internal error
-// list; call() simplifies this process by wrapping the provided function and
-// then calling it immediately. Once the process for which errors should be
-// aggregated is complete, close() constructs and throws an AggregateError
-// 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
-} = {}) {
-    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));
-    };
-
-    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');
-
-// Utility function for providing {errorClass} parameter to aggregate functions.
-export function aggregateThrows(errorClass) {
-    return {[openAggregate.errorClassSymbol]: errorClass};
-}
-
-// Performs an ordinary array map with the given function, collating into a
-// results array (with errored inputs filtered out) and an error aggregate.
-//
-// Optionally, override returnOnFail to disable filtering and map errored inputs
-// to a particular output.
-//
-// Note the aggregate property is the result of openAggregate(), still unclosed;
-// 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);
-}
-
-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
-    });
-
-    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
-// results array (with errored inputs filtered out) and an error aggregate.
-//
-// Optionally, override returnOnFail to disable filtering errors and map errored
-// inputs to a particular output.
-//
-// As with mapAggregate, the returned aggregate property is not yet closed.
-export function filterAggregate(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);
-}
-
-// 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
-    });
-
-    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);
-}
-
-export function withAggregateAsync(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 (mode === 'sync') {
-        const result = fn(aggregate);
-        aggregate.close();
-        return result;
-    } else {
-        return fn(aggregate).then(result => {
-            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('<anonymous>'));
-            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}));
-}
-
-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;
-        }
-    }
-}