From ad146774480bb29cff04b50d887e132f3bd3f64f Mon Sep 17 00:00:00 2001 From: "(quasar) nebula" Date: Tue, 30 Jan 2024 14:04:41 -0400 Subject: find, data: move find specs into Thing subclasses --- src/data/thing.js | 1 + src/data/things/album.js | 7 ++ src/data/things/art-tag.js | 12 +++ src/data/things/artist.js | 61 +++++++++++- src/data/things/flash.js | 14 +++ src/data/things/group.js | 7 ++ src/data/things/news-entry.js | 7 ++ src/data/things/static-page.js | 7 ++ src/data/things/track.js | 29 ++++++ src/find.js | 215 +++++++++++++++-------------------------- 10 files changed, 223 insertions(+), 137 deletions(-) diff --git a/src/data/thing.js b/src/data/thing.js index 028c0dcf..706e893d 100644 --- a/src/data/thing.js +++ b/src/data/thing.js @@ -13,6 +13,7 @@ export default class Thing extends CacheableObject { static getPropertyDescriptors = Symbol.for('Thing.getPropertyDescriptors'); static getSerializeDescriptors = Symbol.for('Thing.getSerializeDescriptors'); + static findSpecs = Symbol.for('Thing.findSpecs'); static yamlDocumentSpec = Symbol.for('Thing.yamlDocumentSpec'); static getYamlLoadingSpec = Symbol.for('Thing.getYamlLoadingSpec'); diff --git a/src/data/things/album.js b/src/data/things/album.js index 318979cc..c5ef444e 100644 --- a/src/data/things/album.js +++ b/src/data/things/album.js @@ -207,6 +207,13 @@ export class Album extends Thing { commentatorArtists: S.toRefs, }); + static [Thing.findSpecs] = { + album: { + referenceTypes: ['album', 'album-commentary', 'album-gallery'], + bindTo: 'albumData', + }, + }; + static [Thing.yamlDocumentSpec] = { fields: { 'Album': {property: 'name'}, diff --git a/src/data/things/art-tag.js b/src/data/things/art-tag.js index 4d423db5..69fbb526 100644 --- a/src/data/things/art-tag.js +++ b/src/data/things/art-tag.js @@ -65,6 +65,18 @@ export class ArtTag extends Thing { }, }); + static [Thing.findSpecs] = { + artTag: { + referenceTypes: ['tag'], + bindTo: 'artTagData', + + getMatchableNames: tag => + (tag.isContentWarning + ? [`cw: ${tag.name}`] + : [tag.name]), + }, + }; + static [Thing.yamlDocumentSpec] = { fields: { 'Tag': {property: 'name'}, diff --git a/src/data/things/artist.js b/src/data/things/artist.js index a12dc963..589eca90 100644 --- a/src/data/things/artist.js +++ b/src/data/things/artist.js @@ -9,7 +9,7 @@ import find from '#find'; import {stitchArrays, unique} from '#sugar'; import Thing from '#thing'; import {isName, validateArrayItems} from '#validators'; -import {sortAlphabetically} from '#wiki-data'; +import {getKebabCase, sortAlphabetically} from '#wiki-data'; import {withReverseContributionList} from '#composite/wiki-data'; @@ -28,6 +28,7 @@ import { export class Artist extends Thing { static [Thing.referenceType] = 'artist'; + static [Thing.wikiDataArray] = 'artistData'; static [Thing.getPropertyDescriptors] = ({Album, Flash, Track}) => ({ // Update & expose @@ -247,6 +248,64 @@ export class Artist extends Thing { flashesAsContributor: S.toRefs, }); + static [Thing.findSpecs] = { + artist: { + referenceTypes: ['artist', 'artist-gallery'], + bindTo: 'artistData', + + include: artist => !artist.isAlias, + }, + + artistIncludingAliases: { + referenceTypes: ['artist', 'artist-gallery'], + bindTo: 'artistData', + + getMatchableDirectories(artist) { + // Regular artists are always matchable by their directory. + if (!artist.isAlias) { + return [artist.directory]; + } + + const originalArtist = artist.aliasedArtist; + + // Aliases never match by the same directory as the original. + if (artist.directory === originalArtist.directory) { + return []; + } + + // Aliases never match by the same directory as some *previous* alias + // in the original's alias list. This is honestly a bit awkward, but it + // avoids artist aliases conflicting with each other when checking for + // duplicate directories. + for (const aliasName of originalArtist.aliasNames) { + // These are trouble. We should be accessing aliases' directories + // directly, but artists currently don't expose a reverse reference + // list for aliases. (This is pending a cleanup of "reverse reference" + // behavior in general.) It doesn't actually cause problems *here* + // because alias directories are computed from their names 100% of the + // time, but that *is* an assumption this code makes. + if (aliasName === artist.name) continue; + if (artist.directory === getKebabCase(aliasName)) { + return []; + } + } + + // And, aliases never return just a blank string. This part is pretty + // spooky because it doesn't handle two differently named aliases, on + // different artists, who have names that are similar *apart* from a + // character that's shortened. But that's also so fundamentally scary + // that we can't support it properly with existing code, anyway - we + // would need to be able to specifically set a directory *on an alias,* + // which currently can't be done in YAML data files. + if (artist.directory === '') { + return []; + } + + return [artist.directory]; + }, + }, + }; + static [Thing.yamlDocumentSpec] = { fields: { 'Artist': {property: 'name'}, diff --git a/src/data/things/flash.js b/src/data/things/flash.js index 93653548..4823f723 100644 --- a/src/data/things/flash.js +++ b/src/data/things/flash.js @@ -130,6 +130,13 @@ export class Flash extends Thing { color: S.id, }); + static [Thing.findSpecs] = { + flash: { + referenceTypes: ['flash'], + bindTo: 'flashData', + }, + }; + static [Thing.yamlDocumentSpec] = { fields: { 'Flash': {property: 'name'}, @@ -193,6 +200,13 @@ export class FlashAct extends Thing { }), }); + static [Thing.findSpecs] = { + flashAct: { + referenceTypes: ['flash-act'], + bindTo: 'flashActData', + }, + }; + static [Thing.yamlDocumentSpec] = { fields: { 'Act': {property: 'name'}, diff --git a/src/data/things/group.js b/src/data/things/group.js index 462c928a..b6fba79c 100644 --- a/src/data/things/group.js +++ b/src/data/things/group.js @@ -87,6 +87,13 @@ export class Group extends Thing { }, }); + static [Thing.findSpecs] = { + group: { + referenceTypes: ['group', 'group-gallery'], + bindTo: 'groupData', + }, + }; + static [Thing.yamlDocumentSpec] = { fields: { 'Group': {property: 'name'}, diff --git a/src/data/things/news-entry.js b/src/data/things/news-entry.js index e0ec7f7b..cb8e3648 100644 --- a/src/data/things/news-entry.js +++ b/src/data/things/news-entry.js @@ -33,6 +33,13 @@ export class NewsEntry extends Thing { }, }); + static [Thing.findSpecs] = { + newsEntry: { + referenceTypes: ['news-entry'], + bindTo: 'newsData', + }, + }; + static [Thing.yamlDocumentSpec] = { fields: { 'Name': {property: 'name'}, diff --git a/src/data/things/static-page.js b/src/data/things/static-page.js index 9c7fec65..69cbfa11 100644 --- a/src/data/things/static-page.js +++ b/src/data/things/static-page.js @@ -35,6 +35,13 @@ export class StaticPage extends Thing { script: simpleString(), }); + static [Thing.findSpecs] = { + staticPage: { + referenceTypes: ['static'], + bindTo: 'staticPageData', + }, + }; + static [Thing.yamlDocumentSpec] = { fields: { 'Name': {property: 'name'}, diff --git a/src/data/things/track.js b/src/data/things/track.js index d1a12aac..5a8bec3a 100644 --- a/src/data/things/track.js +++ b/src/data/things/track.js @@ -451,6 +451,35 @@ export class Track extends Thing { ], }; + static [Thing.findSpecs] = { + track: { + referenceTypes: ['track'], + bindTo: 'trackData', + + getMatchableNames: track => + (track.alwaysReferenceByDirectory + ? [] + : [track.name]), + }, + + trackOriginalReleasesOnly: { + referenceTypes: ['track'], + bindTo: 'trackData', + + include: track => + !CacheableObject.getUpdateValue(track, 'originalReleaseTrack'), + + // It's still necessary to check alwaysReferenceByDirectory here, since + // it may be set manually (with `Always Reference By Directory: true`), + // and these shouldn't be matched by name (as per usual). + // See the definition for that property for more information. + getMatchableNames: track => + (track.alwaysReferenceByDirectory + ? [] + : [track.name]), + }, + }; + // Track YAML loading is handled in album.js. static [Thing.getYamlLoadingSpec] = null; diff --git a/src/find.js b/src/find.js index fecf1ab0..81f910d9 100644 --- a/src/find.js +++ b/src/find.js @@ -2,8 +2,8 @@ import {inspect} from 'node:util'; import CacheableObject from '#cacheable-object'; import {colors, logWarn} from '#cli'; +import thingConstructors from '#things'; import {typeAppearance} from '#sugar'; -import {getKebabCase} from '#wiki-data'; function warnOrThrow(mode, message) { if (mode === 'error') { @@ -149,125 +149,75 @@ function findHelper({ }; } -const find = { - album: findHelper({ - referenceTypes: ['album', 'album-commentary', 'album-gallery'], - }), - - artist: findHelper({ - referenceTypes: ['artist', 'artist-gallery'], - - include: artist => !artist.isAlias, - }), +const hardcodedFindSpecs = { + // Listings aren't Thing objects, so this find spec isn't provided by any + // Thing constructor. + listing: { + referenceTypes: ['listing'], + bindTo: 'listingSpec', + }, +}; - artistIncludingAliases: findHelper({ - referenceTypes: ['artist', 'artist-gallery'], +export function getAllFindSpecs(key) { + try { + thingConstructors; + } catch (error) { + throw new Error(`Thing constructors aren't ready yet, can't get all find specs`); + } - getMatchableDirectories(artist) { - // Regular artists are always matchable by their directory. - if (!artist.isAlias) { - return [artist.directory]; - } + const findSpecs = {...hardcodedFindSpecs}; - const originalArtist = artist.aliasedArtist; + for (const thingConstructor of Object.values(thingConstructors)) { + const thingFindSpecs = thingConstructor[Symbol.for('Thing.findSpecs')]; + if (!thingFindSpecs) continue; - // Aliases never match by the same directory as the original. - if (artist.directory === originalArtist.directory) { - return []; - } + Object.assign(findSpecs, thingFindSpecs); + } - // Aliases never match by the same directory as some *previous* alias - // in the original's alias list. This is honestly a bit awkward, but it - // avoids artist aliases conflicting with each other when checking for - // duplicate directories. - for (const aliasName of originalArtist.aliasNames) { - // These are trouble. We should be accessing aliases' directories - // directly, but artists currently don't expose a reverse reference - // list for aliases. (This is pending a cleanup of "reverse reference" - // behavior in general.) It doesn't actually cause problems *here* - // because alias directories are computed from their names 100% of the - // time, but that *is* an assumption this code makes. - if (aliasName === artist.name) continue; - if (artist.directory === getKebabCase(aliasName)) { - return []; - } - } + return findSpecs; +} - // And, aliases never return just a blank string. This part is pretty - // spooky because it doesn't handle two differently named aliases, on - // different artists, who have names that are similar *apart* from a - // character that's shortened. But that's also so fundamentally scary - // that we can't support it properly with existing code, anyway - we - // would need to be able to specifically set a directory *on an alias,* - // which currently can't be done in YAML data files. - if (artist.directory === '') { - return []; - } +export function findFindSpec(key) { + if (Object.hasOwn(hardcodedFindSpecs, key)) { + return hardcodedFindSpecs[key]; + } - return [artist.directory]; - }, - }), + try { + thingConstructors; + } catch (error) { + throw new Error(`Thing constructors aren't ready yet, can't check if "find.${key}" available`); + } - artTag: findHelper({ - referenceTypes: ['tag'], + for (const thingConstructor of Object.values(thingConstructors)) { + const thingFindSpecs = thingConstructor[Symbol.for('Thing.findSpecs')]; + if (!thingFindSpecs) continue; - getMatchableNames: tag => - (tag.isContentWarning - ? [`cw: ${tag.name}`] - : [tag.name]), - }), + if (Object.hasOwn(thingFindSpecs, key)) { + return thingFindSpecs[key]; + } + } - flash: findHelper({ - referenceTypes: ['flash'], - }), + throw new Error(`"find.${key}" isn't available`); +} - flashAct: findHelper({ - referenceTypes: ['flash-act'], - }), +export default new Proxy({}, { + get: (store, key) => { + if (!Object.hasOwn(store, key)) { + let behavior = (...args) => { + // This will error if the find spec isn't available... + const findSpec = findFindSpec(key); - group: findHelper({ - referenceTypes: ['group', 'group-gallery'], - }), + // ...or, if it is available, replace this function with the + // ready-for-use find function made out of that find spec. + return (behavior = findHelper(findSpec))(...args); + }; - listing: findHelper({ - referenceTypes: ['listing'], - }), - - newsEntry: findHelper({ - referenceTypes: ['news-entry'], - }), - - staticPage: findHelper({ - referenceTypes: ['static'], - }), - - track: findHelper({ - referenceTypes: ['track'], - - getMatchableNames: track => - (track.alwaysReferenceByDirectory - ? [] - : [track.name]), - }), - - trackOriginalReleasesOnly: findHelper({ - referenceTypes: ['track'], - - include: track => - !CacheableObject.getUpdateValue(track, 'originalReleaseTrack'), - - // It's still necessary to check alwaysReferenceByDirectory here, since it - // may be set manually (with the `Always Reference By Directory` field), and - // these shouldn't be matched by name (as per usual). See the definition for - // that property for more information. - getMatchableNames: track => - (track.alwaysReferenceByDirectory - ? [] - : [track.name]), - }), -}; + store[key] = (...args) => behavior(...args); + } -export default find; + return store[key]; + }, +}); // Handy utility function for binding the find.thing() functions to a complete // wikiData object, optionally taking default options to provide to the find @@ -275,34 +225,27 @@ export default find; // called, so if their values change, you'll have to continue with a fresh call // to bindFind. export function bindFind(wikiData, opts1) { - return Object.fromEntries( - Object.entries({ - album: 'albumData', - artist: 'artistData', - artTag: 'artTagData', - flash: 'flashData', - flashAct: 'flashActData', - group: 'groupData', - listing: 'listingSpec', - newsEntry: 'newsData', - staticPage: 'staticPageData', - track: 'trackData', - trackOriginalReleasesOnly: 'trackData', - }).map(([key, value]) => { - const findFn = find[key]; - const thingData = wikiData[value]; - return [ - key, - opts1 - ? (ref, opts2) => - opts2 - ? findFn(ref, thingData, {...opts1, ...opts2}) - : findFn(ref, thingData, opts1) - : (ref, opts2) => - opts2 - ? findFn(ref, thingData, opts2) - : findFn(ref, thingData), - ]; - }) - ); + const findSpecs = getAllFindSpecs(); + + const boundFindFns = {}; + + for (const [key, spec] of Object.entries(findSpecs)) { + if (!spec.bindTo) continue; + + const findFn = findHelper(spec); + const thingData = wikiData[spec.bindTo]; + + boundFindFns[key] = + (opts1 + ? (ref, opts2) => + (opts2 + ? findFn(ref, thingData, {...opts1, ...opts2}) + : findFn(ref, thingData, opts1)) + : (ref, opts2) => + (opts2 + ? findFn(ref, thingData, opts2) + : findFn(ref, thingData))); + } + + return boundFindFns; } -- cgit 1.3.0-6-gf8a5