diff options
author | (quasar) nebula <qznebula@protonmail.com> | 2023-11-09 14:42:24 -0400 |
---|---|---|
committer | (quasar) nebula <qznebula@protonmail.com> | 2023-11-09 14:42:24 -0400 |
commit | aa30c888ea2307931c555db474d709f520c551a8 (patch) | |
tree | b23042b5b575862d83f401b5fa21f8b45f7988ff /src/util | |
parent | e71230340181a3b7b38ff05ba23504b264f5b26c (diff) | |
parent | b62622d3cd8ffe1ed517ceb873d9352943c4a601 (diff) |
Merge branch 'preview' into listing-tweaks
Diffstat (limited to 'src/util')
-rw-r--r-- | src/util/cli.js | 31 | ||||
-rw-r--r-- | src/util/html.js | 4 | ||||
-rw-r--r-- | src/util/sugar.js | 157 |
3 files changed, 155 insertions, 37 deletions
diff --git a/src/util/cli.js b/src/util/cli.js index 4c08c085..973fef19 100644 --- a/src/util/cli.js +++ b/src/util/cli.js @@ -340,3 +340,34 @@ export function fileIssue({ console.error(colors.red(`- https://hsmusic.wiki/feedback/`)); console.error(colors.red(`- https://github.com/hsmusic/hsmusic-wiki/issues/`)); } + +export async function logicalCWD() { + if (process.env.PWD) { + return process.env.PWD; + } + + const {exec} = await import('node:child_process'); + const {stat} = await import('node:fs/promises'); + + try { + await stat('/bin/sh'); + } catch (error) { + // Not logical, so sad. + return process.cwd(); + } + + const proc = exec('/bin/pwd -L'); + + let output = ''; + proc.stdout.on('data', buf => { output += buf; }); + + await new Promise(resolve => proc.on('exit', resolve)); + + return output.trim(); +} + +export async function logicalPathTo(target) { + const {relative} = await import('node:path'); + const cwd = await logicalCWD(); + return relative(cwd, target); +} diff --git a/src/util/html.js b/src/util/html.js index 282a52da..5b6743e0 100644 --- a/src/util/html.js +++ b/src/util/html.js @@ -181,6 +181,10 @@ export function tags(content, attributes = null) { return new Tag(null, attributes, content); } +export function normalize(content) { + return Tag.normalize(content); +} + export class Tag { #tagName = ''; #content = null; diff --git a/src/util/sugar.js b/src/util/sugar.js index 3e39e98f..9646be37 100644 --- a/src/util/sugar.js +++ b/src/util/sugar.js @@ -411,6 +411,18 @@ export function aggregateThrows(errorClass) { return {[openAggregate.errorClassSymbol]: errorClass}; } +// Helper function for allowing both (fn, aggregateOpts) and (aggregateOpts, fn) +// in aggregate utilities. +function _reorganizeAggregateArguments(arg1, arg2) { + if (typeof arg1 === 'function') { + return {fn: arg1, opts: arg2 ?? {}}; + } else if (typeof arg2 === 'function') { + return {fn: arg2, opts: arg1 ?? {}}; + } else { + throw new Error(`Expected a function`); + } +} + // Performs an ordinary array map with the given function, collating into a // results array (with errored inputs filtered out) and an error aggregate. // @@ -420,15 +432,15 @@ export function aggregateThrows(errorClass) { // 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 mapAggregate(array, arg1, arg2) { + const {fn, opts} = _reorganizeAggregateArguments(arg1, arg2); + return _mapAggregate('sync', null, array, fn, opts); } -export function mapAggregateAsync(array, fn, { - promiseAll = Promise.all.bind(Promise), - ...aggregateOpts -} = {}) { - return _mapAggregate('async', promiseAll, array, fn, aggregateOpts); +export function mapAggregateAsync(array, arg1, arg2) { + const {fn, opts} = _reorganizeAggregateArguments(arg1, arg2); + const {promiseAll = Promise.all.bind(Promise), ...remainingOpts} = opts; + return _mapAggregate('async', promiseAll, array, fn, remainingOpts); } // Helper function for mapAggregate which holds code common between sync and @@ -462,15 +474,15 @@ export function _mapAggregate(mode, promiseAll, array, fn, aggregateOpts) { // 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 function filterAggregate(array, arg1, arg2) { + const {fn, opts} = _reorganizeAggregateArguments(arg1, arg2); + return _filterAggregate('sync', null, array, fn, opts); } -export async function filterAggregateAsync(array, fn, { - promiseAll = Promise.all.bind(Promise), - ...aggregateOpts -} = {}) { - return _filterAggregate('async', promiseAll, array, fn, aggregateOpts); +export async function filterAggregateAsync(array, arg1, arg2) { + const {fn, opts} = _reorganizeAggregateArguments(arg1, arg2); + const {promiseAll = Promise.all.bind(Promise), ...remainingOpts} = opts; + return _filterAggregate('async', promiseAll, array, fn, remainingOpts); } // Helper function for filterAggregate which holds code common between sync and @@ -530,20 +542,17 @@ function _filterAggregate(mode, promiseAll, array, fn, aggregateOpts) { // 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 withAggregate(arg1, arg2) { + const {fn, opts} = _reorganizeAggregateArguments(arg1, arg2); + return _withAggregate('sync', opts, fn); } -export function withAggregateAsync(aggregateOpts, fn) { - return _withAggregate('async', aggregateOpts, fn); +export function withAggregateAsync(arg1, arg2) { + const {fn, opts} = _reorganizeAggregateArguments(arg1, arg2); + return _withAggregate('async', opts, fn); } export function _withAggregate(mode, aggregateOpts, fn) { - if (typeof aggregateOpts === 'function') { - fn = aggregateOpts; - aggregateOpts = {}; - } - const aggregate = openAggregate(aggregateOpts); if (mode === 'sync') { @@ -628,27 +637,101 @@ export function showAggregate(topError, { } } -export function decorateErrorWithIndex(fn) { - return (x, index, array) => { +export function annotateError(error, ...callbacks) { + for (const callback of callbacks) { + error = callback(error) ?? error; + } + + return error; +} + +export function annotateErrorWithIndex(error, index) { + return Object.assign(error, { + [Symbol.for('hsmusic.annotateError.indexInSourceArray')]: + index, + + message: + `(${colors.yellow(`#${index + 1}`)}) ` + + error.message, + }); +} + +export function annotateErrorWithFile(error, file) { + return Object.assign(error, { + [Symbol.for('hsmusic.annotateError.file')]: + file, + + message: + error.message + + (error.message.includes('\n') ? '\n' : ' ') + + `(file: ${colors.bright(colors.blue(file))})`, + }); +} + +export function asyncAdaptiveDecorateError(fn, callback) { + if (typeof callback !== 'function') { + throw new Error(`Expected callback to be a function, got ${typeAppearance(callback)}`); + } + + const syncDecorated = function (...args) { try { - return fn(x, index, array); - } catch (error) { - error.message = `(${colors.yellow(`#${index + 1}`)}) ${error.message}`; - error[Symbol.for('hsmusic.decorate.indexInSourceArray')] = index; - throw error; + return fn(...args); + } catch (caughtError) { + throw callback(caughtError, ...args); } }; -} -export function decorateErrorWithCause(fn, cause) { - return (...args) => { + const asyncDecorated = async function(...args) { try { - return fn(...args); - } catch (error) { - error.cause = cause; - throw error; + return await fn(...args); + } catch (caughtError) { + throw callback(caughtError); } }; + + syncDecorated.async = asyncDecorated; + + return syncDecorated; +} + +export function decorateError(fn, callback) { + return asyncAdaptiveDecorateError(fn, callback); +} + +export function asyncDecorateError(fn, callback) { + return asyncAdaptiveDecorateError(fn, callback).async; +} + +export function decorateErrorWithAnnotation(fn, ...annotationCallbacks) { + return asyncAdaptiveDecorateError(fn, + (caughtError, ...args) => + annotateError(caughtError, + ...annotationCallbacks + .map(callback => error => callback(error, ...args)))); +} + +export function decorateErrorWithIndex(fn) { + return decorateErrorWithAnnotation(fn, + (caughtError, _value, index) => + annotateErrorWithIndex(caughtError, index)); +} + +export function decorateErrorWithCause(fn, cause) { + return asyncAdaptiveDecorateError(fn, + (caughtError) => + Object.assign(caughtError, {cause})); +} + +export function asyncDecorateErrorWithAnnotation(fn, ...annotationCallbacks) { + return decorateErrorWithAnnotation(fn, ...annotationCallbacks).async; +} + +export function asyncDecorateErrorWithIndex(fn) { + return decorateErrorWithIndex(fn).async; +} + +export function asyncDecorateErrorWithCause(fn, cause) { + return decorateErrorWithCause(fn, cause).async; } export function conditionallySuppressError(conditionFn, callbackFn) { |