From ab306affb06b9f94a2f6b8dc8607614949b3ab0e Mon Sep 17 00:00:00 2001 From: "(quasar) nebula" Date: Mon, 6 Nov 2023 15:36:57 -0400 Subject: yaml: tidy aggregate nesting and error syntax --- src/data/yaml.js | 196 +++++++++++++++++++++++++++++++------------------------ 1 file changed, 110 insertions(+), 86 deletions(-) diff --git a/src/data/yaml.js b/src/data/yaml.js index 0ffe968..37f3180 100644 --- a/src/data/yaml.js +++ b/src/data/yaml.js @@ -18,11 +18,11 @@ import T, { } from '#things'; import { + annotateErrorWithFile, conditionallySuppressError, decorateErrorWithIndex, empty, filterProperties, - mapAggregate, openAggregate, showAggregate, withAggregate, @@ -1119,15 +1119,30 @@ export async function loadAndProcessDataDocuments({dataPath}) { }); const wikiDataResult = {}; + const _getFileFromArgument = arg => + (typeof arg === 'object' + ? arg.file + : arg); + function decorateErrorWithFile(fn) { - return (x, index, array) => { + return (...args) => { try { - return fn(x, index, array); - } catch (error) { - error.message += - (error.message.includes('\n') ? '\n' : ' ') + - `(file: ${colors.bright(colors.blue(path.relative(dataPath, x.file)))})`; - throw error; + return fn(...args); + } catch (caughtError) { + const file = _getFileFromArgument(args[0]); + throw annotateErrorWithFile(caughtError, path.relative(dataPath, file)); + } + }; + } + + // Certified gensync moment. + function asyncDecorateErrorWithFile(fn) { + return async (...args) => { + try { + return await fn(...args); + } catch (caughtError) { + const file = _getFileFromArgument(args[0]); + throw annotateErrorWithFile(caughtError, path.relative(dataPath, file)); } }; } @@ -1135,7 +1150,7 @@ export async function loadAndProcessDataDocuments({dataPath}) { for (const dataStep of dataSteps) { await processDataAggregate.nestAsync( {message: `Errors during data step: ${colors.bright(dataStep.title)}`}, - async ({call, callAsync, map, mapAsync, push, nest}) => { + async ({call, callAsync, map, mapAsync, push}) => { const {documentMode} = dataStep; if (!Object.values(documentModes).includes(documentMode)) { @@ -1323,8 +1338,8 @@ export async function loadAndProcessDataDocuments({dataPath}) { throw new Error(`Expected 'files' property for ${documentMode.toString()}`); } - let files = ( - typeof dataStep.files === 'function' + const filesFromDataStep = + (typeof dataStep.files === 'function' ? await callAsync(() => dataStep.files(dataPath).then( files => files, @@ -1335,101 +1350,110 @@ export async function loadAndProcessDataDocuments({dataPath}) { throw error; } })) - : dataStep.files - ); + : dataStep.files); - if (!files) { - return; - } + const filesUnderDataPath = + filesFromDataStep + .map(file => path.join(dataPath, file)); - files = files.map((file) => path.join(dataPath, file)); - - const readResults = await mapAsync( - files, - (file) => readFile(file, 'utf-8').then((contents) => ({file, contents})), - {message: `Errors reading data files`}); - - let yamlResults = map( - readResults, - decorateErrorWithFile(({file, contents}) => ({ - file, - documents: yaml.loadAll(contents), - })), - {message: `Errors parsing data files as valid YAML`}); - - yamlResults = yamlResults.map(({file, documents}) => { - const {documents: filteredDocuments, aggregate} = filterBlankDocuments(documents); - call(decorateErrorWithFile(aggregate.close), {file}); - return {file, documents: filteredDocuments}; - }); + const yamlResults = []; + + await mapAsync(filesUnderDataPath, {message: `Errors loading data files`}, + asyncDecorateErrorWithFile(async file => { + let contents; + try { + contents = await readFile(file, 'utf-8'); + } catch (caughtError) { + throw new Error(`Failed to read data file`, {cause: caughtError}); + } + + let documents; + try { + documents = yaml.loadAll(contents); + } catch (caughtError) { + throw new Error(`Failed to parse valid YAML`, {cause: caughtError}); + } + + const {documents: filteredDocuments, aggregate: filterAggregate} = + filterBlankDocuments(documents); + + try { + filterAggregate.close(); + } catch (caughtError) { + // Blank documents aren't a critical error, they're just something + // that should be noted - the (filtered) documents still get pushed. + const pathToFile = path.relative(dataPath, file); + annotateErrorWithFile(caughtError, pathToFile); + push(caughtError); + } + + yamlResults.push({file, documents: filteredDocuments}); + })); const processResults = []; switch (documentMode) { case documentModes.headerAndEntries: - map(yamlResults, decorateErrorWithFile(({documents}) => { - const headerDocument = documents[0]; - const entryDocuments = documents.slice(1).filter(Boolean); - - if (!headerDocument) - throw new Error(`Missing header document (empty file or erroneously starting with "---"?)`); - - // This'll be decorated with the file, and groups together any - // errors from processing the header and entry documents. - const fileAggregate = - openAggregate({message: `Errors processing documents`}); - - const {thing: headerObject, aggregate: headerAggregate} = - dataStep.processHeaderDocument(headerDocument); - - try { - headerAggregate.close() - } catch (caughtError) { - caughtError.message = `(${colors.yellow(`header`)}) ${caughtError.message}`; - fileAggregate.push(caughtError); - } + map(yamlResults, {message: `Errors processing documents in data files`}, + decorateErrorWithFile(({documents}) => { + const headerDocument = documents[0]; + const entryDocuments = documents.slice(1).filter(Boolean); - const entryObjects = []; + if (!headerDocument) + throw new Error(`Missing header document (empty file or erroneously starting with "---"?)`); - for (let index = 0; index < entryDocuments.length; index++) { - const entryDocument = entryDocuments[index]; + withAggregate({message: `Errors processing documents`}, ({push}) => { + const {thing: headerObject, aggregate: headerAggregate} = + dataStep.processHeaderDocument(headerDocument); - const {thing: entryObject, aggregate: entryAggregate} = - dataStep.processEntryDocument(entryDocument); + try { + headerAggregate.close(); + } catch (caughtError) { + caughtError.message = `(${colors.yellow(`header`)}) ${caughtError.message}`; + push(caughtError); + } - entryObjects.push(entryObject); + const entryObjects = []; - try { - entryAggregate.close(); - } catch (caughtError) { - caughtError.message = `(${colors.yellow(`entry #${index + 1}`)}) ${caughtError.message}`; - fileAggregate.push(caughtError); - } - } + for (let index = 0; index < entryDocuments.length; index++) { + const entryDocument = entryDocuments[index]; - processResults.push({ - header: headerObject, - entries: entryObjects, - }); + const {thing: entryObject, aggregate: entryAggregate} = + dataStep.processEntryDocument(entryDocument); - fileAggregate.close(); - }), {message: `Errors processing documents in data files`}); + entryObjects.push(entryObject); + + try { + entryAggregate.close(); + } catch (caughtError) { + caughtError.message = `(${colors.yellow(`entry #${index + 1}`)}) ${caughtError.message}`; + push(caughtError); + } + } + + processResults.push({ + header: headerObject, + entries: entryObjects, + }); + }); + })); break; case documentModes.onePerFile: - map(yamlResults, decorateErrorWithFile(({documents}) => { - if (documents.length > 1) - throw new Error(`Only expected one document to be present per file, got ${documents.length} here`); + map(yamlResults, {message: `Errors processing data files as valid documents`}, + decorateErrorWithFile(({documents}) => { + if (documents.length > 1) + throw new Error(`Only expected one document to be present per file, got ${documents.length} here`); - if (empty(documents) || !documents[0]) - throw new Error(`Expected a document, this file is empty`); + if (empty(documents) || !documents[0]) + throw new Error(`Expected a document, this file is empty`); - const {thing, aggregate} = - dataStep.processDocument(documents[0]); + const {thing, aggregate} = + dataStep.processDocument(documents[0]); - processResults.push(thing); - aggregate.close(); - }), {message: `Errors processing data files as valid documents`}); + processResults.push(thing); + aggregate.close(); + })); break; } -- cgit 1.3.0-6-gf8a5