From 003f594f6348b55109dd66416e75fcc2a88faade Mon Sep 17 00:00:00 2001 From: "(quasar) nebula" Date: Sat, 26 Nov 2022 23:44:08 -0400 Subject: finish up cosmetic style changes --- src/data/cacheable-object.js | 93 +++-- src/data/patches.js | 7 +- src/data/serialize.js | 15 +- src/data/things.js | 73 ++-- src/data/validators.js | 94 ++--- src/data/yaml.js | 620 ++++++++++++++------------------- src/file-size-preloader.js | 2 - src/gen-thumbs.js | 17 +- src/listing-spec.js | 2 - src/misc-templates.js | 8 +- src/page/album-commentary.js | 2 - src/page/album.js | 3 +- src/page/artist-alias.js | 2 - src/page/artist.js | 60 ++-- src/page/flash.js | 2 - src/page/group.js | 2 - src/page/index.js | 2 - src/page/listing.js | 2 - src/page/static.js | 2 - src/page/tag.js | 13 +- src/page/track.js | 4 +- src/repl.js | 2 - src/static/client.js | 5 +- src/static/lazy-loading.js | 2 - src/upd8.js | 800 +++++++++++++++++++++---------------------- src/url-spec.js | 5 +- src/util/cli.js | 2 - src/util/colors.js | 27 +- src/util/find.js | 10 +- src/util/html.js | 18 +- src/util/io.js | 2 - src/util/link.js | 33 +- src/util/magic-constants.js | 2 - src/util/node-utils.js | 2 - src/util/replacer.js | 21 +- src/util/serialize.js | 11 +- src/util/sugar.js | 64 ++-- src/util/urls.js | 19 +- src/util/wiki-data.js | 54 ++- test/cacheable-object.js | 416 +++++++++++----------- test/data-validators.js | 376 ++++++++++---------- test/things.js | 82 ++--- 42 files changed, 1343 insertions(+), 1635 deletions(-) diff --git a/src/data/cacheable-object.js b/src/data/cacheable-object.js index 688d8a0..04e029f 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 dc757fa..feeaf39 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 a4206fd..52aacb0 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 4aa684d..3388046 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 8d92239..5c357c8 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 e18b733..2adce50 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(':')) { - 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; diff --git a/src/file-size-preloader.js b/src/file-size-preloader.js index 363fb4c..ca1452d 100644 --- a/src/file-size-preloader.js +++ b/src/file-size-preloader.js @@ -1,5 +1,3 @@ -/** @format */ - // Very simple, bare-bones file size loader which takes a bunch of file // paths, gets their filesizes, and resolves a promise when it's done. // diff --git a/src/gen-thumbs.js b/src/gen-thumbs.js index 9151201..dc1f6fb 100644 --- a/src/gen-thumbs.js +++ b/src/gen-thumbs.js @@ -1,5 +1,4 @@ #!/usr/bin/env node -/** @format */ // Ok, so the d8te is 3 March 2021, and the music wiki was initially released // on 15 November 2019. That is 474 days or 11376 hours. In my opinion, and @@ -98,10 +97,10 @@ import {commandExists, isMain, promisifyProcess} from './util/node-utils.js'; import {delay, queue} from './util/sugar.js'; -function traverse( - startDirPath, - {filterFile = () => true, filterDir = () => true} = {} -) { +function traverse(startDirPath, { + filterFile = () => true, + filterDir = () => true +} = {}) { const recursive = (names, subDirPath) => Promise.all( names.map((name) => @@ -197,10 +196,10 @@ function generateImageThumbnails(filePath, {spawnConvert}) { ]); } -export default async function genThumbs( - mediaPath, - {queueSize = 0, quiet = false} = {} -) { +export default async function genThumbs(mediaPath, { + queueSize = 0, + quiet = false, +} = {}) { if (!mediaPath) { throw new Error('Expected mediaPath to be passed'); } diff --git a/src/listing-spec.js b/src/listing-spec.js index e6cd7a0..2e129ff 100644 --- a/src/listing-spec.js +++ b/src/listing-spec.js @@ -1,5 +1,3 @@ -/** @format */ - import { empty, accumulateSum, diff --git a/src/misc-templates.js b/src/misc-templates.js index c627533..2614aac 100644 --- a/src/misc-templates.js +++ b/src/misc-templates.js @@ -1,5 +1,3 @@ -/** @format */ - // Miscellaneous utility functions which are useful across page specifications. // These are made available right on a page spec's ({wikiData, language, ...}) // args object! @@ -163,9 +161,7 @@ function unbound_generateChronologyLinks(currentThing, { } if (contributions.length > 8) { - return `
${language.$( - 'misc.chronology.seeArtistPages' - )}
`; + return `
${language.$('misc.chronology.seeArtistPages')}
`; } return contributions @@ -789,4 +785,4 @@ export { unbound_generateNavigationLinks as generateNavigationLinks, unbound_getFooterLocalizationLinks as getFooterLocalizationLinks, -} \ No newline at end of file +} diff --git a/src/page/album-commentary.js b/src/page/album-commentary.js index e3a63ba..a0ac8d9 100644 --- a/src/page/album-commentary.js +++ b/src/page/album-commentary.js @@ -1,5 +1,3 @@ -/** @format */ - // Album commentary page and index specifications. import {filterAlbumsByCommentary} from '../util/wiki-data.js'; diff --git a/src/page/album.js b/src/page/album.js index 14d4a9d..009ed55 100644 --- a/src/page/album.js +++ b/src/page/album.js @@ -1,5 +1,3 @@ -/** @format */ - // Album page specification. import { @@ -30,6 +28,7 @@ export function write(album, {wikiData}) { duration: language.formatDuration(track.duration ?? 0), track: link.track(track), }; + return html.tag('li', {style: getLinkThemeString(track.color)}, compareArrays( diff --git a/src/page/artist-alias.js b/src/page/artist-alias.js index 3d882f6..e2b1604 100644 --- a/src/page/artist-alias.js +++ b/src/page/artist-alias.js @@ -1,5 +1,3 @@ -/** @format */ - // Artist alias redirect pages. // (Makes old permalinks bring visitors to the up-to-date page.) diff --git a/src/page/artist.js b/src/page/artist.js index f6a81f6..1b5a506 100644 --- a/src/page/artist.js +++ b/src/page/artist.js @@ -1,5 +1,3 @@ -/** @format */ - // Artist page specification. // // NB: See artist-alias.js for artist alias redirect pages. @@ -161,16 +159,13 @@ export function write(artist, {wikiData}) { ? language.$('artistPage.creditList.entry.rerelease', {entry}) : !empty(artists) ? contrib.what || contrib.whatArray?.length - ? language.$( - 'artistPage.creditList.entry.withArtists.withContribution', - { - entry, - artists: getArtistString(artists), - contribution: contrib.whatArray - ? language.formatUnitList(contrib.whatArray) - : contrib.what, - } - ) + ? language.$('artistPage.creditList.entry.withArtists.withContribution', { + entry, + artists: getArtistString(artists), + contribution: contrib.whatArray + ? language.formatUnitList(contrib.whatArray) + : contrib.what, + }) : language.$('artistPage.creditList.entry.withArtists', { entry, artists: getArtistString(artists), @@ -227,15 +222,12 @@ export function write(artist, {wikiData}) { original: track.originalReleaseTrack, entry: language.$('artistPage.creditList.entry.track.withDuration', { track: link.track(track), - duration: language.formatDuration( - track.duration ?? 0 - ), + duration: language.formatDuration(track.duration ?? 0), }), ...props, })) .map(({original, ...opts}) => - html.tag( - 'li', + html.tag('li', {class: original && 'rerelease'}, generateEntryAccents({ getArtistString, @@ -282,20 +274,14 @@ export function write(artist, {wikiData}) { type: 'data', path: ['artist', artist.directory], data: ({serializeContribs, serializeLink}) => { - const serializeArtistsAndContrib = bindOpts( - unbound_serializeArtistsAndContrib, - { - serializeContribs, - serializeLink, - } - ); - - const serializeTrackListChunks = bindOpts( - unbound_serializeTrackListChunks, - { - serializeLink, - } - ); + const serializeArtistsAndContrib = bindOpts(unbound_serializeArtistsAndContrib, { + serializeContribs, + serializeLink, + }); + + const serializeTrackListChunks = bindOpts(unbound_serializeTrackListChunks, { + serializeLink, + }); return { albums: { @@ -663,12 +649,12 @@ export function write(artist, {wikiData}) { // Utility functions -function generateNavForArtist( - artist, - isGallery, - hasGallery, - {generateInfoGalleryLinks, link, language, wikiData} -) { +function generateNavForArtist(artist, isGallery, hasGallery, { + generateInfoGalleryLinks, + language, + link, + wikiData, +}) { const {wikiInfo} = wikiData; const infoGalleryLinks = diff --git a/src/page/flash.js b/src/page/flash.js index 237dd47..74a6b4a 100644 --- a/src/page/flash.js +++ b/src/page/flash.js @@ -1,5 +1,3 @@ -/** @format */ - // Flash page and index specifications. import {empty} from '../util/sugar.js'; diff --git a/src/page/group.js b/src/page/group.js index 2bd6da9..c261565 100644 --- a/src/page/group.js +++ b/src/page/group.js @@ -1,5 +1,3 @@ -/** @format */ - // Group page specifications. import { diff --git a/src/page/index.js b/src/page/index.js index 149503f..f580cbe 100644 --- a/src/page/index.js +++ b/src/page/index.js @@ -1,5 +1,3 @@ -/** @format */ - // NB: This is the index for the page/ directory and contains exports for all // other modules here! It's not the page spec for the homepage - see // homepage.js for that. diff --git a/src/page/listing.js b/src/page/listing.js index 65982f8..cb297a8 100644 --- a/src/page/listing.js +++ b/src/page/listing.js @@ -1,5 +1,3 @@ -/** @format */ - // Listing page specification. // // The targets here are a bit different than for most pages: rather than data diff --git a/src/page/static.js b/src/page/static.js index 8925b60..2a0f5e5 100644 --- a/src/page/static.js +++ b/src/page/static.js @@ -1,5 +1,3 @@ -/** @format */ - // Static content page specification. (These are static pages coded into the // wiki data folder, used for a variety of purposes, e.g. wiki info, // changelog, and so on.) diff --git a/src/page/tag.js b/src/page/tag.js index faa0df2..da4f194 100644 --- a/src/page/tag.js +++ b/src/page/tag.js @@ -1,5 +1,3 @@ -/** @format */ - // Art tag page specification. export function condition({wikiData}) { @@ -28,8 +26,8 @@ export function write(tag, {wikiData}) { getThemeString, getTrackCover, html, - link, language, + link, }) => ({ title: language.$('tagPage.title', {tag: tag.name}), theme: getThemeString(tag.color), @@ -79,10 +77,11 @@ export function write(tag, {wikiData}) { // Utility functions -function generateTagNav( - tag, - {link, language, wikiData} -) { +function generateTagNav(tag, { + language, + link, + wikiData, +}) { return { linkContainerClasses: ['nav-links-hierarchy'], links: [ diff --git a/src/page/track.js b/src/page/track.js index cf93724..b61defe 100644 --- a/src/page/track.js +++ b/src/page/track.js @@ -1,5 +1,3 @@ -/** @format */ - // Track page specification. import { @@ -64,7 +62,7 @@ export function write(track, {wikiData}) { const hasCommentary = track.commentary || otherReleases.some((t) => t.commentary); - const generateCommentary = ({link, language, transformMultiline}) => + const generateCommentary = ({language, link, transformMultiline}) => transformMultiline([ track.commentary, ...otherReleases.map((track) => diff --git a/src/repl.js b/src/repl.js index bd447be..5497ef5 100644 --- a/src/repl.js +++ b/src/repl.js @@ -1,5 +1,3 @@ -/** @format */ - import * as os from 'os'; import * as path from 'path'; import * as repl from 'repl'; diff --git a/src/static/client.js b/src/static/client.js index 1ffcb93..32fb2ab 100644 --- a/src/static/client.js +++ b/src/static/client.js @@ -1,5 +1,3 @@ -/** @format */ - // This is the JS file that gets loaded on the client! It's only really used for // the random track feature right now - the idea is we only use it for stuff // that cannot 8e done at static-site compile time, 8y its fundamentally @@ -392,8 +390,7 @@ function makeInfoCardLinkHandlers(type) { fastHover = true; infoCard.show(type, evt.target); }, - fastHover ? FAST_HOVER_INFO_DELAY : NORMAL_HOVER_INFO_DELAY - ); + fastHover ? FAST_HOVER_INFO_DELAY : NORMAL_HOVER_INFO_DELAY); clearTimeout(endFastHoverTimeout); endFastHoverTimeout = null; diff --git a/src/static/lazy-loading.js b/src/static/lazy-loading.js index 1b779d2..b5b0a36 100644 --- a/src/static/lazy-loading.js +++ b/src/static/lazy-loading.js @@ -1,5 +1,3 @@ -/** @format */ - // Lazy loading! Roll your own. Woot. // This file includes a 8unch of fall8acks and stuff like that, and is written // with fairly Olden JavaScript(TM), so as to work on pretty much any 8rowser diff --git a/src/upd8.js b/src/upd8.js index 44cbce0..8e3e092 100755 --- a/src/upd8.js +++ b/src/upd8.js @@ -1,5 +1,4 @@ #!/usr/bin/env node -/** @format */ // HEY N8RDS! // @@ -814,8 +813,8 @@ function validateWriteObject(obj) { /* async function writeData(subKey, directory, data) { - const paths = writePage.paths('', 'data.' + subKey, directory, {file: 'data.json'}); - await writePage.write(JSON.stringify(data), {paths}); + const paths = writePage.paths('', 'data.' + subKey, directory, {file: 'data.json'}); + await writePage.write(JSON.stringify(data), {paths}); } */ @@ -865,20 +864,17 @@ writePage.to = return path; }; -writePage.html = ( - pageInfo, - { - defaultLanguage, - language, - languages, - localizedPaths, - paths, - oEmbedJSONHref, - to, - transformMultiline, - wikiData, - } -) => { +writePage.html = (pageInfo, { + defaultLanguage, + language, + languages, + localizedPaths, + paths, + oEmbedJSONHref, + to, + transformMultiline, + wikiData, +}) => { const {wikiInfo} = wikiData; let { @@ -986,10 +982,13 @@ writePage.html = ( }), ]); - const generateSidebarHTML = ( - id, - {content, multiple, classes, collapse = true, wide = false} - ) => + const generateSidebarHTML = (id, { + content, + multiple, + classes, + collapse = true, + wide = false, + }) => content ? html.tag('div', { @@ -1388,12 +1387,9 @@ writePage.write = async ({html, oEmbedJSON = '', paths}) => { }; // TODO: This only supports one <>-style argument. -writePage.paths = ( - baseDirectory, - fullKey, - directory = '', - {file = 'index.html'} = {} -) => { +writePage.paths = (baseDirectory, fullKey, directory = '', { + file = 'index.html', +} = {}) => { const [groupKey, subKey] = fullKey.split('.'); const pathname = @@ -1485,49 +1481,46 @@ function writeSharedFilesAndPages({language, wikiData}) { await writeFile(path.join(outputPath, from, 'index.html'), content); }; - return progressPromiseAll( - `Writing files & pages shared across languages.`, - [ - groupData?.some((group) => group.directory === 'fandom') && - redirect( - 'Fandom - Gallery', - 'albums/fandom', - 'localized.groupGallery', - 'fandom' - ), + return progressPromiseAll(`Writing files & pages shared across languages.`, [ + groupData?.some((group) => group.directory === 'fandom') && + redirect( + 'Fandom - Gallery', + 'albums/fandom', + 'localized.groupGallery', + 'fandom' + ), - groupData?.some((group) => group.directory === 'official') && - redirect( - 'Official - Gallery', - 'albums/official', - 'localized.groupGallery', - 'official' - ), + groupData?.some((group) => group.directory === 'official') && + redirect( + 'Official - Gallery', + 'albums/official', + 'localized.groupGallery', + 'official' + ), - wikiInfo.enableListings && - redirect( - 'Album Commentary', - 'list/all-commentary', - 'localized.commentaryIndex', - '' - ), + wikiInfo.enableListings && + redirect( + 'Album Commentary', + 'list/all-commentary', + 'localized.commentaryIndex', + '' + ), - writeFile( - path.join(outputPath, 'data.json'), - ( - '{\n' + - [ - `"albumData": ${stringifyThings(wikiData.albumData)},`, - wikiInfo.enableFlashesAndGames && - `"flashData": ${stringifyThings(wikiData.flashData)},`, - `"artistData": ${stringifyThings(wikiData.artistData)}`, - ] - .filter(Boolean) - .map(line => ' ' + line) - .join('\n') + - '\n}')), - ].filter(Boolean) - ); + writeFile( + path.join(outputPath, 'data.json'), + ( + '{\n' + + [ + `"albumData": ${stringifyThings(wikiData.albumData)},`, + wikiInfo.enableFlashesAndGames && + `"flashData": ${stringifyThings(wikiData.flashData)},`, + `"artistData": ${stringifyThings(wikiData.artistData)}`, + ] + .filter(Boolean) + .map(line => ' ' + line) + .join('\n') + + '\n}')), + ].filter(Boolean)); } function generateRedirectPage(title, target, {language}) { @@ -1752,10 +1745,7 @@ async function main() { } }; error(!dataPath, `Expected --data-path option or HSMUSIC_DATA to be set`); - error( - !mediaPath, - `Expected --media-path option or HSMUSIC_MEDIA to be set` - ); + error(!mediaPath, `Expected --media-path option or HSMUSIC_MEDIA to be set`); error(!outputPath, `Expected --out-path option or HSMUSIC_OUT to be set`); if (errored) { return; @@ -1833,9 +1823,7 @@ async function main() { { const logThings = (thingDataProp, label) => - logInfo` - ${ - wikiData[thingDataProp]?.length ?? color.red('(Missing!)') - } ${color.normal(color.dim(label))}`; + logInfo` - ${wikiData[thingDataProp]?.length ?? color.red('(Missing!)')} ${color.normal(color.dim(label))}`; try { logInfo`Loaded data and processed objects:`; logThings('albumData', 'albums'); @@ -2032,10 +2020,8 @@ async function main() { { const tagRefs = new Set( - [...WD.trackData, ...WD.albumData].flatMap( - (thing) => thing.artTagsByRef ?? [] - ) - ); + [...WD.trackData, ...WD.albumData] + .flatMap((thing) => thing.artTagsByRef ?? [])); for (const ref of tagRefs) { if (find.artTag(ref, WD.artTagData)) { @@ -2051,12 +2037,10 @@ async function main() { } } - WD.officialAlbumData = WD.albumData.filter((album) => - album.groups.some((group) => group.directory === OFFICIAL_GROUP_DIRECTORY) - ); - WD.fandomAlbumData = WD.albumData.filter((album) => - album.groups.every((group) => group.directory !== OFFICIAL_GROUP_DIRECTORY) - ); + WD.officialAlbumData = WD.albumData + .filter((album) => album.groups.some((group) => group.directory === OFFICIAL_GROUP_DIRECTORY)); + WD.fandomAlbumData = WD.albumData + .filter((album) => album.groups.every((group) => group.directory !== OFFICIAL_GROUP_DIRECTORY)); const fileSizePreloader = new FileSizePreloader(); @@ -2089,7 +2073,7 @@ async function main() { ]; const getSizeOfAdditionalFile = (mediaPath) => { - const {device = null} = + const {device} = additionalFilePaths.find(({media}) => media === mediaPath) || {}; if (!device) return null; return fileSizePreloader.getSizeOfPath(device); @@ -2097,9 +2081,7 @@ async function main() { logInfo`Preloading filesizes for ${additionalFilePaths.length} additional files...`; - fileSizePreloader.loadPaths( - ...additionalFilePaths.map((path) => path.device) - ); + fileSizePreloader.loadPaths(...additionalFilePaths.map((path) => path.device)); await fileSizePreloader.waitUntilDoneLoading(); logInfo`Done preloading filesizes!`; @@ -2139,18 +2121,14 @@ async function main() { } if (!pageSpec.write) { - logError`${flag + '.targets'} is specified, but ${ - flag + '.write' - } is missing!`; + logError`${flag + '.targets'} is specified, but ${flag + '.write'} is missing!`; error = true; return null; } const targets = pageSpec.targets({wikiData}); if (!Array.isArray(targets)) { - logError`${ - flag + '.targets' - } was called, but it didn't return an array! (${typeof targets})`; + logError`${flag + '.targets'} was called, but it didn't return an array! (${typeof targets})`; error = true; return null; } @@ -2230,345 +2208,333 @@ async function main() { } /* - await progressPromiseAll(`Writing data files shared across languages.`, queue( - dataWrites.map(({path, data}) => () => { - const bound = {}; + await progressPromiseAll(`Writing data files shared across languages.`, queue( + dataWrites.map(({path, data}) => () => { + const bound = {}; - bound.serializeLink = bindOpts(serializeLink, {}); + bound.serializeLink = bindOpts(serializeLink, {}); - bound.serializeContribs = bindOpts(serializeContribs, {}); + bound.serializeContribs = bindOpts(serializeContribs, {}); - bound.serializeImagePaths = bindOpts(serializeImagePaths, { - thumb - }); + bound.serializeImagePaths = bindOpts(serializeImagePaths, { + thumb + }); - bound.serializeCover = bindOpts(serializeCover, { - [bindOpts.bindIndex]: 2, - serializeImagePaths: bound.serializeImagePaths, - urls - }); + bound.serializeCover = bindOpts(serializeCover, { + [bindOpts.bindIndex]: 2, + serializeImagePaths: bound.serializeImagePaths, + urls + }); - bound.serializeGroupsForAlbum = bindOpts(serializeGroupsForAlbum, { - serializeLink - }); + bound.serializeGroupsForAlbum = bindOpts(serializeGroupsForAlbum, { + serializeLink + }); - bound.serializeGroupsForTrack = bindOpts(serializeGroupsForTrack, { - serializeLink - }); + bound.serializeGroupsForTrack = bindOpts(serializeGroupsForTrack, { + serializeLink + }); - // TODO: This only supports one <>-style argument. - return writeData(path[0], path[1], data({ - ...bound - })); - }), - queueSize - )); - */ + // TODO: This only supports one <>-style argument. + return writeData(path[0], path[1], data({...bound})); + }), + queueSize + )); + */ const perLanguageFn = async (language, i, entries) => { const baseDirectory = language === finalDefaultLanguage ? '' : language.code; - console.log( - `\x1b[34;1m${`[${i + 1}/${entries.length}] ${ - language.code - } (-> /${baseDirectory}) `.padEnd(60, '-')}\x1b[0m` - ); + console.log(`\x1b[34;1m${`[${i + 1}/${entries.length}] ${language.code} (-> /${baseDirectory}) `.padEnd(60, '-')}\x1b[0m`); + + await progressPromiseAll(`Writing ${language.code}`, queue([ + ...pageWrites.map((props) => () => { + const {path, page} = props; + + // TODO: This only supports one <>-style argument. + const pageSubKey = path[0]; + const directory = path[1]; + + const localizedPaths = Object.fromEntries( + Object.entries(languages) + .filter( + ([key, language]) => key !== 'default' && !language.hidden + ) + .map(([_key, language]) => [ + language.code, + writePage.paths( + language === finalDefaultLanguage ? '' : language.code, + 'localized.' + pageSubKey, + directory + ), + ]) + ); - await progressPromiseAll( - `Writing ${language.code}`, - queue( - [ - ...pageWrites.map((props) => () => { - const {path, page} = props; + const paths = writePage.paths( + baseDirectory, + 'localized.' + pageSubKey, + directory + ); - // TODO: This only supports one <>-style argument. - const pageSubKey = path[0]; - const directory = path[1]; + const to = writePage.to({ + baseDirectory, + pageSubKey, + paths, + }); - const localizedPaths = Object.fromEntries( - Object.entries(languages) - .filter( - ([key, language]) => key !== 'default' && !language.hidden + const absoluteTo = (targetFullKey, ...args) => { + const [groupKey, subKey] = targetFullKey.split('.'); + const from = urls.from('shared.root'); + return ( + '/' + + (groupKey === 'localized' && baseDirectory + ? from.to( + 'localizedWithBaseDirectory.' + subKey, + baseDirectory, + ...args ) - .map(([_key, language]) => [ - language.code, - writePage.paths( - language === finalDefaultLanguage ? '' : language.code, - 'localized.' + pageSubKey, - directory - ), - ]) - ); - - const paths = writePage.paths( - baseDirectory, - 'localized.' + pageSubKey, - directory - ); - - const to = writePage.to({ - baseDirectory, - pageSubKey, - paths, - }); - - const absoluteTo = (targetFullKey, ...args) => { - const [groupKey, subKey] = targetFullKey.split('.'); - const from = urls.from('shared.root'); - return ( - '/' + - (groupKey === 'localized' && baseDirectory - ? from.to( - 'localizedWithBaseDirectory.' + subKey, - baseDirectory, - ...args - ) - : from.to(targetFullKey, ...args)) - ); - }; - - // TODO: Is there some nicer way to define these, - // may8e without totally re-8inding everything for - // each page? - const bound = {}; - - bound.html = html; - - bound.link = withEntries(unbound_link, (entries) => - entries.map(([key, fn]) => [key, bindOpts(fn, {to})]) - ); - - bound.parseAttributes = bindOpts(parseAttributes, { - to, - }); - - bound.find = bindFind(wikiData, {mode: 'warn'}); - - bound.transformInline = bindOpts(transformInline, { - find: bound.find, - link: bound.link, - replacerSpec, - language, - to, - wikiData, - }); - - bound.transformMultiline = bindOpts(transformMultiline, { - transformInline: bound.transformInline, - parseAttributes: bound.parseAttributes, - }); - - bound.transformLyrics = bindOpts(transformLyrics, { - transformInline: bound.transformInline, - transformMultiline: bound.transformMultiline, - }); - - bound.iconifyURL = bindOpts(iconifyURL, { - html, - language, - to, - }); - - bound.fancifyURL = bindOpts(fancifyURL, { - html, - language, - }); - - bound.fancifyFlashURL = bindOpts(fancifyFlashURL, { - [bindOpts.bindIndex]: 2, - html, - language, - - fancifyURL: bound.fancifyURL, - }); - - bound.getRevealStringFromWarnings = bindOpts(getRevealStringFromWarnings, { - html, - language, - }); - - bound.getRevealStringFromTags = bindOpts(getRevealStringFromTags, { - language, - - getRevealStringFromWarnings: bound.getRevealStringFromWarnings, - }); - - bound.getLinkThemeString = getLinkThemeString; - - bound.getThemeString = getThemeString; - - bound.getArtistString = bindOpts(getArtistString, { - html, - link: bound.link, - language, - - iconifyURL: bound.iconifyURL, - }); - - bound.getAlbumCover = bindOpts(getAlbumCover, { - to, - }); - - bound.getTrackCover = bindOpts(getTrackCover, { - to, - }); - - bound.getFlashCover = bindOpts(getFlashCover, { - to, - }); - - bound.getArtistAvatar = bindOpts(getArtistAvatar, { - to, - }); - - bound.generateAdditionalFilesShortcut = bindOpts(generateAdditionalFilesShortcut, { - html, - language, - }); - - bound.generateAdditionalFilesList = bindOpts(generateAdditionalFilesList, { - html, - language, - }); - - bound.generateNavigationLinks = bindOpts(generateNavigationLinks, { - link: bound.link, - language, - }); - - bound.generateChronologyLinks = bindOpts(generateChronologyLinks, { - html, - language, - link: bound.link, - wikiData, - - generateNavigationLinks: bound.generateNavigationLinks, - }); - - bound.generateCoverLink = bindOpts(generateCoverLink, { - [bindOpts.bindIndex]: 0, - html, - img, - link: bound.link, - language, - to, - wikiData, - - getRevealStringFromTags: bound.getRevealStringFromTags, - }); - - bound.generateInfoGalleryLinks = bindOpts(generateInfoGalleryLinks, { - [bindOpts.bindIndex]: 2, - link: bound.link, - language, - }); - - bound.generateTrackListDividedByGroups = bindOpts(generateTrackListDividedByGroups, { - html, - language, - wikiData, - }); - - bound.getGridHTML = bindOpts(getGridHTML, { - [bindOpts.bindIndex]: 0, - img, - html, - language, - - getRevealStringFromTags: bound.getRevealStringFromTags, - }); - - bound.getAlbumGridHTML = bindOpts(getAlbumGridHTML, { - [bindOpts.bindIndex]: 0, - link: bound.link, - language, - - getAlbumCover: bound.getAlbumCover, - getGridHTML: bound.getGridHTML, - }); - - bound.getFlashGridHTML = bindOpts(getFlashGridHTML, { - [bindOpts.bindIndex]: 0, - link: bound.link, - - getFlashCover: bound.getFlashCover, - getGridHTML: bound.getGridHTML, - }); - - bound.getAlbumStylesheet = bindOpts(getAlbumStylesheet, { - to, - }); - - const pageInfo = page({ - ...bound, - - language, - - absoluteTo, - relativeTo: to, - to, - urls, - - getSizeOfAdditionalFile, - }); - - const oEmbedJSON = writePage.oEmbedJSON(pageInfo, { - language, - wikiData, - }); - - const oEmbedJSONHref = - oEmbedJSON && - wikiData.wikiInfo.canonicalBase && - wikiData.wikiInfo.canonicalBase + - urls - .from('shared.root') - .to('shared.path', paths.pathname + OEMBED_JSON_FILE); - - const pageHTML = writePage.html(pageInfo, { - defaultLanguage: finalDefaultLanguage, - language, - languages, - localizedPaths, - oEmbedJSONHref, - paths, - to, - transformMultiline: bound.transformMultiline, - wikiData, - }); - - return writePage.write({ - html: pageHTML, - oEmbedJSON, - paths, - }); - }), - ...redirectWrites.map(({fromPath, toPath, title: titleFn}) => () => { - const title = titleFn({ - language, - }); - - // TODO: This only supports one <>-style argument. - const fromPaths = writePage.paths( - baseDirectory, - 'localized.' + fromPath[0], - fromPath[1] - ); - const to = writePage.to({ - baseDirectory, - pageSubKey: fromPath[0], - paths: fromPaths, - }); - - const target = to('localized.' + toPath[0], ...toPath.slice(1)); - const html = generateRedirectPage(title, target, {language}); - return writePage.write({html, paths: fromPaths}); - }), - ], - queueSize - ) - ); + : from.to(targetFullKey, ...args)) + ); + }; + + // TODO: Is there some nicer way to define these, + // may8e without totally re-8inding everything for + // each page? + const bound = {}; + + bound.html = html; + + bound.link = withEntries(unbound_link, (entries) => + entries.map(([key, fn]) => [key, bindOpts(fn, {to})]) + ); + + bound.parseAttributes = bindOpts(parseAttributes, { + to, + }); + + bound.find = bindFind(wikiData, {mode: 'warn'}); + + bound.transformInline = bindOpts(transformInline, { + find: bound.find, + link: bound.link, + replacerSpec, + language, + to, + wikiData, + }); + + bound.transformMultiline = bindOpts(transformMultiline, { + transformInline: bound.transformInline, + parseAttributes: bound.parseAttributes, + }); + + bound.transformLyrics = bindOpts(transformLyrics, { + transformInline: bound.transformInline, + transformMultiline: bound.transformMultiline, + }); + + bound.iconifyURL = bindOpts(iconifyURL, { + html, + language, + to, + }); + + bound.fancifyURL = bindOpts(fancifyURL, { + html, + language, + }); + + bound.fancifyFlashURL = bindOpts(fancifyFlashURL, { + [bindOpts.bindIndex]: 2, + html, + language, + + fancifyURL: bound.fancifyURL, + }); + + bound.getRevealStringFromWarnings = bindOpts(getRevealStringFromWarnings, { + html, + language, + }); + + bound.getRevealStringFromTags = bindOpts(getRevealStringFromTags, { + language, + + getRevealStringFromWarnings: bound.getRevealStringFromWarnings, + }); + + bound.getLinkThemeString = getLinkThemeString; + + bound.getThemeString = getThemeString; + + bound.getArtistString = bindOpts(getArtistString, { + html, + link: bound.link, + language, + + iconifyURL: bound.iconifyURL, + }); + + bound.getAlbumCover = bindOpts(getAlbumCover, { + to, + }); + + bound.getTrackCover = bindOpts(getTrackCover, { + to, + }); + + bound.getFlashCover = bindOpts(getFlashCover, { + to, + }); + + bound.getArtistAvatar = bindOpts(getArtistAvatar, { + to, + }); + + bound.generateAdditionalFilesShortcut = bindOpts(generateAdditionalFilesShortcut, { + html, + language, + }); + + bound.generateAdditionalFilesList = bindOpts(generateAdditionalFilesList, { + html, + language, + }); + + bound.generateNavigationLinks = bindOpts(generateNavigationLinks, { + link: bound.link, + language, + }); + + bound.generateChronologyLinks = bindOpts(generateChronologyLinks, { + html, + language, + link: bound.link, + wikiData, + + generateNavigationLinks: bound.generateNavigationLinks, + }); + + bound.generateCoverLink = bindOpts(generateCoverLink, { + [bindOpts.bindIndex]: 0, + html, + img, + link: bound.link, + language, + to, + wikiData, + + getRevealStringFromTags: bound.getRevealStringFromTags, + }); + + bound.generateInfoGalleryLinks = bindOpts(generateInfoGalleryLinks, { + [bindOpts.bindIndex]: 2, + link: bound.link, + language, + }); + + bound.generateTrackListDividedByGroups = bindOpts(generateTrackListDividedByGroups, { + html, + language, + wikiData, + }); + + bound.getGridHTML = bindOpts(getGridHTML, { + [bindOpts.bindIndex]: 0, + img, + html, + language, + + getRevealStringFromTags: bound.getRevealStringFromTags, + }); + + bound.getAlbumGridHTML = bindOpts(getAlbumGridHTML, { + [bindOpts.bindIndex]: 0, + link: bound.link, + language, + + getAlbumCover: bound.getAlbumCover, + getGridHTML: bound.getGridHTML, + }); + + bound.getFlashGridHTML = bindOpts(getFlashGridHTML, { + [bindOpts.bindIndex]: 0, + link: bound.link, + + getFlashCover: bound.getFlashCover, + getGridHTML: bound.getGridHTML, + }); + + bound.getAlbumStylesheet = bindOpts(getAlbumStylesheet, { + to, + }); + + const pageInfo = page({ + ...bound, + + language, + + absoluteTo, + relativeTo: to, + to, + urls, + + getSizeOfAdditionalFile, + }); + + const oEmbedJSON = writePage.oEmbedJSON(pageInfo, { + language, + wikiData, + }); + + const oEmbedJSONHref = + oEmbedJSON && + wikiData.wikiInfo.canonicalBase && + wikiData.wikiInfo.canonicalBase + + urls + .from('shared.root') + .to('shared.path', paths.pathname + OEMBED_JSON_FILE); + + const pageHTML = writePage.html(pageInfo, { + defaultLanguage: finalDefaultLanguage, + language, + languages, + localizedPaths, + oEmbedJSONHref, + paths, + to, + transformMultiline: bound.transformMultiline, + wikiData, + }); + + return writePage.write({ + html: pageHTML, + oEmbedJSON, + paths, + }); + }), + ...redirectWrites.map(({fromPath, toPath, title: titleFn}) => () => { + const title = titleFn({ + language, + }); + + // TODO: This only supports one <>-style argument. + const fromPaths = writePage.paths( + baseDirectory, + 'localized.' + fromPath[0], + fromPath[1] + ); + const to = writePage.to({ + baseDirectory, + pageSubKey: fromPath[0], + paths: fromPaths, + }); + + const target = to('localized.' + toPath[0], ...toPath.slice(1)); + const html = generateRedirectPage(title, target, {language}); + return writePage.write({html, paths: fromPaths}); + }), + ], queueSize)); }; await wrapLanguages(perLanguageFn, { diff --git a/src/url-spec.js b/src/url-spec.js index bab97ef..ce47926 100644 --- a/src/url-spec.js +++ b/src/url-spec.js @@ -1,5 +1,3 @@ -/** @format */ - import {withEntries} from './util/sugar.js'; const urlSpec = { @@ -87,8 +85,7 @@ const urlSpec = { // so it should never be referenced manually. urlSpec.localizedWithBaseDirectory = { paths: withEntries(urlSpec.localized.paths, (entries) => - entries.map(([key, path]) => [key, '<>/' + path]) - ), + entries.map(([key, path]) => [key, '<>/' + path])), }; export default urlSpec; diff --git a/src/util/cli.js b/src/util/cli.js index 99c0638..f1a3190 100644 --- a/src/util/cli.js +++ b/src/util/cli.js @@ -1,5 +1,3 @@ -/** @format */ - // Utility functions for CLI- and de8ugging-rel8ted stuff. // // A 8unch of these depend on process.stdout 8eing availa8le, so they won't diff --git a/src/util/colors.js b/src/util/colors.js index 5848a82..a0cc748 100644 --- a/src/util/colors.js +++ b/src/util/colors.js @@ -1,16 +1,25 @@ -/** @format */ - // Color and theming utility functions! Handy. // Graciously stolen from https://stackoverflow.com/a/54071699! ::::) // in: r,g,b in [0,1], out: h in [0,360) and s,l in [0,1] export function rgb2hsl(r, g, b) { let a = Math.max(r, g, b), - n = a - Math.min(r, g, b), - f = 1 - Math.abs(a + a - n - 1); + n = a - Math.min(r, g, b), + f = 1 - Math.abs(a + a - n - 1); + let h = - n && (a == r ? (g - b) / n : a == g ? 2 + (b - r) / n : 4 + (r - g) / n); - return [60 * (h < 0 ? h + 6 : h), f ? n / f : 0, (a + a - n) / 2]; + n && + a == r + ? (g - b) / n + : a == g + ? 2 + (b - r) / n + : 4 + (r - g) / n; + + return [ + 60 * (h < 0 ? h + 6 : h), + f ? n / f : 0, + (a + a - n) / 2 + ]; } export function getColors(primary) { @@ -19,10 +28,10 @@ export function getColors(primary) { .match(/[0-9a-fA-F]{2,2}/g) .slice(0, 3) .map((val) => parseInt(val, 16) / 255); + const [h, s, l] = rgb2hsl(r, g, b); - const dim = `hsl(${Math.round(h)}deg, ${Math.round(s * 50)}%, ${Math.round( - l * 80 - )}%)`; + + const dim = `hsl(${Math.round(h)}deg, ${Math.round(s * 50)}%, ${Math.round(l * 80)}%)`; const bg = `hsla(${Math.round(h)}deg, ${Math.round(s * 15)}%, 12%, 0.80)`; return { diff --git a/src/util/find.js b/src/util/find.js index 71026fa..ed0a680 100644 --- a/src/util/find.js +++ b/src/util/find.js @@ -1,5 +1,3 @@ -/** @format */ - import {color, logWarn} from './cli.js'; import {inspect} from 'util'; @@ -26,9 +24,7 @@ function findHelper(keys, findFns = {}) { const byDirectory = findFns.byDirectory || matchDirectory; const byName = findFns.byName || matchName; - const keyRefRegex = new RegExp( - String.raw`^(?:(${keys.join('|')}):(?=\S))?(.*)$` - ); + const keyRefRegex = new RegExp(String.raw`^(?:(${keys.join('|')}):(?=\S))?(.*)$`); // The mode argument here may be 'warn', 'error', or 'quiet'. 'error' throws // errors for null matches (with details about the error), while 'warn' and @@ -37,9 +33,7 @@ function findHelper(keys, findFns = {}) { return (fullRef, data, {mode = 'warn'} = {}) => { if (!fullRef) return null; if (typeof fullRef !== 'string') { - throw new Error( - `Got a reference that is ${typeof fullRef}, not string: ${fullRef}` - ); + throw new Error(`Got a reference that is ${typeof fullRef}, not string: ${fullRef}`); } if (!data) { diff --git a/src/util/html.js b/src/util/html.js index 752291e..0a58622 100644 --- a/src/util/html.js +++ b/src/util/html.js @@ -1,5 +1,3 @@ -/** @format */ - // Some really simple functions for formatting HTML content. // COMPREHENSIVE! @@ -116,16 +114,18 @@ export function escapeAttributeValue(value) { export function attributes(attribs) { return Object.entries(attribs) .map(([key, val]) => { - if (typeof val === 'undefined' || val === null) return [key, val, false]; - else if (typeof val === 'string') return [key, val, true]; - else if (typeof val === 'boolean') return [key, val, val]; - else if (typeof val === 'number') return [key, val.toString(), true]; + if (typeof val === 'undefined' || val === null) + return [key, val, false]; + else if (typeof val === 'string') + return [key, val, true]; + else if (typeof val === 'boolean') + return [key, val, val]; + else if (typeof val === 'number') + return [key, val.toString(), true]; else if (Array.isArray(val)) return [key, val.filter(Boolean).join(' '), val.length > 0]; else - throw new Error( - `Attribute value for ${key} should be primitive or array, got ${typeof val}` - ); + throw new Error(`Attribute value for ${key} should be primitive or array, got ${typeof val}`); }) .filter(([_key, _val, keep]) => keep) .map(([key, val]) => diff --git a/src/util/io.js b/src/util/io.js index cfd6708..6cc89b5 100644 --- a/src/util/io.js +++ b/src/util/io.js @@ -1,5 +1,3 @@ -/** @format */ - // Utility functions for interacting with files and other external data // interfacey constructs. diff --git a/src/util/link.js b/src/util/link.js index 8fe3c2f..9de4c95 100644 --- a/src/util/link.js +++ b/src/util/link.js @@ -1,5 +1,3 @@ -/** @format */ - // This file is essentially one level of a8straction a8ove urls.js (and the // urlSpec it gets its paths from). It's a 8unch of utility functions which // take certain types of wiki data o8jects (colloquially known as "things") @@ -35,18 +33,18 @@ export function getLinkThemeString(color) { const appendIndexHTMLRegex = /^(?!https?:\/\/).+\/$/; const linkHelper = - (hrefFn, {color = true, attr = null} = {}) => - ( - thing, - { - to, - text = '', - attributes = null, - class: className = '', - color: color2 = true, - hash = '', - } - ) => { + (hrefFn, { + color = true, + attr = null, + } = {}) => + (thing, { + to, + text = '', + attributes = null, + class: className = '', + color: color2 = true, + hash = '', + }) => { let href = hrefFn(thing, {to}); if (link.globalOptions.appendIndexHTML) { @@ -88,6 +86,7 @@ const linkDirectory = (key, {expose = null, attr = null, ...conf} = {}) => const linkPathname = (key, conf) => linkHelper(({directory: pathname}, {to}) => to(key, pathname), conf); + const linkIndex = (key, conf) => linkHelper((_, {to}) => to('localized.' + key), conf); @@ -143,8 +142,7 @@ const link = { to( 'media.albumAdditionalFile', fakeFileObject.album.directory, - fakeFileObject.name - ), + fakeFileObject.name), {color: false} ), albumAdditionalFile: ({file, album}, {to}) => @@ -153,8 +151,7 @@ const link = { name: file, album, }, - {to} - ), + {to}), media: linkPathname('media.path', {color: false}), root: linkPathname('shared.path', {color: false}), diff --git a/src/util/magic-constants.js b/src/util/magic-constants.js index dbdbcfd..73fdbc6 100644 --- a/src/util/magic-constants.js +++ b/src/util/magic-constants.js @@ -1,5 +1,3 @@ -/** @format */ - // Magic constants only! These are hard-coded, and any use of them should be // considered a flaw in the codebase - areas where we use hard-coded behavior // to support one use of the wiki software (i.e. HSMusic, usually), rather than diff --git a/src/util/node-utils.js b/src/util/node-utils.js index df44665..252e920 100644 --- a/src/util/node-utils.js +++ b/src/util/node-utils.js @@ -1,5 +1,3 @@ -/** @format */ - // Utility functions which are only relevant to particular Node.js constructs. import {fileURLToPath} from 'url'; diff --git a/src/util/replacer.js b/src/util/replacer.js index ea5a674..9d602ca 100644 --- a/src/util/replacer.js +++ b/src/util/replacer.js @@ -1,15 +1,10 @@ -/** @format */ - import {logError, logWarn} from './cli.js'; import {escapeRegex} from './sugar.js'; export function validateReplacerSpec(replacerSpec, {find, link}) { let success = true; - for (const [ - key, - {link: linkKey, find: findKey, html}, - ] of Object.entries(replacerSpec)) { + for (const [key, {link: linkKey, find: findKey, html}] of Object.entries(replacerSpec)) { if (!html && !link[linkKey]) { logError`The replacer spec ${key} has invalid link key ${linkKey}! Specify it in link specs or fix typo.`; success = false; @@ -436,14 +431,18 @@ function transformNodes(nodes, opts) { return nodes.map((node) => transformNode(node, opts)).join(''); } -export function transformInline( - input, - {replacerSpec, find, link, language, to, wikiData} -) { +export function transformInline(input, { + replacerSpec, + find, + language, + link, + to, + wikiData, +}) { if (!replacerSpec) throw new Error('Expected replacerSpec'); if (!find) throw new Error('Expected find'); - if (!link) throw new Error('Expected link'); if (!language) throw new Error('Expected language'); + if (!link) throw new Error('Expected link'); if (!to) throw new Error('Expected to'); if (!wikiData) throw new Error('Expected wikiData'); diff --git a/src/util/serialize.js b/src/util/serialize.js index 9aa8b0c..73a3137 100644 --- a/src/util/serialize.js +++ b/src/util/serialize.js @@ -1,5 +1,3 @@ -/** @format */ - export function serializeLink(thing) { const ret = {}; ret.name = thing.name; @@ -25,11 +23,10 @@ export function serializeImagePaths(original, {thumb}) { }; } -export function serializeCover( - thing, - pathFunction, - {serializeImagePaths, urls} -) { +export function serializeCover(thing, pathFunction, { + serializeImagePaths, + urls, +}) { const coverPath = pathFunction(thing, { to: urls.from('media.root').to, }); diff --git a/src/util/sugar.js b/src/util/sugar.js index 24ae863..808a7e1 100644 --- a/src/util/sugar.js +++ b/src/util/sugar.js @@ -1,5 +1,3 @@ -/** @format */ - // Syntactic sugar! (Mostly.) // Generic functions - these are useful just a8out everywhere. // @@ -17,9 +15,7 @@ import {color} from './cli.js'; export function* splitArray(array, fn) { let lastIndex = 0; while (lastIndex < array.length) { - let nextIndex = array.findIndex( - (item, index) => index >= lastIndex && fn(item) - ); + let nextIndex = array.findIndex((item, index) => index >= lastIndex && fn(item)); if (nextIndex === -1) { nextIndex = array.length; } @@ -54,8 +50,7 @@ export function accumulateSum(array, fn = x => x) { (accumulator, value, index, array) => accumulator + fn(value, index, array) ?? 0, - 0 - ); + 0); } export const mapInPlace = (array, fn) => @@ -275,11 +270,10 @@ export function mapAggregate(array, fn, aggregateOpts) { return _mapAggregate('sync', null, array, fn, aggregateOpts); } -export function mapAggregateAsync( - array, - fn, - {promiseAll = Promise.all.bind(Promise), ...aggregateOpts} = {} -) { +export function mapAggregateAsync(array, fn, { + promiseAll = Promise.all.bind(Promise), + ...aggregateOpts +} = {}) { return _mapAggregate('async', promiseAll, array, fn, aggregateOpts); } @@ -299,10 +293,11 @@ export function _mapAggregate(mode, promiseAll, array, fn, aggregateOpts) { .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}; - }); + return promiseAll(array.map(aggregate.wrapAsync(fn))) + .then((values) => { + const result = values.filter((value) => value !== failureSymbol); + return {result, aggregate}; + }); } } @@ -317,11 +312,10 @@ 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} = {} -) { +export async function filterAggregateAsync(array, fn, { + promiseAll = Promise.all.bind(Promise), + ...aggregateOpts +} = {}) { return _filterAggregate('async', promiseAll, array, fn, aggregateOpts); } @@ -357,24 +351,20 @@ function _filterAggregate(mode, promiseAll, array, fn, aggregateOpts) { if (mode === 'sync') { const result = array - .map( - aggregate.wrap((input, index, array) => { - const output = fn(input, index, array); - return {input, output}; - }) - ) + .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}; - }) - ) + 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); @@ -414,10 +404,10 @@ export function _withAggregate(mode, aggregateOpts, fn) { } } -export function showAggregate( - topError, - {pathToFile = (p) => p, showTraces = true} = {} -) { +export function showAggregate(topError, { + pathToFile = (p) => p, + showTraces = true, +} = {}) { const recursive = (error, {level}) => { let header = showTraces ? `[${error.constructor.name || 'unnamed'}] ${ diff --git a/src/util/urls.js b/src/util/urls.js index d86c047..1f9cd9c 100644 --- a/src/util/urls.js +++ b/src/util/urls.js @@ -1,5 +1,3 @@ -/** @format */ - // Code that deals with URLs (really the pathnames that get referenced all // throughout the gener8ted HTML). Most nota8ly here is generateURLs, which // is in charge of pre-gener8ting a complete network of template strings @@ -28,9 +26,7 @@ export function generateURLs(urlSpec) { const group = obj[groupKey]; if (!Object.hasOwn(group, subKey)) { - throw new Error( - `Expected valid subkey (got ${subKey} for group ${groupKey})` - ); + throw new Error(`Expected valid subkey (got ${subKey} for group ${groupKey})`); } return { @@ -47,9 +43,8 @@ export function generateURLs(urlSpec) { const generateTo = (fromPath, fromGroup) => { const A = trimLeadingSlash(fromPath); - const rebasePrefix = '../'.repeat( - (fromGroup.prefix || '').split('/').filter(Boolean).length - ); + const rebasePrefix = '../' + .repeat((fromGroup.prefix || '').split('/').filter(Boolean).length); const pathHelper = (toPath, toGroup) => { let B = trimLeadingSlash(toPath); @@ -117,14 +112,14 @@ export function generateURLs(urlSpec) { }; const generateFrom = () => { - const map = withEntries(urlSpec, (entries) => - entries.map(([key, group]) => [ + const map = withEntries( + urlSpec, + (entries) => entries.map(([key, group]) => [ key, withEntries(group.paths, (entries) => entries.map(([key, path]) => [key, generateTo(path, group)]) ), - ]) - ); + ])); const from = (key) => getValueForFullKey(map, key).value; diff --git a/src/util/wiki-data.js b/src/util/wiki-data.js index b0b0b2e..7d2cfa9 100644 --- a/src/util/wiki-data.js +++ b/src/util/wiki-data.js @@ -1,5 +1,3 @@ -/** @format */ - // Utility functions for interacting with wiki data. import {empty} from './sugar.js'; @@ -144,10 +142,9 @@ export function normalizeName(s) { // ...trackData]), because the initial sort places albums before tracks - and // sortByDirectory will handle the rest, given all directories are unique // except when album and track directories overlap with each other. -export function sortByDirectory( - data, - {getDirectory = (o) => o.directory} = {} -) { +export function sortByDirectory(data, { + getDirectory = (o) => o.directory, +} = {}) { return data.sort((a, b) => { const ad = getDirectory(a); const bd = getDirectory(b); @@ -155,7 +152,9 @@ export function sortByDirectory( }); } -export function sortByName(data, {getName = (o) => o.name} = {}) { +export function sortByName(data, { + getName = (o) => o.name, +} = {}) { const nameMap = new Map(); const normalizedNameMap = new Map(); for (const o of data) { @@ -178,7 +177,9 @@ export function sortByName(data, {getName = (o) => o.name} = {}) { }); } -export function sortByDate(data, {getDate = (o) => o.date} = {}) { +export function sortByDate(data, { + getDate = (o) => o.date, +} = {}) { return data.sort((a, b) => { const ad = getDate(a); const bd = getDate(b); @@ -269,7 +270,10 @@ export function sortByConditions(data, conditions) { // Expects thing properties: // * directory (or override getDirectory) // * name (or override getName) -export function sortAlphabetically(data, {getDirectory, getName} = {}) { +export function sortAlphabetically(data, { + getDirectory, + getName, +} = {}) { sortByDirectory(data, {getDirectory}); sortByName(data, {getName}); return data; @@ -279,10 +283,11 @@ export function sortAlphabetically(data, {getDirectory, getName} = {}) { // * directory (or override getDirectory) // * name (or override getName) // * date (or override getDate) -export function sortChronologically( - data, - {getDirectory, getName, getDate} = {} -) { +export function sortChronologically(data, { + getDirectory, + getName, + getDate, +} = {}) { sortAlphabetically(data, {getDirectory, getName}); sortByDate(data, {getDate}); return data; @@ -296,7 +301,9 @@ export function sortChronologically( // release date but can be overridden) above all else. // // This function also works for data lists which contain only tracks. -export function sortAlbumsTracksChronologically(data, {getDate} = {}) { +export function sortAlbumsTracksChronologically(data, { + getDate, +} = {}) { // Sort albums before tracks... sortByConditions(data, [(t) => t.album === undefined]); @@ -320,9 +327,8 @@ export function sortAlbumsTracksChronologically(data, {getDate} = {}) { // Specific data utilities export function filterAlbumsByCommentary(albums) { - return albums.filter((album) => - [album, ...album.tracks].some((x) => x.commentary) - ); + return albums + .filter((album) => [album, ...album.tracks].some((x) => x.commentary)); } export function getAlbumCover(album, {to}) { @@ -387,12 +393,7 @@ export function getTrackCover(track, {to}) { if (!track.hasCoverArt) { return getAlbumCover(track.album, {to}); } else { - return to( - 'media.trackCover', - track.album.directory, - track.directory, - track.coverArtFileExtension - ); + return to('media.trackCover', track.album.directory, track.directory, track.coverArtFileExtension); } } @@ -455,11 +456,7 @@ export function getNewAdditions(numAlbums, {wikiData}) { const currentDate = sortedAlbums[i].dateAddedToWiki; const groupMap = new Map(); const groupArray = []; - for ( - let album; - (album = sortedAlbums[i]) && +album.dateAddedToWiki === +currentDate; - i++ - ) { + for (let album; (album = sortedAlbums[i]) && +album.dateAddedToWiki === +currentDate; i++) { const primaryGroup = album.groups[0]; if (groupMap.has(primaryGroup)) { groupMap.get(primaryGroup).push(album); @@ -510,6 +507,7 @@ export function getNewReleases(numReleases, {wikiData}) { const latestFirst = albumData .filter((album) => album.isListedOnHomepage) .reverse(); + const majorReleases = latestFirst.filter((album) => album.isMajorRelease); majorReleases.splice(1); diff --git a/test/cacheable-object.js b/test/cacheable-object.js index dd93343..664a648 100644 --- a/test/cacheable-object.js +++ b/test/cacheable-object.js @@ -5,270 +5,270 @@ import CacheableObject from '../src/data/cacheable-object.js'; // Utility function newCacheableObject(PD) { - return new (class extends CacheableObject { - static propertyDescriptors = PD; - }); + return new (class extends CacheableObject { + static propertyDescriptors = PD; + }); } // Tests test(`CacheableObject simple separate update & expose`, t => { - const obj = newCacheableObject({ - number: { - flags: { - update: true - } - }, - - timesTwo: { - flags: { - expose: true - }, - - expose: { - dependencies: ['number'], - compute: ({ number }) => number * 2 - } - } - }); + const obj = newCacheableObject({ + number: { + flags: { + update: true + } + }, + + timesTwo: { + flags: { + expose: true + }, + + expose: { + dependencies: ['number'], + compute: ({ number }) => number * 2 + } + } + }); - t.plan(1); - obj.number = 5; - t.equal(obj.timesTwo, 10); + t.plan(1); + obj.number = 5; + t.equal(obj.timesTwo, 10); }); test(`CacheableObject basic cache behavior`, t => { - let computeCount = 0; - - const obj = newCacheableObject({ - string: { - flags: { - update: true - } - }, - - karkat: { - flags: { - expose: true - }, - - expose: { - dependencies: ['string'], - compute: ({ string }) => { - computeCount++; - return string.toUpperCase(); - } - } + let computeCount = 0; + + const obj = newCacheableObject({ + string: { + flags: { + update: true + } + }, + + karkat: { + flags: { + expose: true + }, + + expose: { + dependencies: ['string'], + compute: ({ string }) => { + computeCount++; + return string.toUpperCase(); } - }); + } + } + }); - t.plan(8); + t.plan(8); - t.is(computeCount, 0); + t.is(computeCount, 0); - obj.string = 'hello world'; - t.is(computeCount, 0); + obj.string = 'hello world'; + t.is(computeCount, 0); - obj.karkat; - t.is(computeCount, 1); + obj.karkat; + t.is(computeCount, 1); - obj.karkat; - t.is(computeCount, 1); + obj.karkat; + t.is(computeCount, 1); - obj.string = 'testing once again'; - t.is(computeCount, 1); + obj.string = 'testing once again'; + t.is(computeCount, 1); - obj.karkat; - t.is(computeCount, 2); + obj.karkat; + t.is(computeCount, 2); - obj.string = 'testing once again'; - t.is(computeCount, 2); + obj.string = 'testing once again'; + t.is(computeCount, 2); - obj.karkat; - t.is(computeCount, 2); + obj.karkat; + t.is(computeCount, 2); }); test(`CacheableObject combined update & expose (no transform)`, t => { - const obj = newCacheableObject({ - directory: { - flags: { - update: true, - expose: true - } - } - }); + const obj = newCacheableObject({ + directory: { + flags: { + update: true, + expose: true + } + } + }); - t.plan(2); + t.plan(2); - t.directory = 'the-world-revolving'; - t.is(t.directory, 'the-world-revolving'); + t.directory = 'the-world-revolving'; + t.is(t.directory, 'the-world-revolving'); - t.directory = 'chaos-king'; - t.is(t.directory, 'chaos-king'); + t.directory = 'chaos-king'; + t.is(t.directory, 'chaos-king'); }); test(`CacheableObject combined update & expose (basic transform)`, t => { - const obj = newCacheableObject({ - getsRepeated: { - flags: { - update: true, - expose: true - }, - - expose: { - transform: value => value.repeat(2) - } - } - }); + const obj = newCacheableObject({ + getsRepeated: { + flags: { + update: true, + expose: true + }, + + expose: { + transform: value => value.repeat(2) + } + } + }); - t.plan(1); + t.plan(1); - obj.getsRepeated = 'dog'; - t.is(obj.getsRepeated, 'dogdog'); + obj.getsRepeated = 'dog'; + t.is(obj.getsRepeated, 'dogdog'); }); test(`CacheableObject combined update & expose (transform with dependency)`, t => { - const obj = newCacheableObject({ - customRepeat: { - flags: { - update: true, - expose: true - }, - - expose: { - dependencies: ['times'], - transform: (value, { times }) => value.repeat(times) - } - }, - - times: { - flags: { - update: true - } - } - }); + const obj = newCacheableObject({ + customRepeat: { + flags: { + update: true, + expose: true + }, + + expose: { + dependencies: ['times'], + transform: (value, { times }) => value.repeat(times) + } + }, + + times: { + flags: { + update: true + } + } + }); - t.plan(3); + t.plan(3); - obj.customRepeat = 'dog'; - obj.times = 1; - t.is(obj.customRepeat, 'dog'); + obj.customRepeat = 'dog'; + obj.times = 1; + t.is(obj.customRepeat, 'dog'); - obj.times = 5; - t.is(obj.customRepeat, 'dogdogdogdogdog'); + obj.times = 5; + t.is(obj.customRepeat, 'dogdogdogdogdog'); - obj.customRepeat = 'cat'; - t.is(obj.customRepeat, 'catcatcatcatcat'); + obj.customRepeat = 'cat'; + t.is(obj.customRepeat, 'catcatcatcatcat'); }); test(`CacheableObject validate on update`, t => { - const mockError = new TypeError(`Expected a string, not ${typeof value}`); - - const obj = newCacheableObject({ - directory: { - flags: { - update: true, - expose: true - }, - - update: { - validate: value => { - if (typeof value !== 'string') { - throw mockError; - } - return true; - } - } - }, - - date: { - flags: { - update: true, - expose: true - }, - - update: { - validate: value => (value instanceof Date) - } + const mockError = new TypeError(`Expected a string, not ${typeof value}`); + + const obj = newCacheableObject({ + directory: { + flags: { + update: true, + expose: true + }, + + update: { + validate: value => { + if (typeof value !== 'string') { + throw mockError; + } + return true; } - }); + } + }, + + date: { + flags: { + update: true, + expose: true + }, + + update: { + validate: value => (value instanceof Date) + } + } + }); - let thrownError; - t.plan(6); + let thrownError; + t.plan(6); - obj.directory = 'megalovania'; - t.is(obj.directory, 'megalovania'); + obj.directory = 'megalovania'; + t.is(obj.directory, 'megalovania'); - try { - obj.directory = 25; - } catch (err) { - thrownError = err; - } + try { + obj.directory = 25; + } catch (err) { + thrownError = err; + } - t.is(thrownError, mockError); - t.is(obj.directory, 'megalovania'); + t.is(thrownError, mockError); + t.is(obj.directory, 'megalovania'); - const date = new Date(`25 December 2009`); + const date = new Date(`25 December 2009`); - obj.date = date; - t.is(obj.date, date); + obj.date = date; + t.is(obj.date, date); - try { - obj.date = `TWELFTH PERIGEE'S EVE`; - } catch (err) { - thrownError = err; - } + try { + obj.date = `TWELFTH PERIGEE'S EVE`; + } catch (err) { + thrownError = err; + } - t.is(thrownError?.constructor, TypeError); - t.is(obj.date, date); + t.is(thrownError?.constructor, TypeError); + t.is(obj.date, date); }); test(`CacheableObject default update property value`, t => { - const obj = newCacheableObject({ - fruit: { - flags: { - update: true, - expose: true - }, - - update: { - default: 'potassium' - } - } - }); + const obj = newCacheableObject({ + fruit: { + flags: { + update: true, + expose: true + }, + + update: { + default: 'potassium' + } + } + }); - t.plan(1); - t.is(obj.fruit, 'potassium'); + t.plan(1); + t.is(obj.fruit, 'potassium'); }); test(`CacheableObject default property throws if invalid`, t => { - const mockError = new TypeError(`Expected a string, not ${typeof value}`); - - t.plan(1); - - let thrownError; - - try { - newCacheableObject({ - string: { - flags: { - update: true - }, - - update: { - default: 123, - validate: value => { - if (typeof value !== 'string') { - throw mockError; - } - return true; - } - } + const mockError = new TypeError(`Expected a string, not ${typeof value}`); + + t.plan(1); + + let thrownError; + + try { + newCacheableObject({ + string: { + flags: { + update: true + }, + + update: { + default: 123, + validate: value => { + if (typeof value !== 'string') { + throw mockError; } - }); - } catch (err) { - thrownError = err; - } + return true; + } + } + } + }); + } catch (err) { + thrownError = err; + } - t.is(thrownError, mockError); + t.is(thrownError, mockError); }); diff --git a/test/data-validators.js b/test/data-validators.js index a7b9b48..f13f3f0 100644 --- a/test/data-validators.js +++ b/test/data-validators.js @@ -2,41 +2,41 @@ import _test from 'tape'; import { showAggregate } from '../src/util/sugar.js'; import { - // Basic types - isBoolean, - isCountingNumber, - isNumber, - isString, - isStringNonEmpty, - - // Complex types - isArray, - isObject, - validateArrayItems, - - // Wiki data - isDimensions, - isDirectory, - isDuration, - isFileExtension, - validateReference, - validateReferenceList, - - // Compositional utilities - oneOf, + // Basic types + isBoolean, + isCountingNumber, + isNumber, + isString, + isStringNonEmpty, + + // Complex types + isArray, + isObject, + validateArrayItems, + + // Wiki data + isDimensions, + isDirectory, + isDuration, + isFileExtension, + validateReference, + validateReferenceList, + + // Compositional utilities + oneOf, } from '../src/data/validators.js'; function test(msg, fn) { - _test(msg, t => { - try { - fn(t); - } catch (error) { - if (error instanceof AggregateError) { - showAggregate(error); - } - throw error; - } - }); + _test(msg, t => { + try { + fn(t); + } catch (error) { + if (error instanceof AggregateError) { + showAggregate(error); + } + throw error; + } + }); } test.skip = _test.skip; @@ -44,234 +44,234 @@ test.skip = _test.skip; // Basic types test('isBoolean', t => { - t.plan(4); - t.ok(isBoolean(true)); - t.ok(isBoolean(false)); - t.throws(() => isBoolean(1), TypeError); - t.throws(() => isBoolean('yes'), TypeError); + t.plan(4); + t.ok(isBoolean(true)); + t.ok(isBoolean(false)); + t.throws(() => isBoolean(1), TypeError); + t.throws(() => isBoolean('yes'), TypeError); }); test('isNumber', t => { - t.plan(6); - t.ok(isNumber(123)); - t.ok(isNumber(0.05)); - t.ok(isNumber(0)); - t.ok(isNumber(-10)); - t.throws(() => isNumber('413'), TypeError); - t.throws(() => isNumber(true), TypeError); + t.plan(6); + t.ok(isNumber(123)); + t.ok(isNumber(0.05)); + t.ok(isNumber(0)); + t.ok(isNumber(-10)); + t.throws(() => isNumber('413'), TypeError); + t.throws(() => isNumber(true), TypeError); }); test('isCountingNumber', t => { - t.plan(6); - t.ok(isCountingNumber(3)); - t.ok(isCountingNumber(1)); - t.throws(() => isCountingNumber(1.75), TypeError); - t.throws(() => isCountingNumber(0), TypeError); - t.throws(() => isCountingNumber(-1), TypeError); - t.throws(() => isCountingNumber('612'), TypeError); + t.plan(6); + t.ok(isCountingNumber(3)); + t.ok(isCountingNumber(1)); + t.throws(() => isCountingNumber(1.75), TypeError); + t.throws(() => isCountingNumber(0), TypeError); + t.throws(() => isCountingNumber(-1), TypeError); + t.throws(() => isCountingNumber('612'), TypeError); }); test('isString', t => { - t.plan(3); - t.ok(isString('hello!')); - t.ok(isString('')); - t.throws(() => isString(100), TypeError); + t.plan(3); + t.ok(isString('hello!')); + t.ok(isString('')); + t.throws(() => isString(100), TypeError); }); test('isStringNonEmpty', t => { - t.plan(4); - t.ok(isStringNonEmpty('hello!')); - t.throws(() => isStringNonEmpty(''), TypeError); - t.throws(() => isStringNonEmpty(' '), TypeError); - t.throws(() => isStringNonEmpty(100), TypeError); + t.plan(4); + t.ok(isStringNonEmpty('hello!')); + t.throws(() => isStringNonEmpty(''), TypeError); + t.throws(() => isStringNonEmpty(' '), TypeError); + t.throws(() => isStringNonEmpty(100), TypeError); }); // Complex types test('isArray', t => { - t.plan(3); - t.ok(isArray([])); - t.throws(() => isArray({}), TypeError); - t.throws(() => isArray('1, 2, 3'), TypeError); + t.plan(3); + t.ok(isArray([])); + t.throws(() => isArray({}), TypeError); + t.throws(() => isArray('1, 2, 3'), TypeError); }); test.skip('isDate', t => { - // TODO + // TODO }); test('isObject', t => { - t.plan(3); - t.ok(isObject({})); - t.ok(isObject([])); - t.throws(() => isObject(null), TypeError); + t.plan(3); + t.ok(isObject({})); + t.ok(isObject([])); + t.throws(() => isObject(null), TypeError); }); test('validateArrayItems', t => { - t.plan(6); - - t.ok(validateArrayItems(isNumber)([3, 4, 5])); - t.ok(validateArrayItems(validateArrayItems(isNumber))([[3, 4], [4, 5], [6, 7]])); - - let caughtError = null; - try { - validateArrayItems(isNumber)([10, 20, 'one hundred million consorts', 30]); - } catch (err) { - caughtError = err; - } - - t.isNot(caughtError, null); - t.true(caughtError instanceof AggregateError); - t.is(caughtError.errors.length, 1); - t.true(caughtError.errors[0] instanceof TypeError); + t.plan(6); + + t.ok(validateArrayItems(isNumber)([3, 4, 5])); + t.ok(validateArrayItems(validateArrayItems(isNumber))([[3, 4], [4, 5], [6, 7]])); + + let caughtError = null; + try { + validateArrayItems(isNumber)([10, 20, 'one hundred million consorts', 30]); + } catch (err) { + caughtError = err; + } + + t.isNot(caughtError, null); + t.true(caughtError instanceof AggregateError); + t.is(caughtError.errors.length, 1); + t.true(caughtError.errors[0] instanceof TypeError); }); // Wiki data test.skip('isColor', t => { - // TODO + // TODO }); test.skip('isCommentary', t => { - // TODO + // TODO }); test.skip('isContribution', t => { - // TODO + // TODO }); test.skip('isContributionList', t => { - // TODO + // TODO }); test('isDimensions', t => { - t.plan(6); - t.ok(isDimensions([1, 1])); - t.ok(isDimensions([50, 50])); - t.ok(isDimensions([5000, 1])); - t.throws(() => isDimensions([1]), TypeError); - t.throws(() => isDimensions([413, 612, 1025]), TypeError); - t.throws(() => isDimensions('800x200'), TypeError); + t.plan(6); + t.ok(isDimensions([1, 1])); + t.ok(isDimensions([50, 50])); + t.ok(isDimensions([5000, 1])); + t.throws(() => isDimensions([1]), TypeError); + t.throws(() => isDimensions([413, 612, 1025]), TypeError); + t.throws(() => isDimensions('800x200'), TypeError); }); test('isDirectory', t => { - t.plan(6); - t.ok(isDirectory('savior-of-the-waking-world')); - t.ok(isDirectory('MeGaLoVania')); - t.ok(isDirectory('ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_')); - t.throws(() => isDirectory(123), TypeError); - t.throws(() => isDirectory(''), TypeError); - t.throws(() => isDirectory('troll saint nicholas and the quest for the holy pail'), TypeError); + t.plan(6); + t.ok(isDirectory('savior-of-the-waking-world')); + t.ok(isDirectory('MeGaLoVania')); + t.ok(isDirectory('ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_')); + t.throws(() => isDirectory(123), TypeError); + t.throws(() => isDirectory(''), TypeError); + t.throws(() => isDirectory('troll saint nicholas and the quest for the holy pail'), TypeError); }); test('isDuration', t => { - t.plan(5); - t.ok(isDuration(60)); - t.ok(isDuration(0.02)); - t.ok(isDuration(0)); - t.throws(() => isDuration(-1), TypeError); - t.throws(() => isDuration('10:25'), TypeError); + t.plan(5); + t.ok(isDuration(60)); + t.ok(isDuration(0.02)); + t.ok(isDuration(0)); + t.throws(() => isDuration(-1), TypeError); + t.throws(() => isDuration('10:25'), TypeError); }); test('isFileExtension', t => { - t.plan(6); - t.ok(isFileExtension('png')); - t.ok(isFileExtension('jpg')); - t.ok(isFileExtension('sub_loc')); - t.throws(() => isFileExtension(''), TypeError); - t.throws(() => isFileExtension('.jpg'), TypeError); - t.throws(() => isFileExtension('just an image bro!!!!'), TypeError); + t.plan(6); + t.ok(isFileExtension('png')); + t.ok(isFileExtension('jpg')); + t.ok(isFileExtension('sub_loc')); + t.throws(() => isFileExtension(''), TypeError); + t.throws(() => isFileExtension('.jpg'), TypeError); + t.throws(() => isFileExtension('just an image bro!!!!'), TypeError); }); test.skip('isName', t => { - // TODO + // TODO }); test.skip('isURL', t => { - // TODO + // TODO }); test('validateReference', t => { - t.plan(16); - - const typeless = validateReference(); - const track = validateReference('track'); - const album = validateReference('album'); - - t.ok(track('track:doctor')); - t.ok(track('track:MeGaLoVania')); - t.ok(track('Showtime (Imp Strife Mix)')); - t.throws(() => track('track:troll saint nic'), TypeError); - t.throws(() => track('track:'), TypeError); - t.throws(() => track('album:homestuck-vol-1'), TypeError); - - t.ok(album('album:sburb')); - t.ok(album('album:the-wanderers')); - t.ok(album('Homestuck Vol. 8')); - t.throws(() => album('album:Hiveswap Friendsim'), TypeError); - t.throws(() => album('album:'), TypeError); - t.throws(() => album('track:showtime-piano-refrain'), TypeError); - - t.ok(typeless('Hopes and Dreams')); - t.ok(typeless('track:snowdin-town')); - t.throws(() => typeless(''), TypeError); - t.throws(() => typeless('album:undertale-soundtrack')); + t.plan(16); + + const typeless = validateReference(); + const track = validateReference('track'); + const album = validateReference('album'); + + t.ok(track('track:doctor')); + t.ok(track('track:MeGaLoVania')); + t.ok(track('Showtime (Imp Strife Mix)')); + t.throws(() => track('track:troll saint nic'), TypeError); + t.throws(() => track('track:'), TypeError); + t.throws(() => track('album:homestuck-vol-1'), TypeError); + + t.ok(album('album:sburb')); + t.ok(album('album:the-wanderers')); + t.ok(album('Homestuck Vol. 8')); + t.throws(() => album('album:Hiveswap Friendsim'), TypeError); + t.throws(() => album('album:'), TypeError); + t.throws(() => album('track:showtime-piano-refrain'), TypeError); + + t.ok(typeless('Hopes and Dreams')); + t.ok(typeless('track:snowdin-town')); + t.throws(() => typeless(''), TypeError); + t.throws(() => typeless('album:undertale-soundtrack')); }); test('validateReferenceList', t => { - const track = validateReferenceList('track'); - const artist = validateReferenceList('artist'); - - t.plan(9); - - t.ok(track(['track:fallen-down', 'Once Upon a Time'])); - t.ok(artist(['artist:toby-fox', 'Mark Hadley'])); - t.ok(track(['track:amalgam'])); - t.ok(track([])); - - let caughtError = null; - try { - track(['Dog', 'album:vaporwave-2016', 'Cat', 'artist:john-madden']); - } catch (err) { - caughtError = err; - } - - t.isNot(caughtError, null); - t.true(caughtError instanceof AggregateError); - t.is(caughtError.errors.length, 2); - t.true(caughtError.errors[0] instanceof TypeError); - t.true(caughtError.errors[1] instanceof TypeError); + const track = validateReferenceList('track'); + const artist = validateReferenceList('artist'); + + t.plan(9); + + t.ok(track(['track:fallen-down', 'Once Upon a Time'])); + t.ok(artist(['artist:toby-fox', 'Mark Hadley'])); + t.ok(track(['track:amalgam'])); + t.ok(track([])); + + let caughtError = null; + try { + track(['Dog', 'album:vaporwave-2016', 'Cat', 'artist:john-madden']); + } catch (err) { + caughtError = err; + } + + t.isNot(caughtError, null); + t.true(caughtError instanceof AggregateError); + t.is(caughtError.errors.length, 2); + t.true(caughtError.errors[0] instanceof TypeError); + t.true(caughtError.errors[1] instanceof TypeError); }); test('oneOf', t => { - t.plan(11); + t.plan(11); - const isStringOrNumber = oneOf(isString, isNumber); + const isStringOrNumber = oneOf(isString, isNumber); - t.ok(isStringOrNumber('hello world')); - t.ok(isStringOrNumber(42)); - t.throws(() => isStringOrNumber(false)); + t.ok(isStringOrNumber('hello world')); + t.ok(isStringOrNumber(42)); + t.throws(() => isStringOrNumber(false)); - const mockError = new Error(); - const neverSucceeds = () => { - throw mockError; - }; + const mockError = new Error(); + const neverSucceeds = () => { + throw mockError; + }; - const isStringOrGetRekt = oneOf(isString, neverSucceeds); + const isStringOrGetRekt = oneOf(isString, neverSucceeds); - t.ok(isStringOrGetRekt('phew!')); + t.ok(isStringOrGetRekt('phew!')); - let caughtError = null; - try { - isStringOrGetRekt(0xdeadbeef); - } catch (err) { - caughtError = err; - } + let caughtError = null; + try { + isStringOrGetRekt(0xdeadbeef); + } catch (err) { + caughtError = err; + } - t.isNot(caughtError, null); - t.true(caughtError instanceof AggregateError); - t.is(caughtError.errors.length, 2); - t.true(caughtError.errors[0] instanceof TypeError); - t.is(caughtError.errors[0].check, isString); - t.is(caughtError.errors[1], mockError); - t.is(caughtError.errors[1].check, neverSucceeds); + t.isNot(caughtError, null); + t.true(caughtError instanceof AggregateError); + t.is(caughtError.errors.length, 2); + t.true(caughtError.errors[0] instanceof TypeError); + t.is(caughtError.errors[0].check, isString); + t.is(caughtError.errors[1], mockError); + t.is(caughtError.errors[1].check, neverSucceeds); }); diff --git a/test/things.js b/test/things.js index 7274515..f36a499 100644 --- a/test/things.js +++ b/test/things.js @@ -1,71 +1,71 @@ import test from 'tape'; import { - Album, - Thing, - Track, - TrackGroup, + Album, + Thing, + Track, + TrackGroup, } from '../src/data/things.js'; function stubAlbum(tracks) { - const album = new Album(); - const trackGroup = new TrackGroup(); - trackGroup.tracksByRef = tracks.map(t => Thing.getReference(t)); - album.trackGroups = [trackGroup]; - album.trackData = tracks; - return album; + const album = new Album(); + const trackGroup = new TrackGroup(); + trackGroup.tracksByRef = tracks.map(t => Thing.getReference(t)); + album.trackGroups = [trackGroup]; + album.trackData = tracks; + return album; } test(`Track.coverArtDate`, t => { - t.plan(5); + t.plan(5); - // Priority order is as follows, with the last (trackCoverArtDate) being - // greatest priority. - const albumDate = new Date('2010-10-10'); - const albumTrackArtDate = new Date('2012-12-12'); - const trackDateFirstReleased = new Date('2008-08-08'); - const trackCoverArtDate = new Date('2009-09-09'); + // Priority order is as follows, with the last (trackCoverArtDate) being + // greatest priority. + const albumDate = new Date('2010-10-10'); + const albumTrackArtDate = new Date('2012-12-12'); + const trackDateFirstReleased = new Date('2008-08-08'); + const trackCoverArtDate = new Date('2009-09-09'); - const track = new Track(); - track.directory = 'foo'; + const track = new Track(); + track.directory = 'foo'; - const album = stubAlbum([track]); + const album = stubAlbum([track]); - track.albumData = [album]; + track.albumData = [album]; - // 1. coverArtDate defaults to null + // 1. coverArtDate defaults to null - t.is(track.coverArtDate, null); + t.is(track.coverArtDate, null); - // 2. coverArtDate inherits album release date + // 2. coverArtDate inherits album release date - album.date = albumDate; + album.date = albumDate; - // XXX clear cache so change in album's property is reflected - track.albumData = []; - track.albumData = [album]; + // XXX clear cache so change in album's property is reflected + track.albumData = []; + track.albumData = [album]; - t.is(track.coverArtDate, albumDate); + t.is(track.coverArtDate, albumDate); - // 3. coverArtDate inherits album trackArtDate + // 3. coverArtDate inherits album trackArtDate - album.trackArtDate = albumTrackArtDate; + album.trackArtDate = albumTrackArtDate; - // XXX clear cache again - track.albumData = []; - track.albumData = [album]; + // XXX clear cache again + track.albumData = []; + track.albumData = [album]; - t.is(track.coverArtDate, albumTrackArtDate); + t.is(track.coverArtDate, albumTrackArtDate); - // 4. coverArtDate is overridden dateFirstReleased + // 4. coverArtDate is overridden dateFirstReleased - track.dateFirstReleased = trackDateFirstReleased; + track.dateFirstReleased = trackDateFirstReleased; - t.is(track.coverArtDate, trackDateFirstReleased); + t.is(track.coverArtDate, trackDateFirstReleased); - // 5. coverArtDate is overridden coverArtDate + // 5. coverArtDate is overridden coverArtDate - track.coverArtDate = trackCoverArtDate; + track.coverArtDate = trackCoverArtDate; - t.is(track.coverArtDate, trackCoverArtDate); + t.is(track.coverArtDate, trackCoverArtDate); }); -- cgit 1.3.0-6-gf8a5