From cc6fff8d198953b66ec984248cd1a4e02937b55b Mon Sep 17 00:00:00 2001 From: "(quasar) nebula" Date: Mon, 30 Oct 2023 18:24:08 -0300 Subject: util: add logicalCWD, logicalPathTo cli functions --- src/util/cli.js | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) (limited to 'src/util') 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); +} -- cgit 1.3.0-6-gf8a5 From 9f37683838ffc9f04b4e705d382a101b0b422412 Mon Sep 17 00:00:00 2001 From: "(quasar) nebula" Date: Mon, 6 Nov 2023 15:33:57 -0400 Subject: sugar: accept aggregateOpts and fn in either order --- src/util/sugar.js | 55 ++++++++++++++++++++++++++++++++----------------------- 1 file changed, 32 insertions(+), 23 deletions(-) (limited to 'src/util') diff --git a/src/util/sugar.js b/src/util/sugar.js index 3e39e98f..6718b697 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') { -- cgit 1.3.0-6-gf8a5 From 32f5dfa4b3c12dec18d0655160f9d49ca93b16d9 Mon Sep 17 00:00:00 2001 From: "(quasar) nebula" Date: Mon, 6 Nov 2023 15:34:52 -0400 Subject: sugar: separate annotateError functions & utilities --- src/util/sugar.js | 34 ++++++++++++++++++++++++++++++++-- 1 file changed, 32 insertions(+), 2 deletions(-) (limited to 'src/util') diff --git a/src/util/sugar.js b/src/util/sugar.js index 6718b697..d5f0fbd3 100644 --- a/src/util/sugar.js +++ b/src/util/sugar.js @@ -637,13 +637,31 @@ export function showAggregate(topError, { } } +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 decorateErrorWithIndex(fn) { return (x, index, array) => { try { return fn(x, index, array); } catch (error) { - error.message = `(${colors.yellow(`#${index + 1}`)}) ${error.message}`; - error[Symbol.for('hsmusic.decorate.indexInSourceArray')] = index; + annotateErrorWithIndex(error, index); throw error; } }; @@ -660,6 +678,18 @@ export function decorateErrorWithCause(fn, cause) { }; } +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 conditionallySuppressError(conditionFn, callbackFn) { return (...args) => { try { -- cgit 1.3.0-6-gf8a5 From 8d06ad886607e7acdf636e07719bd4fbae3de767 Mon Sep 17 00:00:00 2001 From: "(quasar) nebula" Date: Mon, 6 Nov 2023 16:28:37 -0400 Subject: sugar: expose and integrate async-adaptive error decorators --- src/util/sugar.js | 88 +++++++++++++++++++++++++++++++++++++++++-------------- 1 file changed, 66 insertions(+), 22 deletions(-) (limited to 'src/util') diff --git a/src/util/sugar.js b/src/util/sugar.js index d5f0fbd3..9646be37 100644 --- a/src/util/sugar.js +++ b/src/util/sugar.js @@ -656,28 +656,6 @@ export function annotateErrorWithIndex(error, index) { }); } -export function decorateErrorWithIndex(fn) { - return (x, index, array) => { - try { - return fn(x, index, array); - } catch (error) { - annotateErrorWithIndex(error, index); - throw error; - } - }; -} - -export function decorateErrorWithCause(fn, cause) { - return (...args) => { - try { - return fn(...args); - } catch (error) { - error.cause = cause; - throw error; - } - }; -} - export function annotateErrorWithFile(error, file) { return Object.assign(error, { [Symbol.for('hsmusic.annotateError.file')]: @@ -690,6 +668,72 @@ export function annotateErrorWithFile(error, 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(...args); + } catch (caughtError) { + throw callback(caughtError, ...args); + } + }; + + const asyncDecorated = async function(...args) { + try { + 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) { return (...args) => { try { -- cgit 1.3.0-6-gf8a5