diff options
Diffstat (limited to 'src/data')
-rw-r--r-- | src/data/cacheable-object.js | 93 | ||||
-rw-r--r-- | src/data/patches.js | 7 | ||||
-rw-r--r-- | src/data/serialize.js | 15 | ||||
-rw-r--r-- | src/data/things.js | 73 | ||||
-rw-r--r-- | src/data/validators.js | 94 | ||||
-rw-r--r-- | src/data/yaml.js | 620 |
6 files changed, 362 insertions, 540 deletions
diff --git a/src/data/cacheable-object.js b/src/data/cacheable-object.js index 688d8a0f..04e029f0 100644 --- a/src/data/cacheable-object.js +++ b/src/data/cacheable-object.js @@ -1,7 +1,3 @@ -/** - * @format - */ - // Generally extendable class for caching properties and handling dependencies, // with a few key properties: // @@ -112,9 +108,7 @@ export default class CacheableObject { get: (obj, key) => { if (!Object.hasOwn(obj, key)) { if (key !== 'constructor') { - CacheableObject._invalidAccesses.add( - `(${obj.constructor.name}).${key}` - ); + CacheableObject._invalidAccesses.add(`(${obj.constructor.name}).${key}`); } } return obj[key]; @@ -124,9 +118,7 @@ export default class CacheableObject { } #initializeUpdatingPropertyValues() { - for (const [property, descriptor] of Object.entries( - this.constructor.propertyDescriptors - )) { + for (const [property, descriptor] of Object.entries(this.constructor.propertyDescriptors)) { const {flags, update} = descriptor; if (!flags.update) { @@ -143,14 +135,10 @@ export default class CacheableObject { #defineProperties() { if (!this.constructor.propertyDescriptors) { - throw new Error( - `Expected constructor ${this.constructor.name} to define propertyDescriptors` - ); + throw new Error(`Expected constructor ${this.constructor.name} to define propertyDescriptors`); } - for (const [property, descriptor] of Object.entries( - this.constructor.propertyDescriptors - )) { + for (const [property, descriptor] of Object.entries(this.constructor.propertyDescriptors)) { const {flags} = descriptor; const definition = { @@ -159,13 +147,11 @@ export default class CacheableObject { }; if (flags.update) { - definition.set = - this.#getUpdateObjectDefinitionSetterFunction(property); + definition.set = this.#getUpdateObjectDefinitionSetterFunction(property); } if (flags.expose) { - definition.get = - this.#getExposeObjectDefinitionGetterFunction(property); + definition.get = this.#getExposeObjectDefinitionGetterFunction(property); } Object.defineProperty(this, property, definition); @@ -198,9 +184,11 @@ export default class CacheableObject { throw new TypeError(`Validation failed for value ${newValue}`); } } catch (error) { - error.message = `Property ${color.green(property)} (${inspect( - this[property] - )} -> ${inspect(newValue)}): ${error.message}`; + error.message = [ + `Property ${color.green(property)}`, + `(${inspect(this[property])} -> ${inspect(newValue)}):`, + error.message + ].join(' '); throw error; } } @@ -215,8 +203,12 @@ export default class CacheableObject { } #invalidateCachesDependentUpon(property) { - for (const invalidate of this.#propertyUpdateCacheInvalidators[property] || - []) { + const invalidators = this.#propertyUpdateCacheInvalidators[property]; + if (!invalidators) { + return; + } + + for (const invalidate of invalidators) { invalidate(); } } @@ -236,9 +228,7 @@ export default class CacheableObject { } }; } else if (!flags.update && !compute) { - throw new Error( - `Exposed property ${property} does not update and is missing compute function` - ); + throw new Error(`Exposed property ${property} does not update and is missing compute function`); } else { return () => this.#propertyUpdateValues[property]; } @@ -253,30 +243,31 @@ export default class CacheableObject { if (flags.update && !transform) { return null; } else if (flags.update && compute) { - throw new Error( - `Updating property ${property} has compute function, should be formatted as transform` - ); + throw new Error(`Updating property ${property} has compute function, should be formatted as transform`); } else if (!flags.update && !compute) { - throw new Error( - `Exposed property ${property} does not update and is missing compute function` - ); + throw new Error(`Exposed property ${property} does not update and is missing compute function`); } - const dependencyKeys = expose.dependencies || []; - const dependencyGetters = dependencyKeys.map((key) => () => [ - key, - this.#propertyUpdateValues[key], - ]); - const getAllDependencies = () => - Object.fromEntries( - dependencyGetters - .map((f) => f()) - .concat([[this.constructor.instance, this]]) - ); + let getAllDependencies; + + const dependencyKeys = expose.dependencies; + if (dependencyKeys?.length > 0) { + const reflectionEntry = [this.constructor.instance, this]; + const dependencyGetters = dependencyKeys + .map(key => () => [key, this.#propertyUpdateValues[key]]); + + getAllDependencies = () => + Object.fromEntries(dependencyGetters + .map(f => f()) + .concat([reflectionEntry])); + } else { + const allDependencies = {[this.constructor.instance]: this}; + Object.freeze(allDependencies); + getAllDependencies = () => allDependencies; + } if (flags.update) { - return () => - transform(this.#propertyUpdateValues[property], getAllDependencies()); + return () => transform(this.#propertyUpdateValues[property], getAllDependencies()); } else { return () => compute(getAllDependencies()); } @@ -321,14 +312,14 @@ export default class CacheableObject { return; } - if (!obj.constructor.propertyDescriptors) { + const {propertyDescriptors} = obj.constructor; + + if (!propertyDescriptors) { console.warn('Missing property descriptors:', obj); return; } - for (const [property, descriptor] of Object.entries( - obj.constructor.propertyDescriptors - )) { + for (const [property, descriptor] of Object.entries(propertyDescriptors)) { const {flags} = descriptor; if (!flags.expose) { diff --git a/src/data/patches.js b/src/data/patches.js index dc757fa9..feeaf39b 100644 --- a/src/data/patches.js +++ b/src/data/patches.js @@ -1,5 +1,3 @@ -/** @format */ - // --> Patch export class Patch { @@ -137,6 +135,7 @@ export class PatchManager extends Patch { this.#externalInputPatch = new PatchManagerExternalInputPatch({ manager: this, }); + this.#externalOutputPatch = new PatchManagerExternalOutputPatch({ manager: this, }); @@ -184,9 +183,7 @@ export class PatchManager extends Patch { addManagedInput(patchWithInput, inputName, patchWithOutput, outputName) { if (patchWithInput.manager !== this || patchWithOutput.manager !== this) { - throw new Error( - `Input and output patches must belong to same manager (this)` - ); + throw new Error(`Input and output patches must belong to same manager (this)`); } const input = patchWithInput.inputs[inputName]; diff --git a/src/data/serialize.js b/src/data/serialize.js index a4206fd0..52aacb07 100644 --- a/src/data/serialize.js +++ b/src/data/serialize.js @@ -1,6 +1,4 @@ -/** @format */ - -// serialize-util.js: simple interface and utility functions for converting +// serialize.js: simple interface and utility functions for converting // Things into a directly serializeable format // Utility functions @@ -27,17 +25,14 @@ export const serializeDescriptors = Symbol(); export function serializeThing(thing) { const descriptors = thing.constructor[serializeDescriptors]; + if (!descriptors) { - throw new Error( - `Constructor ${thing.constructor.name} does not provide serialize descriptors` - ); + throw new Error(`Constructor ${thing.constructor.name} does not provide serialize descriptors`); } return Object.fromEntries( - Object.entries(descriptors).map(([property, transform]) => [ - property, - transform(thing[property]), - ]) + Object.entries(descriptors) + .map(([property, transform]) => [property, transform(thing[property])]) ); } diff --git a/src/data/things.js b/src/data/things.js index 4aa684d4..33880460 100644 --- a/src/data/things.js +++ b/src/data/things.js @@ -1,5 +1,3 @@ -/** @format */ - // things.js: class definitions for various object types used across the wiki, // most of which correspond to an output page, such as Track, Album, Artist @@ -236,9 +234,7 @@ Thing.common = { referenceList: (thingClass) => { const {[Thing.referenceType]: referenceType} = thingClass; if (!referenceType) { - throw new Error( - `The passed constructor ${thingClass.name} doesn't define Thing.referenceType!` - ); + throw new Error(`The passed constructor ${thingClass.name} doesn't define Thing.referenceType!`); } return { @@ -251,9 +247,7 @@ Thing.common = { singleReference: (thingClass) => { const {[Thing.referenceType]: referenceType} = thingClass; if (!referenceType) { - throw new Error( - `The passed constructor ${thingClass.name} doesn't define Thing.referenceType!` - ); + throw new Error(`The passed constructor ${thingClass.name} doesn't define Thing.referenceType!`); } return { @@ -441,15 +435,13 @@ Thing.common = { // constructor's [Thing.referenceType] as the prefix. This will throw an error // if the thing's directory isn't yet provided/computable. Thing.getReference = function (thing) { - if (!thing.constructor[Thing.referenceType]) - throw TypeError( - `Passed Thing is ${thing.constructor.name}, which provides no [Thing.referenceType]` - ); + if (!thing.constructor[Thing.referenceType]) { + throw TypeError(`Passed Thing is ${thing.constructor.name}, which provides no [Thing.referenceType]`); + } - if (!thing.directory) - throw TypeError( - `Passed ${thing.constructor.name} is missing its directory` - ); + if (!thing.directory) { + throw TypeError(`Passed ${thing.constructor.name} is missing its directory`); + } return `${thing.constructor[Thing.referenceType]}:${thing.directory}`; }; @@ -735,10 +727,11 @@ Track.propertyDescriptors = { expose: { dependencies: ['albumData', 'coverArtistContribsByRef'], - transform: ( - hasCoverArt, - {albumData, coverArtistContribsByRef, [Track.instance]: track} - ) => + transform: (hasCoverArt, { + albumData, + coverArtistContribsByRef, + [Track.instance]: track, + }) => Track.hasCoverArt( track, albumData, @@ -755,15 +748,12 @@ Track.propertyDescriptors = { expose: { dependencies: ['albumData', 'coverArtistContribsByRef'], - transform: ( - coverArtFileExtension, - { - albumData, - coverArtistContribsByRef, - hasCoverArt, - [Track.instance]: track, - } - ) => + transform: (coverArtFileExtension, { + albumData, + coverArtistContribsByRef, + hasCoverArt, + [Track.instance]: track, + }) => coverArtFileExtension ?? (Track.hasCoverArt( track, @@ -851,10 +841,11 @@ Track.propertyDescriptors = { expose: { dependencies: ['albumData', 'dateFirstReleased'], - transform: ( - coverArtDate, - {albumData, dateFirstReleased, [Track.instance]: track} - ) => + transform: (coverArtDate, { + albumData, + dateFirstReleased, + [Track.instance]: track, + }) => coverArtDate ?? dateFirstReleased ?? Track.findAlbum(track, albumData)?.trackArtDate ?? @@ -1691,9 +1682,7 @@ Object.assign(Language.prototype, { formatString(key, args = {}) { if (this.strings && !this.strings_htmlEscaped) { - throw new Error( - `HTML-escaped strings unavailable - please ensure escapeHTML function is provided` - ); + throw new Error(`HTML-escaped strings unavailable - please ensure escapeHTML function is provided`); } return this.formatStringHelper(this.strings_htmlEscaped, key, args); @@ -1780,12 +1769,7 @@ Object.assign(Language.prototype, { formatIndex(value) { this.assertIntlAvailable('intl_pluralOrdinal'); - return this.formatString( - 'count.index.' + this.intl_pluralOrdinal.select(value), - { - index: value, - } - ); + return this.formatString('count.index.' + this.intl_pluralOrdinal.select(value), {index: value}); }, formatNumber(value) { @@ -1803,10 +1787,7 @@ Object.assign(Language.prototype, { ? this.formatString('count.words.thousand', {words: num}) : this.formatString('count.words', {words: num}); - return this.formatString( - 'count.words.withUnit.' + this.getUnitForm(value), - {words} - ); + return this.formatString('count.words.withUnit.' + this.getUnitForm(value), {words}); }, // Conjunction list: A, B, and C diff --git a/src/data/validators.js b/src/data/validators.js index 8d922399..5c357c83 100644 --- a/src/data/validators.js +++ b/src/data/validators.js @@ -1,5 +1,3 @@ -/** @format */ - import {withAggregate} from '../util/sugar.js'; import {color, ENABLE_COLOR} from '../util/cli.js'; @@ -104,9 +102,7 @@ export function isInstance(value, constructor) { isObject(value); if (!(value instanceof constructor)) - throw new TypeError( - `Expected ${constructor.name}, got ${value.constructor.name}` - ); + throw new TypeError(`Expected ${constructor.name}, got ${value.constructor.name}`); return true; } @@ -142,9 +138,7 @@ function validateArrayItemsHelper(itemValidator) { throw new Error(`Expected validator to return true`); } } catch (error) { - error.message = `(index: ${color.green(index)}, item: ${inspect(item)}) ${ - error.message - }`; + error.message = `(index: ${color.green(index)}, item: ${inspect(item)}) ${error.message}`; throw error; } }; @@ -174,10 +168,8 @@ export function isColor(color) { isStringNonEmpty(color); if (color.startsWith('#')) { - if (![1 + 3, 1 + 4, 1 + 6, 1 + 8].includes(color.length)) - throw new TypeError( - `Expected #rgb, #rgba, #rrggbb, or #rrggbbaa, got length ${color.length}` - ); + if (![4, 5, 7, 9].includes(color.length)) + throw new TypeError(`Expected #rgb, #rgba, #rrggbb, or #rrggbbaa, got length ${color.length}`); if (/[^0-9a-fA-F]/.test(color.slice(1))) throw new TypeError(`Expected hexadecimal digits`); @@ -204,37 +196,26 @@ export function validateProperties(spec) { if (Array.isArray(object)) throw new TypeError(`Expected an object, got array`); - withAggregate( - {message: `Errors validating object properties`}, - ({call}) => { - for (const [specKey, specValidator] of specEntries) { - call(() => { - const value = object[specKey]; - try { - specValidator(value); - } catch (error) { - error.message = `(key: ${color.green(specKey)}, value: ${inspect( - value - )}) ${error.message}`; - throw error; - } - }); - } + withAggregate({message: `Errors validating object properties`}, ({call}) => { + for (const [specKey, specValidator] of specEntries) { + call(() => { + const value = object[specKey]; + try { + specValidator(value); + } catch (error) { + error.message = `(key: ${color.green(specKey)}, value: ${inspect(value)}) ${error.message}`; + throw error; + } + }); + } - const unknownKeys = Object.keys(object).filter( - (key) => !specKeys.includes(key) - ); - if (unknownKeys.length > 0) { - call(() => { - throw new Error( - `Unknown keys present (${ - unknownKeys.length - }): [${unknownKeys.join(', ')}]` - ); - }); - } + const unknownKeys = Object.keys(object).filter((key) => !specKeys.includes(key)); + if (unknownKeys.length > 0) { + call(() => { + throw new Error(`Unknown keys present (${unknownKeys.length}): [${unknownKeys.join(', ')}]`); + }); } - ); + }); return true; }; @@ -243,7 +224,9 @@ export function validateProperties(spec) { export const isContribution = validateProperties({ who: isArtistRef, what: (value) => - value === undefined || value === null || isStringNonEmpty(value), + value === undefined || + value === null || + isStringNonEmpty(value), }); export const isContributionList = validateArrayItems(isContribution); @@ -251,7 +234,9 @@ export const isContributionList = validateArrayItems(isContribution); export const isAdditionalFile = validateProperties({ title: isString, description: (value) => - value === undefined || value === null || isString(value), + value === undefined || + value === null || + isString(value), files: validateArrayItems(isString), }); @@ -274,9 +259,7 @@ export function isDirectory(directory) { isStringNonEmpty(directory); if (directory.match(/[^a-zA-Z0-9_-]/)) - throw new TypeError( - `Expected only letters, numbers, dash, and underscore, got "${directory}"` - ); + throw new TypeError(`Expected only letters, numbers, dash, and underscore, got "${directory}"`); return true; } @@ -331,16 +314,14 @@ export function validateReference(type = 'track') { if (!match) throw new TypeError(`Malformed reference`); - const { - groups: {typePart, directoryPart}, - } = match; + const {groups: {typePart, directoryPart}} = match; - if (typePart && typePart !== type) - throw new TypeError( - `Expected ref to begin with "${type}:", got "${typePart}:"` - ); + if (typePart) { + if (typePart !== type) + throw new TypeError(`Expected ref to begin with "${type}:", got "${typePart}:"`); - if (typePart) isDirectory(directoryPart); + isDirectory(directoryPart); + } isName(ref); @@ -381,9 +362,6 @@ export function oneOf(...checks) { error.check = check; errors.push(error); } - throw new AggregateError( - errors, - `Expected one of ${checks.length} possible checks, but none were true` - ); + throw new AggregateError(errors, `Expected one of ${checks.length} possible checks, but none were true`); }; } diff --git a/src/data/yaml.js b/src/data/yaml.js index e18b7334..2adce50b 100644 --- a/src/data/yaml.js +++ b/src/data/yaml.js @@ -1,5 +1,3 @@ -/** @format */ - // yaml.js - specification for HSMusic YAML data file format and utilities for // loading and processing YAML files and documents @@ -112,11 +110,8 @@ function makeProcessDocument( // Invert the property-field mapping, since it'll come in handy for // assigning update() source values later. const fieldPropertyMapping = Object.fromEntries( - Object.entries(propertyFieldMapping).map(([property, field]) => [ - field, - property, - ]) - ); + Object.entries(propertyFieldMapping) + .map(([property, field]) => [field, property])); const decorateErrorWithName = (fn) => { const nameField = propertyFieldMapping['name']; @@ -136,9 +131,8 @@ function makeProcessDocument( }; return decorateErrorWithName((document) => { - const documentEntries = Object.entries(document).filter( - ([field]) => !ignoredFields.includes(field) - ); + const documentEntries = Object.entries(document) + .filter(([field]) => !ignoredFields.includes(field)); const unknownFields = documentEntries .map(([field]) => field) @@ -167,22 +161,17 @@ function makeProcessDocument( const thing = Reflect.construct(thingClass, []); - withAggregate( - {message: `Errors applying ${color.green(thingClass.name)} properties`}, - ({call}) => { - for (const [property, value] of Object.entries(sourceProperties)) { - call(() => (thing[property] = value)); - } + withAggregate({message: `Errors applying ${color.green(thingClass.name)} properties`}, ({call}) => { + for (const [property, value] of Object.entries(sourceProperties)) { + call(() => (thing[property] = value)); } - ); + }); return thing; }); } -makeProcessDocument.UnknownFieldsError = class UnknownFieldsError extends ( - Error -) { +makeProcessDocument.UnknownFieldsError = class UnknownFieldsError extends Error { constructor(fields) { super(`Unknown fields present: ${fields.join(', ')}`); this.fields = fields; @@ -191,13 +180,13 @@ makeProcessDocument.UnknownFieldsError = class UnknownFieldsError extends ( export const processAlbumDocument = makeProcessDocument(Album, { fieldTransformations: { - Artists: parseContributors, + 'Artists': parseContributors, 'Cover Artists': parseContributors, 'Default Track Cover Artists': parseContributors, 'Wallpaper Artists': parseContributors, 'Banner Artists': parseContributors, - Date: (value) => new Date(value), + 'Date': (value) => new Date(value), 'Date Added': (value) => new Date(value), 'Cover Art Date': (value) => new Date(value), 'Default Track Cover Art Date': (value) => new Date(value), @@ -263,13 +252,13 @@ export const processTrackGroupDocument = makeProcessDocument(TrackGroup, { export const processTrackDocument = makeProcessDocument(Track, { fieldTransformations: { - Duration: getDurationInSeconds, + 'Duration': getDurationInSeconds, 'Date First Released': (value) => new Date(value), 'Cover Art Date': (value) => new Date(value), - Artists: parseContributors, - Contributors: parseContributors, + 'Artists': parseContributors, + 'Contributors': parseContributors, 'Cover Artists': parseContributors, 'Additional Files': parseAdditionalFiles, @@ -323,9 +312,9 @@ export const processArtistDocument = makeProcessDocument(Artist, { export const processFlashDocument = makeProcessDocument(Flash, { fieldTransformations: { - Date: (value) => new Date(value), + 'Date': (value) => new Date(value), - Contributors: parseContributors, + 'Contributors': parseContributors, }, propertyFieldMapping: { @@ -354,7 +343,7 @@ export const processFlashActDocument = makeProcessDocument(FlashAct, { export const processNewsEntryDocument = makeProcessDocument(NewsEntry, { fieldTransformations: { - Date: (value) => new Date(value), + 'Date': (value) => new Date(value), }, propertyFieldMapping: { @@ -421,16 +410,13 @@ export const processWikiInfoDocument = makeProcessDocument(WikiInfo, { }, }); -export const processHomepageLayoutDocument = makeProcessDocument( - HomepageLayout, - { - propertyFieldMapping: { - sidebarContent: 'Sidebar Content', - }, +export const processHomepageLayoutDocument = makeProcessDocument(HomepageLayout, { + propertyFieldMapping: { + sidebarContent: 'Sidebar Content', + }, - ignoredFields: ['Homepage'], - } -); + ignoredFields: ['Homepage'], +}); export function makeProcessHomepageLayoutRowDocument(rowClass, spec) { return makeProcessDocument(rowClass, { @@ -459,9 +445,8 @@ export const homepageLayoutRowTypeProcessMapping = { export function processHomepageLayoutRowDocument(document) { const type = document['Type']; - const match = Object.entries(homepageLayoutRowTypeProcessMapping).find( - ([key]) => key === type - ); + const match = Object.entries(homepageLayoutRowTypeProcessMapping) + .find(([key]) => key === type); if (!match) { throw new TypeError(`No processDocument function for row type ${type}!`); @@ -507,14 +492,9 @@ export function parseAdditionalFiles(array) { export function parseCommentary(text) { if (text) { - const lines = String(text).split('\n'); + const lines = String(text.trim()).split('\n'); if (!lines[0].replace(/<\/b>/g, '').includes(':</i>')) { - return { - error: `An entry is missing commentary citation: "${lines[0].slice( - 0, - 40 - )}..."`, - }; + throw new Error(`Missing commentary citation: "${lines[0].slice(0, 40)}..."`); } return text; } else { @@ -547,9 +527,7 @@ export function parseContributors(contributors) { const badContributor = contributors.find((val) => typeof val === 'string'); if (badContributor) { - return { - error: `An entry has an incorrectly formatted contributor, "${badContributor}".`, - }; + throw new Error(`Incorrectly formatted contribution: "${badContributor}".`); } if (contributors.length === 1 && contributors[0].who === 'none') { @@ -565,13 +543,17 @@ function parseDimensions(string) { } const parts = string.split(/[x,* ]+/g); - if (parts.length !== 2) - throw new Error(`Invalid dimensions: ${string} (expected width & height)`); + + if (parts.length !== 2) { + throw new Error(`Invalid dimensions: ${string} (expected "width & height")`); + } + const nums = parts.map((part) => Number(part.trim())); - if (nums.includes(NaN)) - throw new Error( - `Invalid dimensions: ${string} (couldn't parse as numbers)` - ); + + if (nums.includes(NaN)) { + throw new Error(`Invalid dimensions: ${string} (couldn't parse as numbers)`); + } + return nums; } @@ -671,7 +653,7 @@ export const dataSteps = [ files: async (dataPath) => ( await findFiles(path.join(dataPath, DATA_ALBUM_DIRECTORY), { - filter: f => path.extname(f) === '.yaml', + filter: (f) => path.extname(f) === '.yaml', joinParentDirectory: false, }) ).map(file => path.join(DATA_ALBUM_DIRECTORY, file)), @@ -759,16 +741,14 @@ export const dataSteps = [ const artistAliasData = results.flatMap((artist) => { const origRef = Thing.getReference(artist); - return ( - artist.aliasNames?.map((name) => { - const alias = new Artist(); - alias.name = name; - alias.isAlias = true; - alias.aliasedArtistRef = origRef; - alias.artistData = artistData; - return alias; - }) ?? [] - ); + return artist.aliasNames?.map((name) => { + const alias = new Artist(); + alias.name = name; + alias.isAlias = true; + alias.aliasedArtistRef = origRef; + alias.artistData = artistData; + return alias; + }) ?? []; }); return {artistData, artistAliasData}; @@ -856,9 +836,7 @@ export const dataSteps = [ } const groupData = results.filter((x) => x instanceof Group); - const groupCategoryData = results.filter( - (x) => x instanceof GroupCategory - ); + const groupCategoryData = results.filter((x) => x instanceof GroupCategory); return {groupData, groupCategoryData}; }, @@ -945,9 +923,7 @@ export async function loadAndProcessDataDocuments({dataPath}) { } catch (error) { error.message += (error.message.includes('\n') ? '\n' : ' ') + - `(file: ${color.bright( - color.blue(path.relative(dataPath, x.file)) - )})`; + `(file: ${color.bright(color.blue(path.relative(dataPath, x.file)))})`; throw error; } }; @@ -968,17 +944,14 @@ export async function loadAndProcessDataDocuments({dataPath}) { documentMode === documentModes.oneDocumentTotal ) { if (!dataStep.file) { - throw new Error( - `Expected 'file' property for ${documentMode.toString()}` - ); + throw new Error(`Expected 'file' property for ${documentMode.toString()}`); } const file = path.join( dataPath, typeof dataStep.file === 'function' ? await callAsync(dataStep.file, dataPath) - : dataStep.file - ); + : dataStep.file); const readResult = await callAsync(readFile, file, 'utf-8'); @@ -1005,8 +978,7 @@ export async function loadAndProcessDataDocuments({dataPath}) { const {result, aggregate} = mapAggregate( yamlResult, decorateErrorWithIndex(dataStep.processDocument), - {message: `Errors processing documents`} - ); + {message: `Errors processing documents`}); processResults = result; call(aggregate.close); } @@ -1023,9 +995,7 @@ export async function loadAndProcessDataDocuments({dataPath}) { } if (!dataStep.files) { - throw new Error( - `Expected 'files' property for ${documentMode.toString()}` - ); + throw new Error(`Expected 'files' property for ${documentMode.toString()}`); } let files = ( @@ -1042,8 +1012,7 @@ export async function loadAndProcessDataDocuments({dataPath}) { const readResults = await mapAsync( files, - (file) => - readFile(file, 'utf-8').then((contents) => ({file, contents})), + (file) => readFile(file, 'utf-8').then((contents) => ({file, contents})), {message: `Errors reading data files`} ); @@ -1059,82 +1028,76 @@ export async function loadAndProcessDataDocuments({dataPath}) { let processResults; if (documentMode === documentModes.headerAndEntries) { - nest( - {message: `Errors processing data files as valid documents`}, - ({call, map}) => { - processResults = []; - - yamlResults.forEach(({file, documents}) => { - const [headerDocument, ...entryDocuments] = documents; - - const header = call( - decorateErrorWithFile(({document}) => - dataStep.processHeaderDocument(document) - ), - {file, document: headerDocument} - ); - - // Don't continue processing files whose header - // document is invalid - the entire file is excempt - // from data in this case. - if (!header) { - return; - } - - const entries = map( - entryDocuments.map((document) => ({file, document})), - decorateErrorWithFile( - decorateErrorWithIndex(({document}) => - dataStep.processEntryDocument(document) - ) - ), - {message: `Errors processing entry documents`} - ); + nest({message: `Errors processing data files as valid documents`}, ({call, map}) => { + processResults = []; + + yamlResults.forEach(({file, documents}) => { + const [headerDocument, ...entryDocuments] = documents; + + const header = call( + decorateErrorWithFile(({document}) => + dataStep.processHeaderDocument(document) + ), + {file, document: headerDocument} + ); + + // Don't continue processing files whose header + // document is invalid - the entire file is excempt + // from data in this case. + if (!header) { + return; + } - // Entries may be incomplete (i.e. any errored - // documents won't have a processed output - // represented here) - this is intentional! By - // principle, partial output is preferred over - // erroring an entire file. - processResults.push({header, entries}); - }); - } - ); + const entries = map( + entryDocuments.map((document) => ({file, document})), + decorateErrorWithFile( + decorateErrorWithIndex(({document}) => + dataStep.processEntryDocument(document) + ) + ), + {message: `Errors processing entry documents`} + ); + + // Entries may be incomplete (i.e. any errored + // documents won't have a processed output + // represented here) - this is intentional! By + // principle, partial output is preferred over + // erroring an entire file. + processResults.push({header, entries}); + }); + }); } if (documentMode === documentModes.onePerFile) { - nest( - {message: `Errors processing data files as valid documents`}, - ({call}) => { - processResults = []; - - yamlResults.forEach(({file, documents}) => { - if (documents.length > 1) { - call( - decorateErrorWithFile(() => { - throw new Error( - `Only expected one document to be present per file` - ); - }) - ); - return; - } - - const result = call( - decorateErrorWithFile(({document}) => - dataStep.processDocument(document) - ), - {file, document: documents[0]} + nest({message: `Errors processing data files as valid documents`}, ({call}) => { + processResults = []; + + yamlResults.forEach(({file, documents}) => { + if (documents.length > 1) { + call( + decorateErrorWithFile(() => { + throw new Error( + `Only expected one document to be present per file` + ); + }) ); + return; + } - if (!result) { - return; - } + const result = call( + decorateErrorWithFile(({document}) => + dataStep.processDocument(document) + ), + {file, document: documents[0]} + ); - processResults.push(result); - }); - } - ); + if (!result) { + return; + } + + processResults.push(result); + }); + }); } const saveResult = call(dataStep.save, processResults); @@ -1158,9 +1121,10 @@ export async function loadAndProcessDataDocuments({dataPath}) { export function linkWikiDataArrays(wikiData) { function assignWikiData(things, ...keys) { for (let i = 0; i < things.length; i++) { + const thing = things[i]; for (let j = 0; j < keys.length; j++) { const key = keys[j]; - things[i][key] = wikiData[key]; + thing[key] = wikiData[key]; } } } @@ -1169,32 +1133,11 @@ export function linkWikiDataArrays(wikiData) { assignWikiData([WD.wikiInfo], 'groupData'); - assignWikiData( - WD.albumData, - 'artistData', - 'artTagData', - 'groupData', - 'trackData' - ); - WD.albumData.forEach((album) => - assignWikiData(album.trackGroups, 'trackData') - ); - - assignWikiData( - WD.trackData, - 'albumData', - 'artistData', - 'artTagData', - 'flashData', - 'trackData' - ); - assignWikiData( - WD.artistData, - 'albumData', - 'artistData', - 'flashData', - 'trackData' - ); + assignWikiData(WD.albumData, 'artistData', 'artTagData', 'groupData', 'trackData'); + WD.albumData.forEach((album) => assignWikiData(album.trackGroups, 'trackData')); + + assignWikiData(WD.trackData, 'albumData', 'artistData', 'artTagData', 'flashData', 'trackData'); + assignWikiData(WD.artistData, 'albumData', 'artistData', 'flashData', 'trackData'); assignWikiData(WD.groupData, 'albumData', 'groupCategoryData'); assignWikiData(WD.groupCategoryData, 'groupData'); assignWikiData(WD.flashData, 'artistData', 'flashActData', 'trackData'); @@ -1236,48 +1179,47 @@ export function filterDuplicateDirectories(wikiData) { const aggregate = openAggregate({message: `Duplicate directories found`}); for (const thingDataProp of deduplicateSpec) { const thingData = wikiData[thingDataProp]; - aggregate.nest( - { - message: `Duplicate directories found in ${color.green( - 'wikiData.' + thingDataProp - )}`, - }, - ({call}) => { - const directoryPlaces = Object.create(null); - const duplicateDirectories = []; - for (const thing of thingData) { - const {directory} = thing; - if (directory in directoryPlaces) { - directoryPlaces[directory].push(thing); - duplicateDirectories.push(directory); - } else { - directoryPlaces[directory] = [thing]; - } + aggregate.nest({message: `Duplicate directories found in ${color.green('wikiData.' + thingDataProp)}`}, ({call}) => { + const directoryPlaces = Object.create(null); + const duplicateDirectories = []; + + for (const thing of thingData) { + const {directory} = thing; + if (directory in directoryPlaces) { + directoryPlaces[directory].push(thing); + duplicateDirectories.push(directory); + } else { + directoryPlaces[directory] = [thing]; } - if (empty(duplicateDirectories)) return; - duplicateDirectories.sort((a, b) => { - const aL = a.toLowerCase(); - const bL = b.toLowerCase(); - return aL < bL ? -1 : aL > bL ? 1 : 0; + } + + if (empty(duplicateDirectories)) return; + + duplicateDirectories.sort((a, b) => { + const aL = a.toLowerCase(); + const bL = b.toLowerCase(); + return aL < bL ? -1 : aL > bL ? 1 : 0; + }); + + for (const directory of duplicateDirectories) { + const places = directoryPlaces[directory]; + call(() => { + throw new Error( + `Duplicate directory ${color.green(directory)}:\n` + + places.map((thing) => ` - ` + inspect(thing)).join('\n') + ); }); - for (const directory of duplicateDirectories) { - const places = directoryPlaces[directory]; - call(() => { - throw new Error( - `Duplicate directory ${color.green(directory)}:\n` + - places.map((thing) => ` - ` + inspect(thing)).join('\n') - ); - }); - } - const allDuplicatedThings = Object.values(directoryPlaces) - .filter((arr) => arr.length > 1) - .flat(); - const filteredThings = thingData.filter( - (thing) => !allDuplicatedThings.includes(thing) - ); - wikiData[thingDataProp] = filteredThings; } - ); + + const allDuplicatedThings = Object.values(directoryPlaces) + .filter((arr) => arr.length > 1) + .flat(); + + const filteredThings = thingData + .filter((thing) => !allDuplicatedThings.includes(thing)); + + wikiData[thingDataProp] = filteredThings; + }); } // TODO: This code closes the aggregate but it generally gets closed again @@ -1303,67 +1245,46 @@ export function filterDuplicateDirectories(wikiData) { // data array. export function filterReferenceErrors(wikiData) { const referenceSpec = [ - [ - 'wikiInfo', - { - divideTrackListsByGroupsByRef: 'group', - }, - ], - - [ - 'albumData', - { - artistContribsByRef: '_contrib', - coverArtistContribsByRef: '_contrib', - trackCoverArtistContribsByRef: '_contrib', - wallpaperArtistContribsByRef: '_contrib', - bannerArtistContribsByRef: '_contrib', - groupsByRef: 'group', - artTagsByRef: 'artTag', - }, - ], - - [ - 'trackData', - { - artistContribsByRef: '_contrib', - contributorContribsByRef: '_contrib', - coverArtistContribsByRef: '_contrib', - referencedTracksByRef: 'track', - artTagsByRef: 'artTag', - originalReleaseTrackByRef: 'track', - }, - ], - - [ - 'groupCategoryData', - { - groupsByRef: 'group', - }, - ], - - [ - 'homepageLayout.rows', - { - sourceGroupsByRef: 'group', - sourceAlbumsByRef: 'album', - }, - ], - - [ - 'flashData', - { - contributorContribsByRef: '_contrib', - featuredTracksByRef: 'track', - }, - ], - - [ - 'flashActData', - { - flashesByRef: 'flash', - }, - ], + ['wikiInfo', { + divideTrackListsByGroupsByRef: 'group', + }], + + ['albumData', { + artistContribsByRef: '_contrib', + coverArtistContribsByRef: '_contrib', + trackCoverArtistContribsByRef: '_contrib', + wallpaperArtistContribsByRef: '_contrib', + bannerArtistContribsByRef: '_contrib', + groupsByRef: 'group', + artTagsByRef: 'artTag', + }], + + ['trackData', { + artistContribsByRef: '_contrib', + contributorContribsByRef: '_contrib', + coverArtistContribsByRef: '_contrib', + referencedTracksByRef: 'track', + artTagsByRef: 'artTag', + originalReleaseTrackByRef: 'track', + }], + + ['groupCategoryData', { + groupsByRef: 'group', + }], + + ['homepageLayout.rows', { + sourceGroupsByRef: 'group', + sourceAlbumsByRef: 'album', + }], + + ['flashData', { + contributorContribsByRef: '_contrib', + featuredTracksByRef: 'track', + }], + + ['flashActData', { + flashesByRef: 'flash', + }], ]; function getNestedProp(obj, key) { @@ -1373,94 +1294,56 @@ export function filterReferenceErrors(wikiData) { return recursive(obj, keys); } - const aggregate = openAggregate({ - message: `Errors validating between-thing references in data`, - }); + const aggregate = openAggregate({message: `Errors validating between-thing references in data`}); const boundFind = bindFind(wikiData, {mode: 'error'}); for (const [thingDataProp, propSpec] of referenceSpec) { const thingData = getNestedProp(wikiData, thingDataProp); - aggregate.nest( - { - message: `Reference errors in ${color.green( - 'wikiData.' + thingDataProp - )}`, - }, - ({nest}) => { - const things = Array.isArray(thingData) ? thingData : [thingData]; - for (const thing of things) { - nest( - {message: `Reference errors in ${inspect(thing)}`}, - ({filter}) => { - for (const [property, findFnKey] of Object.entries(propSpec)) { - if (!thing[property]) continue; - if (findFnKey === '_contrib') { - thing[property] = filter( - thing[property], - decorateErrorWithIndex(({who}) => { - const alias = find.artist(who, wikiData.artistAliasData, { - mode: 'quiet', - }); - if (alias) { - const original = find.artist( - alias.aliasedArtistRef, - wikiData.artistData, - { - mode: 'quiet', - } - ); - throw new Error( - `Reference ${color.red( - who - )} is to an alias, should be ${color.green( - original.name - )}` - ); - } - return boundFind.artist(who); - }), - { - message: `Reference errors in contributions ${color.green( - property - )} (${color.green('find.artist')})`, - } - ); - continue; - } - const findFn = boundFind[findFnKey]; - const value = thing[property]; - if (Array.isArray(value)) { - thing[property] = filter( - value, - decorateErrorWithIndex(findFn), - { - message: `Reference errors in property ${color.green( - property - )} (${color.green('find.' + findFnKey)})`, - } - ); - } else { - nest( - { - message: `Reference error in property ${color.green( - property - )} (${color.green('find.' + findFnKey)})`, - }, - ({call}) => { - try { - call(findFn, value); - } catch (error) { - thing[property] = null; - throw error; - } - } - ); + + aggregate.nest({message: `Reference errors in ${color.green('wikiData.' + thingDataProp)}`}, ({nest}) => { + const things = Array.isArray(thingData) ? thingData : [thingData]; + + for (const thing of things) { + nest({message: `Reference errors in ${inspect(thing)}`}, ({filter}) => { + for (const [property, findFnKey] of Object.entries(propSpec)) { + if (!thing[property]) continue; + + if (findFnKey === '_contrib') { + thing[property] = filter( + thing[property], + decorateErrorWithIndex(({who}) => { + const alias = find.artist(who, wikiData.artistAliasData, {mode: 'quiet'}); + if (alias) { + const original = find.artist(alias.aliasedArtistRef, wikiData.artistData, {mode: 'quiet'}); + throw new Error(`Reference ${color.red(who)} is to an alias, should be ${color.green(original.name)}`); + } + return boundFind.artist(who); + }), + {message: `Reference errors in contributions ${color.green(property)} (${color.green('find.artist')})`}); + continue; + } + + const findFn = boundFind[findFnKey]; + const value = thing[property]; + + if (Array.isArray(value)) { + thing[property] = filter( + value, + decorateErrorWithIndex(findFn), + {message: `Reference errors in property ${color.green(property)} (${color.green('find.' + findFnKey)})`}); + } else { + nest({message: `Reference error in property ${color.green(property)} (${color.green('find.' + findFnKey)})`}, ({call}) => { + try { + call(findFn, value); + } catch (error) { + thing[property] = null; + throw error; } - } + }); } - ); - } + } + }); } - ); + }); } return aggregate; @@ -1472,18 +1355,15 @@ export function filterReferenceErrors(wikiData) { // a boilerplate for more specialized output, or as a quick start in utilities // where reporting info about data loading isn't as relevant as during the // main wiki build process. -export async function quickLoadAllFromYAML( - dataPath, - {showAggregate: customShowAggregate = showAggregate} = {} -) { +export async function quickLoadAllFromYAML(dataPath, { + showAggregate: customShowAggregate = showAggregate, +} = {}) { const showAggregate = customShowAggregate; let wikiData; { - const {aggregate, result} = await loadAndProcessDataDocuments({ - dataPath, - }); + const {aggregate, result} = await loadAndProcessDataDocuments({dataPath}); wikiData = result; |