From bbccaf51222cb4bed73466164496f5bc1030292c Mon Sep 17 00:00:00 2001 From: "(quasar) nebula" Date: Thu, 7 Sep 2023 17:30:54 -0300 Subject: data: roll paired "byRef" and "dynamic" properties into one --- .../dependencies/generateWikiHomeAlbumsRow.js | 2 +- src/data/things/album.js | 75 +++++------ src/data/things/artist.js | 19 +-- src/data/things/cacheable-object.js | 26 ++-- src/data/things/composite.js | 15 +++ src/data/things/flash.js | 34 ++--- src/data/things/group.js | 27 ++-- src/data/things/homepage-layout.js | 60 ++++++--- src/data/things/thing.js | 147 +++++++++++---------- src/data/things/track.js | 132 ++++++++---------- src/data/things/validators.js | 2 +- src/data/things/wiki-info.js | 15 +-- src/data/yaml.js | 133 ++++++++++--------- src/gen-thumbs.js | 9 +- 14 files changed, 341 insertions(+), 355 deletions(-) (limited to 'src') diff --git a/src/content/dependencies/generateWikiHomeAlbumsRow.js b/src/content/dependencies/generateWikiHomeAlbumsRow.js index 99c1be55..cb0860f5 100644 --- a/src/content/dependencies/generateWikiHomeAlbumsRow.js +++ b/src/content/dependencies/generateWikiHomeAlbumsRow.js @@ -16,7 +16,7 @@ export default { sprawl({albumData}, row) { const sprawl = {}; - switch (row.sourceGroupByRef) { + switch (row.sourceGroup) { case 'new-releases': sprawl.albums = getNewReleases(row.countAlbumsFromGroup, {albumData}); break; diff --git a/src/data/things/album.js b/src/data/things/album.js index 9cf58641..88308182 100644 --- a/src/data/things/album.js +++ b/src/data/things/album.js @@ -7,14 +7,12 @@ import Thing, { commentary, color, commentatorArtists, - contribsByRef, contribsPresent, + contributionList, directory, - dynamicContribs, fileExtension, flag, name, - resolvedReferenceList, referenceList, simpleDate, simpleString, @@ -43,25 +41,31 @@ export class Album extends Thing { update: {validate: isDate}, expose: { - dependencies: ['date', 'coverArtistContribsByRef'], - transform: (coverArtDate, { - coverArtistContribsByRef, - date, - }) => - (!empty(coverArtistContribsByRef) + dependencies: ['date', 'coverArtistContribs'], + transform: (coverArtDate, {coverArtistContribs, date}) => + (!empty(coverArtistContribs) ? coverArtDate ?? date ?? null : null), }, }, - artistContribsByRef: contribsByRef(), - coverArtistContribsByRef: contribsByRef(), - trackCoverArtistContribsByRef: contribsByRef(), - wallpaperArtistContribsByRef: contribsByRef(), - bannerArtistContribsByRef: contribsByRef(), + artistContribs: contributionList(), + coverArtistContribs: contributionList(), + trackCoverArtistContribs: contributionList(), + wallpaperArtistContribs: contributionList(), + bannerArtistContribs: contributionList(), - groupsByRef: referenceList(Group), - artTagsByRef: referenceList(ArtTag), + groups: referenceList({ + class: Group, + find: find.group, + data: 'groupData', + }), + + artTags: referenceList({ + class: ArtTag, + find: find.artTag, + data: 'artTagData', + }), trackSections: { flags: {update: true, expose: true}, @@ -84,13 +88,12 @@ export class Album extends Thing { isDefaultTrackSection: section.isDefaultTrackSection ?? false, startIndex: ( - startIndex += section.tracksByRef.length, - startIndex - section.tracksByRef.length + startIndex += section.tracks.length, + startIndex - section.tracks.length ), - tracksByRef: section.tracksByRef ?? [], tracks: - (trackData && section.tracksByRef + (trackData && section.tracks ?.map(ref => find.track(ref, trackData, {mode: 'quiet'})) .filter(Boolean)) ?? [], @@ -128,29 +131,11 @@ export class Album extends Thing { // Expose only - artistContribs: dynamicContribs('artistContribsByRef'), - coverArtistContribs: dynamicContribs('coverArtistContribsByRef'), - trackCoverArtistContribs: dynamicContribs('trackCoverArtistContribsByRef'), - wallpaperArtistContribs: dynamicContribs('wallpaperArtistContribsByRef'), - bannerArtistContribs: dynamicContribs('bannerArtistContribsByRef'), - commentatorArtists: commentatorArtists(), - groups: resolvedReferenceList({ - list: 'groupsByRef', - data: 'groupData', - find: find.group, - }), - - artTags: resolvedReferenceList({ - list: 'artTagsByRef', - data: 'artTagData', - find: find.artTag, - }), - - hasCoverArt: contribsPresent('coverArtistContribsByRef'), - hasWallpaperArt: contribsPresent('wallpaperArtistContribsByRef'), - hasBannerArt: contribsPresent('bannerArtistContribsByRef'), + hasCoverArt: contribsPresent('coverArtistContribs'), + hasWallpaperArt: contribsPresent('wallpaperArtistContribs'), + hasBannerArt: contribsPresent('bannerArtistContribs'), tracks: { flags: {expose: true}, @@ -158,12 +143,12 @@ export class Album extends Thing { expose: { dependencies: ['trackSections', 'trackData'], compute: ({trackSections, trackData}) => - trackSections && trackData + (trackSections && trackData ? trackSections - .flatMap((section) => section.tracksByRef ?? []) - .map((ref) => find.track(ref, trackData, {mode: 'quiet'})) + .flatMap(section => section.tracks ?? []) + .map(ref => find.track(ref, trackData, {mode: 'quiet'})) .filter(Boolean) - : [], + : []), }, }, }); diff --git a/src/data/things/artist.js b/src/data/things/artist.js index 2676591a..7a9dbd3c 100644 --- a/src/data/things/artist.js +++ b/src/data/things/artist.js @@ -33,7 +33,12 @@ export class Artist extends Thing { }, isAlias: flag(), - aliasedArtistRef: singleReference(Artist), + + aliasedArtist: singleReference({ + class: Artist, + find: find.artist, + data: 'artistData', + }), // Update only @@ -44,18 +49,6 @@ export class Artist extends Thing { // Expose only - aliasedArtist: { - flags: {expose: true}, - - expose: { - dependencies: ['artistData', 'aliasedArtistRef'], - compute: ({artistData, aliasedArtistRef}) => - aliasedArtistRef && artistData - ? find.artist(aliasedArtistRef, artistData, {mode: 'quiet'}) - : null, - }, - }, - tracksAsArtist: Artist.filterByContrib('trackData', 'artistContribs'), tracksAsContributor: diff --git a/src/data/things/cacheable-object.js b/src/data/things/cacheable-object.js index 92a46d66..4bc3668d 100644 --- a/src/data/things/cacheable-object.js +++ b/src/data/things/cacheable-object.js @@ -86,16 +86,14 @@ export default class CacheableObject { #propertyUpdateValues = Object.create(null); #propertyUpdateCacheInvalidators = Object.create(null); - /* - // Note the constructor doesn't take an initial data source. Due to a quirk - // of JavaScript, private members can't be accessed before the superclass's - // constructor is finished processing - so if we call the overridden - // update() function from inside this constructor, it will error when - // writing to private members. Pretty bad! - // - // That means initial data must be provided by following up with update() - // after constructing the new instance of the Thing (sub)class. - */ + // Note the constructor doesn't take an initial data source. Due to a quirk + // of JavaScript, private members can't be accessed before the superclass's + // constructor is finished processing - so if we call the overridden + // update() function from inside this constructor, it will error when + // writing to private members. Pretty bad! + // + // That means initial data must be provided by following up with update() + // after constructing the new instance of the Thing (sub)class. constructor() { this.#defineProperties(); @@ -352,4 +350,12 @@ export default class CacheableObject { console.log(` - ${line}`); } } + + static getUpdateValue(object, key) { + if (!Object.hasOwn(object, key)) { + return undefined; + } + + return object.#propertyUpdateValues[key] ?? null; + } } diff --git a/src/data/things/composite.js b/src/data/things/composite.js index 29f5770c..96abf4af 100644 --- a/src/data/things/composite.js +++ b/src/data/things/composite.js @@ -1071,3 +1071,18 @@ export function raiseWithoutUpdateValue({ }, ]); } + +export function withUpdateValueAsDependency({ + into = '#updateValue', +} = {}) { + return { + annotation: `withUpdateValueAsDependency`, + flags: {expose: true, compose: true}, + + expose: { + mapContinuation: {into}, + transform: (value, continuation) => + continuation(value, {into: value}), + }, + }; +} diff --git a/src/data/things/flash.js b/src/data/things/flash.js index 4e640dac..eb16d29e 100644 --- a/src/data/things/flash.js +++ b/src/data/things/flash.js @@ -9,13 +9,11 @@ import { } from '#validators'; import Thing, { - dynamicContribs, color, - contribsByRef, + contributionList, fileExtension, name, referenceList, - resolvedReferenceList, simpleDate, simpleString, urls, @@ -60,9 +58,13 @@ export class Flash extends Thing { coverArtFileExtension: fileExtension('jpg'), - contributorContribsByRef: contribsByRef(), + contributorContribs: contributionList(), - featuredTracksByRef: referenceList(Track), + featuredTracks: referenceList({ + class: Track, + find: find.track, + data: 'trackData', + }), urls: urls(), @@ -74,14 +76,6 @@ export class Flash extends Thing { // Expose only - contributorContribs: dynamicContribs('contributorContribsByRef'), - - featuredTracks: resolvedReferenceList({ - list: 'featuredTracksByRef', - data: 'trackData', - find: find.track, - }), - act: { flags: {expose: true}, @@ -138,18 +132,14 @@ export class FlashAct extends Thing { } }, - flashesByRef: referenceList(Flash), + flashes: referenceList({ + class: Flash, + data: 'flashData', + find: find.flash, + }), // Update only flashData: wikiData(Flash), - - // Expose only - - flashes: resolvedReferenceList({ - list: 'flashesByRef', - data: 'flashData', - find: find.flash, - }), }) } diff --git a/src/data/things/group.js b/src/data/things/group.js index 873c6d88..f53fa48e 100644 --- a/src/data/things/group.js +++ b/src/data/things/group.js @@ -5,7 +5,6 @@ import Thing, { directory, name, referenceList, - resolvedReferenceList, simpleString, urls, wikiData, @@ -24,7 +23,11 @@ export class Group extends Thing { urls: urls(), - featuredAlbumsByRef: referenceList(Album), + featuredAlbums: referenceList({ + class: Album, + find: find.album, + data: 'albumData', + }), // Update only @@ -33,12 +36,6 @@ export class Group extends Thing { // Expose only - featuredAlbums: resolvedReferenceList({ - list: 'featuredAlbumsByRef', - data: 'albumData', - find: find.album, - }), - descriptionShort: { flags: {expose: true}, @@ -89,18 +86,14 @@ export class GroupCategory extends Thing { name: name('Unnamed Group Category'), color: color(), - groupsByRef: referenceList(Group), + groups: referenceList({ + class: Group, + find: find.group, + data: 'groupData', + }), // Update only groupData: wikiData(Group), - - // Expose only - - groups: resolvedReferenceList({ - list: 'groupsByRef', - data: 'groupData', - find: find.group, - }), }); } diff --git a/src/data/things/homepage-layout.js b/src/data/things/homepage-layout.js index ab6f4cff..b509c1e2 100644 --- a/src/data/things/homepage-layout.js +++ b/src/data/things/homepage-layout.js @@ -1,23 +1,29 @@ import find from '#find'; +import { + compositeFrom, + exposeDependency, + withUpdateValueAsDependency, +} from '#composite'; + import { is, isCountingNumber, isString, isStringNonEmpty, + oneOf, validateArrayItems, validateInstanceOf, + validateReference, } from '#validators'; import Thing, { color, name, referenceList, - resolvedReference, - resolvedReferenceList, simpleString, - singleReference, wikiData, + withResolvedReference, } from './thing.js'; export class HomepageLayout extends Thing { @@ -101,8 +107,38 @@ export class HomepageLayoutAlbumsRow extends HomepageLayoutRow { }, }, - sourceGroupByRef: singleReference(Group), - sourceAlbumsByRef: referenceList(Album), + sourceGroup: compositeFrom(`HomepageLayoutAlbumsRow.sourceGroup`, [ + { + transform: (value, continuation) => + (value === 'new-releases' || value === 'new-additions' + ? value + : continuation(value)), + }, + + withUpdateValueAsDependency(), + + withResolvedReference({ + ref: '#updateValue', + data: 'groupData', + find: find.group, + }), + + exposeDependency({ + dependency: '#resolvedReference', + update: { + validate: + oneOf( + is('new-releases', 'new-additions'), + validateReference(Group[Thing.referenceType])), + }, + }), + ]), + + sourceAlbums: referenceList({ + class: Album, + find: find.album, + data: 'albumData', + }), countAlbumsFromGroup: { flags: {update: true, expose: true}, @@ -113,19 +149,5 @@ export class HomepageLayoutAlbumsRow extends HomepageLayoutRow { flags: {update: true, expose: true}, update: {validate: validateArrayItems(isString)}, }, - - // Expose only - - sourceGroup: resolvedReference({ - ref: 'sourceGroupByRef', - data: 'groupData', - find: find.group, - }), - - sourceAlbums: resolvedReferenceList({ - list: 'sourceAlbumsByRef', - data: 'albumData', - find: find.album, - }), }); } diff --git a/src/data/things/thing.js b/src/data/things/thing.js index 9d8b2ea2..91ad96af 100644 --- a/src/data/things/thing.js +++ b/src/data/things/thing.js @@ -11,8 +11,11 @@ import {filterMultipleArrays, getKebabCase} from '#wiki-data'; import { compositeFrom, exitWithoutDependency, + exposeConstant, exposeDependency, + exposeDependencyOrContinue, raiseWithoutDependency, + withUpdateValueAsDependency, } from '#composite'; import { @@ -162,22 +165,31 @@ export function externalFunction() { }; } -// Super simple "contributions by reference" list, used for a variety of -// properties (Artists, Cover Artists, etc). This is the property which is -// externally provided, in the form: +// Strong 'n sturdy contribution list, rolling a list of references (provided +// as this property's update value) and the resolved results (as get exposed) +// into one property. Update value will look something like this: // -// [ -// {who: 'Artist Name', what: 'Viola'}, -// {who: 'artist:john-cena', what: null}, -// ... -// ] +// [ +// {who: 'Artist Name', what: 'Viola'}, +// {who: 'artist:john-cena', what: null}, +// ... +// ] // -// ...processed from YAML, spreadsheet, or any other kind of input. -export function contribsByRef() { - return { - flags: {update: true, expose: true}, - update: {validate: isContributionList}, - }; +// ...typically as processed from YAML, spreadsheet, or elsewhere. +// Exposes as the same, but with the "who" replaced with matches found in +// artistData - which means this always depends on an `artistData` property +// also existing on this object! +// +export function contributionList() { + return compositeFrom(`contributionList`, [ + withUpdateValueAsDependency(), + withResolvedContribs({from: '#updateValue'}), + exposeDependencyOrContinue({dependency: '#resolvedContribs'}), + exposeConstant({ + value: [], + update: {validate: isContributionList}, + }), + ]); } // Artist commentary! Generally present on tracks and albums. @@ -222,88 +234,77 @@ export function additionalFiles() { // 'artist' or 'track', but this utility keeps from having to hard-code the // string in multiple places by referencing the value saved on the class // instead. -export function referenceList(thingClass) { - const {[Thing.referenceType]: referenceType} = thingClass; - if (!referenceType) { - throw new Error(`The passed constructor ${thingClass.name} doesn't define Thing.referenceType!`); +export function referenceList({ + class: thingClass, + data, + find, +}) { + if (!thingClass) { + throw new TypeError(`Expected a Thing class`); } - return { - flags: {update: true, expose: true}, - update: {validate: validateReferenceList(referenceType)}, - }; -} - -// Corresponding function for a single reference. -export function singleReference(thingClass) { const {[Thing.referenceType]: referenceType} = thingClass; if (!referenceType) { throw new Error(`The passed constructor ${thingClass.name} doesn't define Thing.referenceType!`); } - return { - flags: {update: true, expose: true}, - update: {validate: validateReference(referenceType)}, - }; -} + return compositeFrom(`referenceList`, [ + withUpdateValueAsDependency(), -// Corresponding dynamic property to referenceList, which takes the values -// in the provided property and searches the specified wiki data for -// matching actual Thing-subclass objects. -export function resolvedReferenceList({list, data, find}) { - return compositeFrom(`resolvedReferenceList`, [ withResolvedReferenceList({ - list, data, find, + data, find, + list: '#updateValue', notFoundMode: 'filter', }), - exposeDependency({dependency: '#resolvedReferenceList'}), + exposeDependency({ + dependency: '#resolvedReferenceList', + update: { + validate: validateReferenceList(referenceType), + }, + }), ]); } // Corresponding function for a single reference. -export function resolvedReference({ref, data, find}) { - return compositeFrom(`resolvedReference`, [ - withResolvedReference({ref, data, find}), - exposeDependency({dependency: '#resolvedReference'}), - ]); -} +export function singleReference({ + class: thingClass, + data, + find, +}) { + if (!thingClass) { + throw new TypeError(`Expected a Thing class`); + } -// Corresponding dynamic property to contribsByRef, which takes the values -// in the provided property and searches the object's artistData for -// matching actual Artist objects. The computed structure has the same form -// as contribsByRef, but with Artist objects instead of string references: -// -// [ -// {who: (an Artist), what: 'Viola'}, -// {who: (an Artist), what: null}, -// ... -// ] -// -// Contributions whose "who" values don't match anything in artistData are -// filtered out. (So if the list is all empty, chances are that either the -// reference list is somehow messed up, or artistData isn't being provided -// properly.) -export function dynamicContribs(contribsByRefProperty) { - return compositeFrom(`dynamicContribs`, [ - withResolvedContribs({from: contribsByRefProperty}), - exposeDependency({dependency: '#resolvedContribs'}), + const {[Thing.referenceType]: referenceType} = thingClass; + if (!referenceType) { + throw new Error(`The passed constructor ${thingClass.name} doesn't define Thing.referenceType!`); + } + + return compositeFrom(`singleReference`, [ + withUpdateValueAsDependency(), + + withResolvedReference({ref: '#updateValue', data, find}), + + exposeDependency({ + dependency: '#resolvedReference', + update: { + validate: validateReference(referenceType), + }, + }), ]); } // Nice 'n simple shorthand for an exposed-only flag which is true when any // contributions are present in the specified property. -export function contribsPresent(contribsByRefProperty) { +export function contribsPresent(contribsProperty) { return { flags: {expose: true}, expose: { - dependencies: [contribsByRefProperty], - compute({ - [contribsByRefProperty]: contribsByRef, - }) { - return !empty(contribsByRef); - }, - } + dependencies: [contribsProperty], + compute: ({[contribsProperty]: contribs}) => + !empty(contribs), + }, }; } @@ -380,13 +381,13 @@ export function withResolvedContribs({ mapDependencies: {from}, compute: ({from}, continuation) => continuation({ - '#whoByRef': from.map(({who}) => who), + '#artistRefs': from.map(({who}) => who), '#what': from.map(({what}) => what), }), }, withResolvedReferenceList({ - list: '#whoByRef', + list: '#artistRefs', data: 'artistData', into: '#who', find: find.artist, diff --git a/src/data/things/track.js b/src/data/things/track.js index fcfd39c7..8263d399 100644 --- a/src/data/things/track.js +++ b/src/data/things/track.js @@ -3,7 +3,6 @@ import {inspect} from 'node:util'; import {colors} from '#cli'; import find from '#find'; import {empty} from '#sugar'; -import {isColor, isDate, isDuration, isFileExtension} from '#validators'; import { compositeFrom, @@ -13,20 +12,28 @@ import { exposeDependencyOrContinue, exposeUpdateValueOrContinue, withResultOfAvailabilityCheck, + withUpdateValueAsDependency, } from '#composite'; +import { + isColor, + isContributionList, + isDate, + isDuration, + isFileExtension, +} from '#validators'; + +import CacheableObject from './cacheable-object.js'; + import Thing, { additionalFiles, commentary, commentatorArtists, - contribsByRef, + contributionList, directory, - dynamicContribs, flag, name, referenceList, - resolvedReference, - resolvedReferenceList, reverseReferenceList, simpleDate, singleReference, @@ -55,13 +62,11 @@ export class Track extends Thing { urls: urls(), dateFirstReleased: simpleDate(), - artistContribsByRef: contribsByRef(), - contributorContribsByRef: contribsByRef(), - coverArtistContribsByRef: contribsByRef(), - - referencedTracksByRef: referenceList(Track), - sampledTracksByRef: referenceList(Track), - artTagsByRef: referenceList(ArtTag), + artTags: referenceList({ + class: ArtTag, + find: find.artTag, + data: 'artTagData', + }), color: compositeFrom(`Track.color`, [ exposeUpdateValueOrContinue(), @@ -134,9 +139,24 @@ export class Track extends Thing { }), ]), - originalReleaseTrackByRef: singleReference(Track), + originalReleaseTrack: singleReference({ + class: Track, + find: find.track, + data: 'trackData', + }), - dataSourceAlbumByRef: singleReference(Album), + // Note - this is an internal property used only to help identify a track. + // It should not be assumed in general that the album and dataSourceAlbum match + // (i.e. a track may dynamically be moved from one album to another, at + // which point dataSourceAlbum refers to where it was originally from, and is + // not generally relevant information). It's also not guaranteed that + // dataSourceAlbum is available (depending on the Track creator to optionally + // provide this property's update value). + dataSourceAlbum: singleReference({ + class: Album, + find: find.album, + data: 'albumData', + }), commentary: commentary(), lyrics: simpleString(), @@ -161,19 +181,6 @@ export class Track extends Thing { exposeDependency({dependency: '#album'}), ]), - // Note - this is an internal property used only to help identify a track. - // It should not be assumed in general that the album and dataSourceAlbum match - // (i.e. a track may dynamically be moved from one album to another, at - // which point dataSourceAlbum refers to where it was originally from, and is - // not generally relevant information). It's also not guaranteed that - // dataSourceAlbum is available (depending on the Track creator to optionally - // provide dataSourceAlbumByRef). - dataSourceAlbum: resolvedReference({ - ref: 'dataSourceAlbumByRef', - data: 'albumData', - find: find.album, - }), - date: compositeFrom(`Track.date`, [ exposeDependencyOrContinue({dependency: 'dateFirstReleased'}), withAlbumProperty({property: 'date'}), @@ -192,11 +199,6 @@ export class Track extends Thing { exposeDependency({dependency: '#hasUniqueCoverArt'}), ]), - originalReleaseTrack: compositeFrom(`Track.originalReleaseTrack`, [ - withOriginalRelease(), - exposeDependency({dependency: '#originalRelease'}), - ]), - otherReleases: compositeFrom(`Track.otherReleases`, [ exitWithoutDependency({dependency: 'trackData', mode: 'empty'}), withOriginalRelease({selfIfOriginal: true}), @@ -224,26 +226,20 @@ export class Track extends Thing { artistContribs: compositeFrom(`Track.artistContribs`, [ inheritFromOriginalRelease({property: 'artistContribs'}), - withResolvedContribs({ - from: 'artistContribsByRef', - into: '#artistContribs', - }), - - { - dependencies: ['#artistContribs'], - compute: ({'#artistContribs': contribsFromTrack}, continuation) => - (empty(contribsFromTrack) - ? continuation() - : contribsFromTrack), - }, + withUpdateValueAsDependency(), + withResolvedContribs({from: '#updateValue', into: '#artistContribs'}), + exposeDependencyOrContinue({dependency: '#artistContribs'}), withAlbumProperty({property: 'artistContribs'}), - exposeDependency({dependency: '#album.artistContribs'}), + exposeDependency({ + dependency: '#album.artistContribs', + update: {validate: isContributionList}, + }), ]), contributorContribs: compositeFrom(`Track.contributorContribs`, [ inheritFromOriginalRelease({property: 'contributorContribs'}), - dynamicContribs('contributorContribsByRef'), + contributionList(), ]), // Cover artists aren't inherited from the original release, since it @@ -258,47 +254,35 @@ export class Track extends Thing { : continuation()), }, - withResolvedContribs({ - from: 'coverArtistContribsByRef', - into: '#coverArtistContribs', - }), - - { - dependencies: ['#coverArtistContribs'], - compute: ({'#coverArtistContribs': contribsFromTrack}, continuation) => - (empty(contribsFromTrack) - ? continuation() - : contribsFromTrack), - }, + withUpdateValueAsDependency(), + withResolvedContribs({from: '#updateValue', into: '#coverArtistContribs'}), + exposeDependencyOrContinue({dependency: '#coverArtistContribs'}), withAlbumProperty({property: 'trackCoverArtistContribs'}), - exposeDependency({dependency: '#album.trackCoverArtistContribs'}), + exposeDependency({ + dependency: '#album.trackCoverArtistContribs', + update: {validate: isContributionList}, + }), ]), referencedTracks: compositeFrom(`Track.referencedTracks`, [ inheritFromOriginalRelease({property: 'referencedTracks'}), - resolvedReferenceList({ - list: 'referencedTracksByRef', - data: 'trackData', + referenceList({ + class: Track, find: find.track, + data: 'trackData', }), ]), sampledTracks: compositeFrom(`Track.sampledTracks`, [ inheritFromOriginalRelease({property: 'sampledTracks'}), - resolvedReferenceList({ - list: 'sampledTracksByRef', - data: 'trackData', + referenceList({ + class: Track, find: find.track, + data: 'trackData', }), ]), - artTags: resolvedReferenceList({ - list: 'artTagsByRef', - data: 'artTagData', - find: find.artTag, - }), - // Specifically exclude re-releases from this list - while it's useful to // get from a re-release to the tracks it references, re-releases aren't // generally relevant from the perspective of the tracks being referenced. @@ -327,7 +311,7 @@ export class Track extends Thing { parts.push(Thing.prototype[inspect.custom].apply(this)); - if (this.originalReleaseTrackByRef) { + if (CacheableObject.getUpdateValue(this, 'originalReleaseTrack')) { parts.unshift(`${colors.yellow('[rerelease]')} `); } @@ -564,7 +548,7 @@ function withOriginalRelease({ } = {}) { return compositeFrom(`withOriginalRelease`, [ withResolvedReference({ - ref: 'originalReleaseTrackByRef', + ref: 'originalReleaseTrack', data: 'trackData', into: '#originalRelease', find: find.track, @@ -607,7 +591,7 @@ function withHasUniqueCoverArt({ }, withResolvedContribs({ - from: 'coverArtistContribsByRef', + from: 'coverArtistContribs', into: '#coverArtistContribs', }), diff --git a/src/data/things/validators.js b/src/data/things/validators.js index 4c8f683b..f0d1d9fd 100644 --- a/src/data/things/validators.js +++ b/src/data/things/validators.js @@ -308,7 +308,7 @@ export const isTrackSection = validateProperties({ color: optional(isColor), dateOriginallyReleased: optional(isDate), isDefaultTrackSection: optional(isBoolean), - tracksByRef: optional(validateReferenceList('track')), + tracks: optional(validateReferenceList('track')), }); export const isTrackSectionList = validateArrayItems(isTrackSection); diff --git a/src/data/things/wiki-info.js b/src/data/things/wiki-info.js index 416b6c4e..7c2de324 100644 --- a/src/data/things/wiki-info.js +++ b/src/data/things/wiki-info.js @@ -6,7 +6,6 @@ import Thing, { flag, name, referenceList, - resolvedReferenceList, simpleString, wikiData, } from './thing.js'; @@ -45,7 +44,11 @@ export class WikiInfo extends Thing { update: {validate: isURL}, }, - divideTrackListsByGroupsByRef: referenceList(Group), + divideTrackListsByGroups: referenceList({ + class: Group, + find: find.group, + data: 'groupData', + }), // Feature toggles enableFlashesAndGames: flag(false), @@ -57,13 +60,5 @@ export class WikiInfo extends Thing { // Update only groupData: wikiData(Group), - - // Expose only - - divideTrackListsByGroups: resolvedReferenceList({ - list: 'divideTrackListsByGroupsByRef', - data: 'groupData', - find: find.group, - }), }); } diff --git a/src/data/yaml.js b/src/data/yaml.js index 8aca3299..e1e5803d 100644 --- a/src/data/yaml.js +++ b/src/data/yaml.js @@ -10,7 +10,7 @@ import yaml from 'js-yaml'; import {colors, ENABLE_COLOR, logInfo, logWarn} from '#cli'; import find, {bindFind} from '#find'; import {traverse} from '#node-utils'; -import T from '#things'; +import T, {CacheableObject, Thing} from '#things'; import { conditionallySuppressError, @@ -278,11 +278,11 @@ export const processAlbumDocument = makeProcessDocument(T.Album, { coverArtFileExtension: 'Cover Art File Extension', trackCoverArtFileExtension: 'Track Art File Extension', - wallpaperArtistContribsByRef: 'Wallpaper Artists', + wallpaperArtistContribs: 'Wallpaper Artists', wallpaperStyle: 'Wallpaper Style', wallpaperFileExtension: 'Wallpaper File Extension', - bannerArtistContribsByRef: 'Banner Artists', + bannerArtistContribs: 'Banner Artists', bannerStyle: 'Banner Style', bannerFileExtension: 'Banner File Extension', bannerDimensions: 'Banner Dimensions', @@ -290,11 +290,11 @@ export const processAlbumDocument = makeProcessDocument(T.Album, { commentary: 'Commentary', additionalFiles: 'Additional Files', - artistContribsByRef: 'Artists', - coverArtistContribsByRef: 'Cover Artists', - trackCoverArtistContribsByRef: 'Default Track Cover Artists', - groupsByRef: 'Groups', - artTagsByRef: 'Art Tags', + artistContribs: 'Artists', + coverArtistContribs: 'Cover Artists', + trackCoverArtistContribs: 'Default Track Cover Artists', + groups: 'Groups', + artTags: 'Art Tags', }, }); @@ -348,13 +348,13 @@ export const processTrackDocument = makeProcessDocument(T.Track, { sheetMusicFiles: 'Sheet Music Files', midiProjectFiles: 'MIDI Project Files', - originalReleaseTrackByRef: 'Originally Released As', - referencedTracksByRef: 'Referenced Tracks', - sampledTracksByRef: 'Sampled Tracks', - artistContribsByRef: 'Artists', - contributorContribsByRef: 'Contributors', - coverArtistContribsByRef: 'Cover Artists', - artTagsByRef: 'Art Tags', + originalReleaseTrack: 'Originally Released As', + referencedTracks: 'Referenced Tracks', + sampledTracks: 'Sampled Tracks', + artistContribs: 'Artists', + contributorContribs: 'Contributors', + coverArtistContribs: 'Cover Artists', + artTags: 'Art Tags', }, invalidFieldCombinations: [ @@ -424,8 +424,8 @@ export const processFlashDocument = makeProcessDocument(T.Flash, { date: 'Date', coverArtFileExtension: 'Cover Art File Extension', - featuredTracksByRef: 'Featured Tracks', - contributorContribsByRef: 'Contributors', + featuredTracks: 'Featured Tracks', + contributorContribs: 'Contributors', }, }); @@ -470,7 +470,7 @@ export const processGroupDocument = makeProcessDocument(T.Group, { description: 'Description', urls: 'URLs', - featuredAlbumsByRef: 'Featured Albums', + featuredAlbums: 'Featured Albums', }, }); @@ -501,7 +501,7 @@ export const processWikiInfoDocument = makeProcessDocument(T.WikiInfo, { footerContent: 'Footer Content', defaultLanguage: 'Default Language', canonicalBase: 'Canonical Base', - divideTrackListsByGroupsByRef: 'Divide Track Lists By Groups', + divideTrackListsByGroups: 'Divide Track Lists By Groups', enableFlashesAndGames: 'Enable Flashes & Games', enableListings: 'Enable Listings', enableNews: 'Enable News', @@ -536,9 +536,9 @@ export const homepageLayoutRowTypeProcessMapping = { albums: makeProcessHomepageLayoutRowDocument(T.HomepageLayoutAlbumsRow, { propertyFieldMapping: { displayStyle: 'Display Style', - sourceGroupByRef: 'Group', + sourceGroup: 'Group', countAlbumsFromGroup: 'Count', - sourceAlbumsByRef: 'Albums', + sourceAlbums: 'Albums', actionLinks: 'Actions', }, }), @@ -771,13 +771,13 @@ export const dataSteps = [ let currentTrackSection = { name: `Default Track Section`, isDefaultTrackSection: true, - tracksByRef: [], + tracks: [], }; - const albumRef = T.Thing.getReference(album); + const albumRef = Thing.getReference(album); const closeCurrentTrackSection = () => { - if (!empty(currentTrackSection.tracksByRef)) { + if (!empty(currentTrackSection.tracks)) { trackSections.push(currentTrackSection); } }; @@ -791,7 +791,7 @@ export const dataSteps = [ color: entry.color, dateOriginallyReleased: entry.dateOriginallyReleased, isDefaultTrackSection: false, - tracksByRef: [], + tracks: [], }; continue; @@ -799,9 +799,9 @@ export const dataSteps = [ trackData.push(entry); - entry.dataSourceAlbumByRef = albumRef; + entry.dataSourceAlbum = albumRef; - currentTrackSection.tracksByRef.push(T.Thing.getReference(entry)); + currentTrackSection.tracks.push(Thing.getReference(entry)); } closeCurrentTrackSection(); @@ -825,12 +825,12 @@ export const dataSteps = [ const artistData = results; const artistAliasData = results.flatMap((artist) => { - const origRef = T.Thing.getReference(artist); + const origRef = Thing.getReference(artist); return artist.aliasNames?.map((name) => { const alias = new T.Artist(); alias.name = name; alias.isAlias = true; - alias.aliasedArtistRef = origRef; + alias.aliasedArtist = origRef; alias.artistData = artistData; return alias; }) ?? []; @@ -854,7 +854,7 @@ export const dataSteps = [ save(results) { let flashAct; - let flashesByRef = []; + let flashRefs = []; if (results[0] && !(results[0] instanceof T.FlashAct)) { throw new Error(`Expected an act at top of flash data file`); @@ -863,18 +863,18 @@ export const dataSteps = [ for (const thing of results) { if (thing instanceof T.FlashAct) { if (flashAct) { - Object.assign(flashAct, {flashesByRef}); + Object.assign(flashAct, {flashes: flashRefs}); } flashAct = thing; - flashesByRef = []; + flashRefs = []; } else { - flashesByRef.push(T.Thing.getReference(thing)); + flashRefs.push(Thing.getReference(thing)); } } if (flashAct) { - Object.assign(flashAct, {flashesByRef}); + Object.assign(flashAct, {flashes: flashRefs}); } const flashData = results.filter((x) => x instanceof T.Flash); @@ -897,7 +897,7 @@ export const dataSteps = [ save(results) { let groupCategory; - let groupsByRef = []; + let groupRefs = []; if (results[0] && !(results[0] instanceof T.GroupCategory)) { throw new Error(`Expected a category at top of group data file`); @@ -906,18 +906,18 @@ export const dataSteps = [ for (const thing of results) { if (thing instanceof T.GroupCategory) { if (groupCategory) { - Object.assign(groupCategory, {groupsByRef}); + Object.assign(groupCategory, {groups: groupRefs}); } groupCategory = thing; - groupsByRef = []; + groupRefs = []; } else { - groupsByRef.push(T.Thing.getReference(thing)); + groupRefs.push(Thing.getReference(thing)); } } if (groupCategory) { - Object.assign(groupCategory, {groupsByRef}); + Object.assign(groupCategory, {groups: groupRefs}); } const groupData = results.filter((x) => x instanceof T.Group); @@ -1462,45 +1462,45 @@ export function filterDuplicateDirectories(wikiData) { export function filterReferenceErrors(wikiData) { const referenceSpec = [ ['wikiInfo', processWikiInfoDocument, { - divideTrackListsByGroupsByRef: 'group', + divideTrackListsByGroups: 'group', }], ['albumData', processAlbumDocument, { - artistContribsByRef: '_contrib', - coverArtistContribsByRef: '_contrib', - trackCoverArtistContribsByRef: '_contrib', - wallpaperArtistContribsByRef: '_contrib', - bannerArtistContribsByRef: '_contrib', - groupsByRef: 'group', - artTagsByRef: 'artTag', + artistContribs: '_contrib', + coverArtistContribs: '_contrib', + trackCoverArtistContribs: '_contrib', + wallpaperArtistContribs: '_contrib', + bannerArtistContribs: '_contrib', + groups: 'group', + artTags: 'artTag', }], ['trackData', processTrackDocument, { - artistContribsByRef: '_contrib', - contributorContribsByRef: '_contrib', - coverArtistContribsByRef: '_contrib', - referencedTracksByRef: '_trackNotRerelease', - sampledTracksByRef: '_trackNotRerelease', - artTagsByRef: 'artTag', - originalReleaseTrackByRef: '_trackNotRerelease', + artistContribs: '_contrib', + contributorContribs: '_contrib', + coverArtistContribs: '_contrib', + referencedTracks: '_trackNotRerelease', + sampledTracks: '_trackNotRerelease', + artTags: 'artTag', + originalReleaseTrack: '_trackNotRerelease', }], ['groupCategoryData', processGroupCategoryDocument, { - groupsByRef: 'group', + groups: 'group', }], ['homepageLayout.rows', undefined, { - sourceGroupByRef: '_homepageSourceGroup', - sourceAlbumsByRef: 'album', + sourceGroup: '_homepageSourceGroup', + sourceAlbums: 'album', }], ['flashData', processFlashDocument, { - contributorContribsByRef: '_contrib', - featuredTracksByRef: 'track', + contributorContribs: '_contrib', + featuredTracks: 'track', }], ['flashActData', processFlashActDocument, { - flashesByRef: 'flash', + flashes: 'flash', }], ]; @@ -1532,7 +1532,7 @@ export function filterReferenceErrors(wikiData) { nest({message: `Reference errors in ${inspect(thing)}`}, ({push, filter}) => { for (const [property, findFnKey] of Object.entries(propSpec)) { - const value = thing[property]; + const value = CacheableObject.getUpdateValue(thing, property); if (value === undefined) { push(new TypeError(`Property ${colors.red(property)} isn't valid for ${colors.green(thing.constructor.name)}`)); @@ -1552,7 +1552,7 @@ export function filterReferenceErrors(wikiData) { if (alias) { // No need to check if the original exists here. Aliases are automatically // created from a field on the original, so the original certainly exists. - const original = find.artist(alias.aliasedArtistRef, wikiData.artistData, {mode: 'quiet'}); + const original = alias.aliasedArtist; throw new Error(`Reference ${colors.red(contribRef.who)} is to an alias, should be ${colors.green(original.name)}`); } @@ -1573,12 +1573,13 @@ export function filterReferenceErrors(wikiData) { case '_trackNotRerelease': findFn = trackRef => { const track = find.track(trackRef, wikiData.trackData, {mode: 'error'}); + const originalRef = track && CacheableObject.getUpdateValue(track, 'originalReleaseTrack'); - if (track?.originalReleaseTrackByRef) { + if (originalRef) { // It's possible for the original to not actually exist, in this case. // It should still be reported since the 'Originally Released As' field // was present. - const original = find.track(track.originalReleaseTrackByRef, wikiData.trackData, {mode: 'quiet'}); + const original = find.track(originalRef, wikiData.trackData, {mode: 'quiet'}); // Prefer references by name, but only if it's unambiguous. const originalByName = @@ -1591,7 +1592,7 @@ export function filterReferenceErrors(wikiData) { ? colors.green(original.name) : original ? colors.green('track:' + original.directory) - : colors.green(track.originalReleaseTrackByRef)); + : colors.green(originalRef)); throw new Error(`Reference ${colors.red(trackRef)} is to a rerelease, should be ${shouldBeMessage}`); } @@ -1606,7 +1607,7 @@ export function filterReferenceErrors(wikiData) { } const suppress = fn => conditionallySuppressError(error => { - if (property === 'sampledTracksByRef') { + if (property === 'sampledTracks') { // Suppress "didn't match anything" errors in particular, just for samples. // In hsmusic-data we have a lot of "stub" sample data which don't have // corresponding tracks yet, so it won't be useful to report such reference diff --git a/src/gen-thumbs.js b/src/gen-thumbs.js index fafd17f6..4977ade7 100644 --- a/src/gen-thumbs.js +++ b/src/gen-thumbs.js @@ -93,6 +93,9 @@ import * as path from 'node:path'; import dimensionsOf from 'image-size'; +import {delay, empty, queue} from '#sugar'; +import {CacheableObject} from '#things'; + import { colors, fileIssue, @@ -110,8 +113,6 @@ import { traverse, } from '#node-utils'; -import {delay, empty, queue} from '#sugar'; - export const defaultMagickThreads = 8; export function getThumbnailsAvailableForDimensions([width, height]) { @@ -608,8 +609,8 @@ export function getExpectedImagePaths(mediaPath, {urls, wikiData}) { wikiData.albumData .flatMap(album => [ album.hasCoverArt && fromRoot.to('media.albumCover', album.directory, album.coverArtFileExtension), - !empty(album.bannerArtistContribsByRef) && fromRoot.to('media.albumBanner', album.directory, album.bannerFileExtension), - !empty(album.wallpaperArtistContribsByRef) && fromRoot.to('media.albumWallpaper', album.directory, album.wallpaperFileExtension), + !empty(CacheableObject.getUpdateValue(album, 'bannerArtistContribs')) && fromRoot.to('media.albumBanner', album.directory, album.bannerFileExtension), + !empty(CacheableObject.getUpdateValue(album, 'wallpaperArtistContribs')) && fromRoot.to('media.albumWallpaper', album.directory, album.wallpaperFileExtension), ]) .filter(Boolean), -- cgit 1.3.0-6-gf8a5