diff options
| author | (quasar) nebula <qznebula@protonmail.com> | 2025-11-20 15:51:14 -0400 |
|---|---|---|
| committer | (quasar) nebula <qznebula@protonmail.com> | 2025-11-25 07:06:49 -0400 |
| commit | f44b69b6079c80da98aafe9022cb68923e52a03c (patch) | |
| tree | f5899a51a4dba595a031b7a40c3e0ebd1a468380 /src | |
| parent | 03142771e556f9e115709832a98d81942528f10a (diff) | |
data, yaml: save() -> connect(), Thing.wikiData & friends
HOLY GUACAMOLE
Diffstat (limited to 'src')
25 files changed, 346 insertions, 418 deletions
diff --git a/src/data/composite/things/track-section/index.js b/src/data/composite/things/track-section/index.js index f11a2ab5..1da49ea4 100644 --- a/src/data/composite/things/track-section/index.js +++ b/src/data/composite/things/track-section/index.js @@ -1,3 +1,2 @@ -export {default as withAlbum} from './withAlbum.js'; export {default as withContinueCountingFrom} from './withContinueCountingFrom.js'; export {default as withStartCountingFrom} from './withStartCountingFrom.js'; diff --git a/src/data/composite/things/track-section/withAlbum.js b/src/data/composite/things/track-section/withAlbum.js deleted file mode 100644 index e257062e..00000000 --- a/src/data/composite/things/track-section/withAlbum.js +++ /dev/null @@ -1,20 +0,0 @@ -// Gets the track section's album. - -import {templateCompositeFrom} from '#composite'; - -import {withUniqueReferencingThing} from '#composite/wiki-data'; -import {soupyReverse} from '#composite/wiki-properties'; - -export default templateCompositeFrom({ - annotation: `withAlbum`, - - outputs: ['#album'], - - steps: () => [ - withUniqueReferencingThing({ - reverse: soupyReverse.input('albumsWhoseTrackSectionsInclude'), - }).outputs({ - ['#uniqueReferencingThing']: '#album', - }), - ], -}); diff --git a/src/data/composite/things/track-section/withStartCountingFrom.js b/src/data/composite/things/track-section/withStartCountingFrom.js index ef345327..20e18edb 100644 --- a/src/data/composite/things/track-section/withStartCountingFrom.js +++ b/src/data/composite/things/track-section/withStartCountingFrom.js @@ -3,8 +3,6 @@ import {input, templateCompositeFrom} from '#composite'; import {raiseOutputWithoutDependency} from '#composite/control-flow'; import {withNearbyItemFromList, withPropertyFromObject} from '#composite/data'; -import withAlbum from './withAlbum.js'; - export default templateCompositeFrom({ annotation: `withStartCountingFrom`, @@ -29,15 +27,13 @@ export default templateCompositeFrom({ : continuation.raiseOutput({'#startCountingFrom': from})), }, - withAlbum(), - raiseOutputWithoutDependency({ - dependency: '#album', + dependency: 'album', output: input.value({'#startCountingFrom': 1}), }), withPropertyFromObject({ - object: '#album', + object: 'album', property: input.value('trackSections'), }), diff --git a/src/data/composite/things/track/index.js b/src/data/composite/things/track/index.js index 1c203cd9..be276d25 100644 --- a/src/data/composite/things/track/index.js +++ b/src/data/composite/things/track/index.js @@ -3,7 +3,6 @@ export {default as exitWithoutUniqueCoverArt} from './exitWithoutUniqueCoverArt. export {default as inheritContributionListFromMainRelease} from './inheritContributionListFromMainRelease.js'; export {default as inheritFromMainRelease} from './inheritFromMainRelease.js'; export {default as withAllReleases} from './withAllReleases.js'; -export {default as withContainingTrackSection} from './withContainingTrackSection.js'; export {default as withCoverArtistContribs} from './withCoverArtistContribs.js'; export {default as withDate} from './withDate.js'; export {default as withDirectorySuffix} from './withDirectorySuffix.js'; diff --git a/src/data/composite/things/track/withContainingTrackSection.js b/src/data/composite/things/track/withContainingTrackSection.js deleted file mode 100644 index 3d4d081e..00000000 --- a/src/data/composite/things/track/withContainingTrackSection.js +++ /dev/null @@ -1,20 +0,0 @@ -// Gets the track section containing this track from its album's track list. - -import {templateCompositeFrom} from '#composite'; - -import {withUniqueReferencingThing} from '#composite/wiki-data'; -import {soupyReverse} from '#composite/wiki-properties'; - -export default templateCompositeFrom({ - annotation: `withContainingTrackSection`, - - outputs: ['#trackSection'], - - steps: () => [ - withUniqueReferencingThing({ - reverse: soupyReverse.input('trackSectionsWhichInclude'), - }).outputs({ - ['#uniqueReferencingThing']: '#trackSection', - }), - ], -}); diff --git a/src/data/composite/things/track/withDirectorySuffix.js b/src/data/composite/things/track/withDirectorySuffix.js index c3651491..13813eeb 100644 --- a/src/data/composite/things/track/withDirectorySuffix.js +++ b/src/data/composite/things/track/withDirectorySuffix.js @@ -3,7 +3,6 @@ import {input, templateCompositeFrom} from '#composite'; import {raiseOutputWithoutDependency} from '#composite/control-flow'; import {withPropertyFromObject} from '#composite/data'; -import withContainingTrackSection from './withContainingTrackSection.js'; import withSuffixDirectoryFromAlbum from './withSuffixDirectoryFromAlbum.js'; export default templateCompositeFrom({ @@ -20,10 +19,8 @@ export default templateCompositeFrom({ output: input.value({'#directorySuffix': null}), }), - withContainingTrackSection(), - withPropertyFromObject({ - object: '#trackSection', + object: 'trackSection', property: input.value('directorySuffix'), }).outputs({ '#trackSection.directorySuffix': '#directorySuffix', diff --git a/src/data/composite/things/track/withSuffixDirectoryFromAlbum.js b/src/data/composite/things/track/withSuffixDirectoryFromAlbum.js index 30c777b6..047077fd 100644 --- a/src/data/composite/things/track/withSuffixDirectoryFromAlbum.js +++ b/src/data/composite/things/track/withSuffixDirectoryFromAlbum.js @@ -3,8 +3,6 @@ import {input, templateCompositeFrom} from '#composite'; import {withResultOfAvailabilityCheck} from '#composite/control-flow'; import {withPropertyFromObject} from '#composite/data'; -import withContainingTrackSection from './withContainingTrackSection.js'; - export default templateCompositeFrom({ annotation: `withSuffixDirectoryFromAlbum`, @@ -37,10 +35,8 @@ export default templateCompositeFrom({ : continuation()), }, - withContainingTrackSection(), - withPropertyFromObject({ - object: '#trackSection', + object: 'trackSection', property: input.value('suffixTrackDirectories'), }).outputs({ '#trackSection.suffixTrackDirectories': '#suffixDirectoryFromAlbum', diff --git a/src/data/composite/things/track/withTrackNumber.js b/src/data/composite/things/track/withTrackNumber.js index 61428e8c..bb0f1366 100644 --- a/src/data/composite/things/track/withTrackNumber.js +++ b/src/data/composite/things/track/withTrackNumber.js @@ -3,25 +3,21 @@ import {input, templateCompositeFrom} from '#composite'; import {raiseOutputWithoutDependency} from '#composite/control-flow'; import {withIndexInList, withPropertiesFromObject} from '#composite/data'; -import withContainingTrackSection from './withContainingTrackSection.js'; - export default templateCompositeFrom({ annotation: `withTrackNumber`, outputs: ['#trackNumber'], steps: () => [ - withContainingTrackSection(), - // Zero is the fallback, not one, but in most albums the first track // (and its intended output by this composition) will be one. raiseOutputWithoutDependency({ - dependency: '#trackSection', + dependency: 'trackSection', output: input.value({'#trackNumber': 0}), }), withPropertiesFromObject({ - object: '#trackSection', + object: 'trackSection', properties: input.value(['tracks', 'startCountingFrom']), }), diff --git a/src/data/thing.js b/src/data/thing.js index 4fbad5f5..32eff4d1 100644 --- a/src/data/thing.js +++ b/src/data/thing.js @@ -10,6 +10,10 @@ export default class Thing extends CacheableObject { static referenceType = Symbol.for('Thing.referenceType'); static friendlyName = Symbol.for('Thing.friendlyName'); + static wikiData = Symbol.for('Thing.wikiData'); + static oneInstancePerWiki = Symbol.for('Thing.oneThingPerWiki'); + static constitutibleProperties = Symbol.for('Thing.constitutibleProperties'); + static getPropertyDescriptors = Symbol.for('Thing.getPropertyDescriptors'); static getSerializeDescriptors = Symbol.for('Thing.getSerializeDescriptors'); diff --git a/src/data/things/adventure.js b/src/data/things/adventure.js index ed5da39b..98b23f39 100644 --- a/src/data/things/adventure.js +++ b/src/data/things/adventure.js @@ -16,6 +16,7 @@ import {Flash, FlashAct} from './flash.js'; export class Adventure extends Thing { static [Thing.referenceType] = 'adventure'; + static [Thing.wikiData] = 'adventureData'; static [Thing.getPropertyDescriptors] = ({FlashAct}) => ({ // > Internal relationships @@ -63,56 +64,41 @@ export class Adventure extends Thing { ? AdventureFlashAct : AdventureFlash), - save(results) { - const adventureData = []; - const flashActData = []; - const flashData = []; + connect({header: adventure, entries}) { + const acts = []; - for (const {header: adventure, entries} of results) { - const acts = []; + let thing, i; + for (i = 0; thing = entries[i]; i++) { + if (thing.isFlashAct) { + const act = thing; + const flashes = []; - let thing, i; - for (i = 0; thing = entries[i]; i++) { - if (thing.isFlashAct) { - const act = thing; - const flashes = []; + for (i++; thing = entries[i]; i++) { + if (thing.isFlash) { + const flash = thing; - for (i++; thing = entries[i]; i++) { - if (thing.isFlash) { - const flash = thing; + flash.act = act; + flashes.push(flash); - flash.act = act; - flashes.push(flash); - flashData.push(flash); - - continue; - } - - i--; - break; + continue; } - act.flashes = flashes; - acts.push(act); - flashActData.push(act); - - continue; + i--; + break; } - if (thing.isFlash) { - throw new Error(`Flashes must be under a flash act`); - } + act.flashes = flashes; + acts.push(act); + + continue; } - adventure.acts = acts; - adventureData.push(adventure); + if (thing.isFlash) { + throw new Error(`Flashes must be under a flash act`); + } } - return { - adventureData, - flashActData, - flashData, - }; + adventure.acts = acts; }, }); } diff --git a/src/data/things/album.js b/src/data/things/album.js index c0042d25..defb8a87 100644 --- a/src/data/things/album.js +++ b/src/data/things/album.js @@ -78,11 +78,18 @@ import { } from '#composite/wiki-properties'; import {withCoverArtDate, withTracks} from '#composite/things/album'; -import {withAlbum, withContinueCountingFrom, withStartCountingFrom} +import {withContinueCountingFrom, withStartCountingFrom} from '#composite/things/track-section'; export class Album extends Thing { static [Thing.referenceType] = 'album'; + static [Thing.wikiData] = 'albumData'; + + static [Thing.constitutibleProperties] = [ + 'coverArtworks', + 'wallpaperArtwork', + 'bannerArtwork', + ]; static [Thing.getPropertyDescriptors] = ({ AdditionalFile, @@ -569,20 +576,6 @@ export class Album extends Thing { }; static [Thing.reverseSpecs] = { - albumsWhoseTracksInclude: { - bindTo: 'albumData', - - referencing: album => [album], - referenced: album => album.tracks, - }, - - albumsWhoseTrackSectionsInclude: { - bindTo: 'albumData', - - referencing: album => [album], - referenced: album => album.trackSections, - }, - albumsWhoseArtworksFeature: { bindTo: 'albumData', @@ -882,101 +875,48 @@ export class Album extends Thing { ? TrackSection : Track), - save(results) { - const albumData = []; - const trackSectionData = []; - const trackData = []; - - const artworkData = []; - const commentaryData = []; - const creditingSourceData = []; - const referencingSourceData = []; - const lyricsData = []; - - for (const {header: album, entries} of results) { - const trackSections = []; - - let currentTrackSection = new TrackSection(); - let currentTrackSectionTracks = []; - - Object.assign(currentTrackSection, { - name: `Default Track Section`, - isDefaultTrackSection: true, - }); - - const closeCurrentTrackSection = () => { - if ( - currentTrackSection.isDefaultTrackSection && - empty(currentTrackSectionTracks) - ) { - return; - } - - currentTrackSection.tracks = - currentTrackSectionTracks; - - trackSections.push(currentTrackSection); - trackSectionData.push(currentTrackSection); - }; - - for (const entry of entries) { - if (entry instanceof TrackSection) { - closeCurrentTrackSection(); - currentTrackSection = entry; - currentTrackSectionTracks = []; - continue; - } - - currentTrackSectionTracks.push(entry); - trackData.push(entry); - - // Set the track's album before accessing its list of artworks. - // The existence of its artwork objects may depend on access to - // its album's 'Default Track Cover Artists'. - entry.album = album; - - artworkData.push(...entry.trackArtworks); - commentaryData.push(...entry.commentary); - creditingSourceData.push(...entry.creditingSources); - referencingSourceData.push(...entry.referencingSources); - - // TODO: As exposed, Track.lyrics tries to inherit from the main - // release, which is impossible before the data's been linked. - // We just use the update value here. But it's icky! - lyricsData.push(...CacheableObject.getUpdateValue(entry, 'lyrics') ?? []); - } - - closeCurrentTrackSection(); + connect({header: album, entries}) { + const trackSections = []; - albumData.push(album); + let currentTrackSection = new TrackSection(); + let currentTrackSectionTracks = []; - artworkData.push(...album.coverArtworks); + Object.assign(currentTrackSection, { + name: `Default Track Section`, + isDefaultTrackSection: true, + }); - if (album.bannerArtwork) { - artworkData.push(album.bannerArtwork); + const closeCurrentTrackSection = () => { + if ( + currentTrackSection.isDefaultTrackSection && + empty(currentTrackSectionTracks) + ) { + return; } - if (album.wallpaperArtwork) { - artworkData.push(album.wallpaperArtwork); + currentTrackSection.tracks = currentTrackSectionTracks; + currentTrackSection.album = album; + + trackSections.push(currentTrackSection); + }; + + for (const entry of entries) { + if (entry instanceof TrackSection) { + closeCurrentTrackSection(); + currentTrackSection = entry; + currentTrackSectionTracks = []; + continue; } - commentaryData.push(...album.commentary); - creditingSourceData.push(...album.creditingSources); + entry.album = album; + entry.trackSection = currentTrackSection; - album.trackSections = trackSections; + currentTrackSectionTracks.push(entry); } - return { - albumData, - trackSectionData, - trackData, + closeCurrentTrackSection(); - artworkData, - commentaryData, - creditingSourceData, - referencingSourceData, - lyricsData, - }; + album.trackSections = trackSections; }, sort({albumData, trackData}) { @@ -1041,10 +981,15 @@ export class Album extends Thing { export class TrackSection extends Thing { static [Thing.friendlyName] = `Track Section`; static [Thing.referenceType] = `track-section`; + static [Thing.wikiData] = 'trackSectionData'; static [Thing.getPropertyDescriptors] = ({Track}) => ({ // Update & expose + album: thing({ + class: input.value(Album), + }), + name: name('Unnamed Track Section'), unqualifiedDirectory: directory(), @@ -1054,10 +999,8 @@ export class TrackSection extends Thing { validate: input.value(isDirectory), }), - withAlbum(), - withPropertyFromObject({ - object: '#album', + object: 'album', property: input.value('directorySuffix'), }), @@ -1069,10 +1012,8 @@ export class TrackSection extends Thing { validate: input.value(isBoolean), }), - withAlbum(), - withPropertyFromObject({ - object: '#album', + object: 'album', property: input.value('suffixTrackDirectories'), }), @@ -1084,10 +1025,8 @@ export class TrackSection extends Thing { validate: input.value(isColor), }), - withAlbum(), - withPropertyFromObject({ - object: '#album', + object: 'album', property: input.value('color'), }), @@ -1109,10 +1048,8 @@ export class TrackSection extends Thing { validate: input.value(isBoolean), }), - withAlbum(), - withPropertyFromObject({ - object: '#album', + object: 'album', property: input.value('countTracksInArtistTotals'), }), @@ -1123,11 +1060,6 @@ export class TrackSection extends Thing { description: contentString(), - album: [ - withAlbum(), - exposeDependency({dependency: '#album'}), - ], - tracks: thingList({ class: input.value(Track), }), @@ -1145,14 +1077,12 @@ export class TrackSection extends Thing { ], directory: [ - withAlbum(), - exitWithoutDependency({ - dependency: '#album', + dependency: 'album', }), withPropertyFromObject({ - object: '#album', + object: 'album', property: input.value('directory'), }), @@ -1193,15 +1123,6 @@ export class TrackSection extends Thing { }, }; - static [Thing.reverseSpecs] = { - trackSectionsWhichInclude: { - bindTo: 'trackSectionData', - - referencing: trackSection => [trackSection], - referenced: trackSection => trackSection.tracks, - }, - }; - static [Thing.yamlDocumentSpec] = { fields: { 'Section': {property: 'name'}, diff --git a/src/data/things/art-tag.js b/src/data/things/art-tag.js index fff724cb..3570b2e7 100644 --- a/src/data/things/art-tag.js +++ b/src/data/things/art-tag.js @@ -40,6 +40,7 @@ import {withAllDescendantArtTags, withAncestorArtTagBaobabTree} export class ArtTag extends Thing { static [Thing.referenceType] = 'tag'; static [Thing.friendlyName] = `Art Tag`; + static [Thing.wikiData] = 'artTagData'; static [Thing.getPropertyDescriptors] = ({AdditionalName}) => ({ // Update & expose @@ -210,8 +211,6 @@ export class ArtTag extends Thing { documentMode: allTogether, documentThing: ArtTag, - save: (results) => ({artTagData: results}), - sort({artTagData}) { sortAlphabetically(artTagData); }, diff --git a/src/data/things/artist.js b/src/data/things/artist.js index 24c99698..a5601d60 100644 --- a/src/data/things/artist.js +++ b/src/data/things/artist.js @@ -41,7 +41,11 @@ import {artistTotalDuration} from '#composite/things/artist'; export class Artist extends Thing { static [Thing.referenceType] = 'artist'; - static [Thing.wikiDataArray] = 'artistData'; + static [Thing.wikiData] = 'artistData'; + + static [Thing.constitutibleProperties] = [ + 'avatarArtwork', // from inline fields + ]; static [Thing.getPropertyDescriptors] = () => ({ // Update & expose @@ -343,19 +347,6 @@ export class Artist extends Thing { documentMode: allInOne, documentThing: Artist, - save(results) { - const artists = results; - const artistAliases = artists.flatMap(artist => artist.artistAliases); - const artistData = [...artists, ...artistAliases]; - - const artworkData = - artistData - .filter(artist => artist.hasAvatar) - .map(artist => artist.avatarArtwork); - - return {artistData, artworkData}; - }, - sort({artistData}) { sortAlphabetically(artistData); }, diff --git a/src/data/things/artwork.js b/src/data/things/artwork.js index 916aac0a..c1ae4f62 100644 --- a/src/data/things/artwork.js +++ b/src/data/things/artwork.js @@ -64,6 +64,12 @@ import { export class Artwork extends Thing { static [Thing.referenceType] = 'artwork'; + static [Thing.wikiData] = 'artworkData'; + + static [Thing.constitutibleProperties] = [ + // Contributions currently aren't being observed for constitution. + // 'artistContribs', // from attached artwork or thing + ]; static [Thing.getPropertyDescriptors] = ({ArtTag}) => ({ // Update & expose diff --git a/src/data/things/content.js b/src/data/things/content.js index a3dfc183..95836abd 100644 --- a/src/data/things/content.js +++ b/src/data/things/content.js @@ -154,6 +154,8 @@ export class ContentEntry extends Thing { } export class CommentaryEntry extends ContentEntry { + static [Thing.wikiData] = 'commentaryData'; + static [Thing.getPropertyDescriptors] = () => ({ // Expose only @@ -170,6 +172,8 @@ export class CommentaryEntry extends ContentEntry { } export class LyricsEntry extends ContentEntry { + static [Thing.wikiData] = 'lyricsData'; + static [Thing.getPropertyDescriptors] = () => ({ // Update & expose @@ -223,6 +227,8 @@ export class LyricsEntry extends ContentEntry { } export class CreditingSourcesEntry extends ContentEntry { + static [Thing.wikiData] = 'creditingSourceData'; + static [Thing.getPropertyDescriptors] = () => ({ // Expose only @@ -235,6 +241,8 @@ export class CreditingSourcesEntry extends ContentEntry { } export class ReferencingSourcesEntry extends ContentEntry { + static [Thing.wikiData] = 'referencingSourceData'; + static [Thing.getPropertyDescriptors] = () => ({ // Expose only diff --git a/src/data/things/flash.js b/src/data/things/flash.js index 5c9023fa..19f6093e 100644 --- a/src/data/things/flash.js +++ b/src/data/things/flash.js @@ -46,6 +46,11 @@ import { export class Flash extends Thing { static [Thing.referenceType] = 'flash'; + static [Thing.wikiData] = 'flashData'; + + static [Thing.constitutibleProperties] = [ + 'coverArtwork', // from inline fields + ]; static [Thing.getPropertyDescriptors] = ({ AdditionalName, @@ -274,6 +279,7 @@ export class Flash extends Thing { export class FlashAct extends Thing { static [Thing.referenceType] = 'flash-act'; static [Thing.friendlyName] = `Flash Act`; + static [Thing.wikiData] = 'flashActData'; static [Thing.getPropertyDescriptors] = ({Flash, FlashSide}) => ({ // Update & expose @@ -355,6 +361,7 @@ export class FlashAct extends Thing { export class FlashSide extends Thing { static [Thing.referenceType] = 'flash-side'; static [Thing.friendlyName] = `Flash Side`; + static [Thing.wikiData] = 'flashSideData'; static [Thing.getPropertyDescriptors] = ({FlashAct}) => ({ // Update & expose @@ -421,15 +428,7 @@ export class FlashSide extends Thing { ? FlashAct : Flash), - save(results) { - const flashSideData = []; - const flashActData = []; - const flashData = []; - - const artworkData = []; - const commentaryData = []; - const creditingSourceData = []; - + connect(results) { let thing, i; for (i = 0; thing = results[i]; i++) { @@ -449,11 +448,6 @@ export class FlashSide extends Thing { flash.act = act; flashes.push(flash); - flashData.push(flash); - artworkData.push(flash.coverArtwork); - commentaryData.push(...flash.commentary); - creditingSourceData.push(...flash.creditingSources); - continue; } @@ -465,8 +459,6 @@ export class FlashSide extends Thing { act.flashes = flashes; acts.push(act); - flashActData.push(act); - continue; } @@ -480,8 +472,6 @@ export class FlashSide extends Thing { side.acts = acts; - flashSideData.push(side); - continue; } @@ -493,16 +483,6 @@ export class FlashSide extends Thing { throw new Error(`Flashes must be under a side and act`); } } - - return { - flashSideData, - flashActData, - flashData, - - artworkData, - commentaryData, - creditingSourceData, - }; }, sort({flashData}) { diff --git a/src/data/things/group.js b/src/data/things/group.js index 0935dc93..ac051343 100644 --- a/src/data/things/group.js +++ b/src/data/things/group.js @@ -34,6 +34,7 @@ import { export class Group extends Thing { static [Thing.referenceType] = 'group'; + static [Thing.wikiData] = 'groupData'; static [Thing.getPropertyDescriptors] = ({Album, Artist, Series}) => ({ // Update & expose @@ -217,7 +218,7 @@ export class Group extends Thing { ? GroupCategory : Group), - save(results) { + connect(results) { let groupCategory; let groupRefs = []; @@ -241,12 +242,6 @@ export class Group extends Thing { if (groupCategory) { Object.assign(groupCategory, {groups: groupRefs}); } - - const groupData = results.filter(x => x instanceof Group); - const groupCategoryData = results.filter(x => x instanceof GroupCategory); - const seriesData = groupData.flatMap(group => group.serieses); - - return {groupData, groupCategoryData, seriesData}; }, // Groups aren't sorted at all, always preserving the order in the data @@ -258,6 +253,7 @@ export class Group extends Thing { export class GroupCategory extends Thing { static [Thing.referenceType] = 'group-category'; static [Thing.friendlyName] = `Group Category`; + static [Thing.wikiData] = 'groupCategoryData'; static [Thing.getPropertyDescriptors] = ({Group}) => ({ // Update & expose @@ -310,6 +306,8 @@ export class GroupCategory extends Thing { } export class Series extends Thing { + static [Thing.wikiData] = 'seriesData'; + static [Thing.getPropertyDescriptors] = ({Album, Group}) => ({ // Update & expose diff --git a/src/data/things/homepage-layout.js b/src/data/things/homepage-layout.js index 7c97935e..5da13e37 100644 --- a/src/data/things/homepage-layout.js +++ b/src/data/things/homepage-layout.js @@ -32,6 +32,8 @@ import { export class HomepageLayout extends Thing { static [Thing.friendlyName] = `Homepage Layout`; + static [Thing.wikiData] = 'homepageLayout'; + static [Thing.oneInstancePerWiki] = true; static [Thing.getPropertyDescriptors] = ({HomepageLayoutSection}) => ({ // Update & expose @@ -102,7 +104,7 @@ export class HomepageLayout extends Thing { return null; }, - save(results) { + connect(results) { if (!empty(results) && !(results[0] instanceof HomepageLayout)) { throw new Error(`Expected 'Homepage' document at top of homepage layout file`); } @@ -145,8 +147,6 @@ export class HomepageLayout extends Thing { closeCurrentSection(); homepageLayout.sections = sections; - - return {homepageLayout}; }, }); } diff --git a/src/data/things/news-entry.js b/src/data/things/news-entry.js index 28289f53..e5467a46 100644 --- a/src/data/things/news-entry.js +++ b/src/data/things/news-entry.js @@ -12,6 +12,7 @@ import {contentString, directory, name, simpleDate} export class NewsEntry extends Thing { static [Thing.referenceType] = 'news-entry'; static [Thing.friendlyName] = `News Entry`; + static [Thing.wikiData] = 'newsData'; static [Thing.getPropertyDescriptors] = () => ({ // Update & expose @@ -72,8 +73,6 @@ export class NewsEntry extends Thing { documentMode: allInOne, documentThing: NewsEntry, - save: (results) => ({newsData: results}), - sort({newsData}) { sortChronologically(newsData, {latestFirst: true}); }, diff --git a/src/data/things/sorting-rule.js b/src/data/things/sorting-rule.js index 8ed3861a..e113955f 100644 --- a/src/data/things/sorting-rule.js +++ b/src/data/things/sorting-rule.js @@ -38,6 +38,7 @@ function isSelectFollowingEntry(value) { export class SortingRule extends Thing { static [Thing.friendlyName] = `Sorting Rule`; + static [Thing.wikiData] = 'sortingRules'; static [Thing.getPropertyDescriptors] = () => ({ // Update & expose @@ -77,8 +78,6 @@ export class SortingRule extends Thing { (document['Sort Documents'] ? DocumentSortingRule : null), - - save: (results) => ({sortingRules: results}), }); check(opts) { diff --git a/src/data/things/static-page.js b/src/data/things/static-page.js index 28167df2..617bc940 100644 --- a/src/data/things/static-page.js +++ b/src/data/things/static-page.js @@ -15,6 +15,7 @@ import {contentString, directory, flag, name, simpleString} export class StaticPage extends Thing { static [Thing.referenceType] = 'static'; static [Thing.friendlyName] = `Static Page`; + static [Thing.wikiData] = 'staticPageData'; static [Thing.getPropertyDescriptors] = () => ({ // Update & expose @@ -86,8 +87,6 @@ export class StaticPage extends Thing { documentMode: onePerFile, documentThing: StaticPage, - save: (results) => ({staticPageData: results}), - sort({staticPageData}) { sortAlphabetically(staticPageData); }, diff --git a/src/data/things/track.js b/src/data/things/track.js index 0d565086..4a24a9e0 100644 --- a/src/data/things/track.js +++ b/src/data/things/track.js @@ -76,7 +76,6 @@ import { inheritContributionListFromMainRelease, inheritFromMainRelease, withAllReleases, - withContainingTrackSection, withCoverArtistContribs, withDate, withDirectorySuffix, @@ -92,6 +91,16 @@ import { export class Track extends Thing { static [Thing.referenceType] = 'track'; + static [Thing.wikiData] = 'trackData'; + + static [Thing.constitutibleProperties] = [ + // Contributions currently aren't being observed for constitution. + // 'artistContribs', // from main release or album + // 'contributorContribs', // from main release + // 'coverArtistContribs', // from main release + + 'trackArtworks', // from inline fields + ]; static [Thing.getPropertyDescriptors] = ({ AdditionalFile, @@ -103,6 +112,7 @@ export class Track extends Thing { CreditingSourcesEntry, LyricsEntry, ReferencingSourcesEntry, + TrackSection, WikiInfo, }) => ({ // > Update & expose - Internal relationships @@ -111,6 +121,10 @@ export class Track extends Thing { class: input.value(Album), }), + trackSection: thing({ + class: input.value(TrackSection), + }), + // > Update & expose - Identifying metadata name: name('Unnamed Track'), @@ -263,10 +277,8 @@ export class Track extends Thing { validate: input.value(isBoolean), }), - withContainingTrackSection(), - withPropertyFromObject({ - object: '#trackSection', + object: 'trackSection', property: input.value('countTracksInArtistTotals'), }), @@ -285,10 +297,8 @@ export class Track extends Thing { validate: input.value(isColor), }), - withContainingTrackSection(), - withPropertyFromObject({ - object: '#trackSection', + object: 'trackSection', property: input.value('color'), }), @@ -510,6 +520,11 @@ export class Track extends Thing { commentatorArtists: commentatorArtists(), + directorySuffix: [ + withDirectorySuffix(), + exposeDependency({dependency: '#directorySuffix'}), + ], + date: [ withDate(), exposeDependency({dependency: '#date'}), diff --git a/src/data/things/wiki-info.js b/src/data/things/wiki-info.js index 7fb6a350..89248d11 100644 --- a/src/data/things/wiki-info.js +++ b/src/data/things/wiki-info.js @@ -28,6 +28,8 @@ import { export class WikiInfo extends Thing { static [Thing.friendlyName] = `Wiki Info`; + static [Thing.wikiData] = 'wikiInfo'; + static [Thing.oneInstancePerWiki] = true; static [Thing.getPropertyDescriptors] = ({Group}) => ({ // Update & expose @@ -168,13 +170,5 @@ export class WikiInfo extends Thing { documentMode: oneDocumentTotal, documentThing: WikiInfo, - - save(wikiInfo) { - if (!wikiInfo) { - return; - } - - return {wikiInfo}; - }, }); } diff --git a/src/data/yaml.js b/src/data/yaml.js index 13dfd24d..4e6f4502 100644 --- a/src/data/yaml.js +++ b/src/data/yaml.js @@ -44,6 +44,32 @@ function inspect(value, opts = {}) { return nodeInspect(value, {colors: ENABLE_COLOR, ...opts}); } +function pushWikiData(a, b) { + for (const key of Object.keys(b)) { + if (Object.hasOwn(a, key)) { + if (Array.isArray(a[key])) { + if (Array.isArray(b[key])) { + a[key].push(...b[key]); + } else { + throw new Error(`${key} already present, expected array of items to push`); + } + } else { + if (Array.isArray(a[key])) { + throw new Error(`${key} already present and not an array, refusing to overwrite`); + } else { + throw new Error(`${key} already present, refusing to overwrite`); + } + } + } else { + if (Array.isArray(b[key])) { + a[key] = [...b[key]]; + } else { + a[key] = b[key]; + } + } + } +} + // General function for inputting a single document (usually loaded from YAML) // and outputting an instance of a provided Thing subclass. // @@ -161,6 +187,16 @@ function makeProcessDocument(thingConstructor, { const thing = Reflect.construct(thingConstructor, []); + const wikiData = {}; + const flat = [thing]; + if (thingConstructor[Thing.wikiData]) { + if (thingConstructor[Thing.oneInstancePerWiki]) { + wikiData[thingConstructor[Thing.wikiData]] = thing; + } else { + wikiData[thingConstructor[Thing.wikiData]] = [thing]; + } + } + const documentEntries = Object.entries(document) .filter(([field]) => !ignoredFields.includes(field)); @@ -312,26 +348,29 @@ function makeProcessDocument(thingConstructor, { const followSubdocSetup = setup => { let error = null; - let subthing; + let result; try { - const result = bouncer(setup.data, setup.documentType); - subthing = result.thing; - result.aggregate.close(); + let aggregate; + ({result, aggregate} = bouncer(setup.data, setup.documentType)); + aggregate.close(); } catch (caughtError) { error = caughtError; } - if (subthing) { + if (result.thing) { if (setup.bindInto) { - subthing[setup.bindInto] = thing; + result.thing[setup.bindInto] = thing; } if (setup.provide) { - Object.assign(subthing, setup.provide); + Object.assign(result.thing, setup.provide); } } - return {error, subthing}; + pushWikiData(wikiData, result.wikiData); + flat.push(...result.flat); + + return {error, subthing: result.thing}; }; for (const [field, layout] of Object.entries(subdocLayouts)) { @@ -414,7 +453,14 @@ function makeProcessDocument(thingConstructor, { {preserveOriginalOrder: true}))); } - return {thing, aggregate}; + return { + aggregate, + result: { + thing, + flat, + wikiData, + }, + }; }); } @@ -1309,26 +1355,35 @@ export function processThingsFromDataStep(documents, dataStep) { switch (documentMode) { case documentModes.allInOne: case documentModes.allTogether: { - const result = []; + const things = []; + const flat = []; + const wikiData = {}; const aggregate = openAggregate({message: `Errors processing documents`}); documents.forEach( decorateErrorWithIndex((document, index) => { - const {thing, aggregate: subAggregate} = + const {result, aggregate: subAggregate} = processDocument(document, dataStep.documentThing); - thing[Thing.yamlSourceDocument] = document; - thing[Thing.yamlSourceDocumentPlacement] = + result.thing[Thing.yamlSourceDocument] = document; + result.thing[Thing.yamlSourceDocumentPlacement] = [documentModes.allInOne, index]; - result.push(thing); + things.push(result.thing); + flat.push(...result.flat); + pushWikiData(wikiData, result.wikiData); + aggregate.call(subAggregate.close); })); return { aggregate, - result, - things: result, + result: { + network: things, + flat: things, + file: things, + wikiData, + }, }; } @@ -1336,17 +1391,21 @@ export function processThingsFromDataStep(documents, dataStep) { if (documents.length > 1) throw new Error(`Only expected one document to be present, got ${documents.length}`); - const {thing, aggregate} = + const {result, aggregate} = processDocument(documents[0], dataStep.documentThing); - thing[Thing.yamlSourceDocument] = documents[0]; - thing[Thing.yamlSourceDocumentPlacement] = + result.thing[Thing.yamlSourceDocument] = documents[0]; + result.thing[Thing.yamlSourceDocumentPlacement] = [documentModes.oneDocumentTotal]; return { aggregate, - result: thing, - things: [thing], + result: { + network: result.thing, + flat: result.flat, + file: [result.thing], + wikiData: result.wikiData, + }, }; } @@ -1358,14 +1417,17 @@ export function processThingsFromDataStep(documents, dataStep) { throw new Error(`Missing header document (empty file or erroneously starting with "---"?)`); const aggregate = openAggregate({message: `Errors processing documents`}); + const wikiData = {}; - const {thing: headerThing, aggregate: headerAggregate} = + const {result: headerResult, aggregate: headerAggregate} = processDocument(headerDocument, dataStep.headerDocumentThing); - headerThing[Thing.yamlSourceDocument] = headerDocument; - headerThing[Thing.yamlSourceDocumentPlacement] = + headerResult.thing[Thing.yamlSourceDocument] = headerDocument; + headerResult.thing[Thing.yamlSourceDocumentPlacement] = [documentModes.headerAndEntries, 'header']; + pushWikiData(wikiData, headerResult.wikiData); + try { headerAggregate.close(); } catch (caughtError) { @@ -1373,17 +1435,18 @@ export function processThingsFromDataStep(documents, dataStep) { aggregate.push(caughtError); } - const entryThings = []; + const entryResults = []; for (const [index, entryDocument] of entryDocuments.entries()) { - const {thing: entryThing, aggregate: entryAggregate} = + const {result: entryResult, aggregate: entryAggregate} = processDocument(entryDocument, dataStep.entryDocumentThing); - entryThing[Thing.yamlSourceDocument] = entryDocument; - entryThing[Thing.yamlSourceDocumentPlacement] = + entryResult.thing[Thing.yamlSourceDocument] = entryDocument; + entryResult.thing[Thing.yamlSourceDocumentPlacement] = [documentModes.headerAndEntries, 'entry', index]; - entryThings.push(entryThing); + entryResults.push(entryResult); + pushWikiData(wikiData, entryResult.wikiData); try { entryAggregate.close(); @@ -1396,10 +1459,16 @@ export function processThingsFromDataStep(documents, dataStep) { return { aggregate, result: { - header: headerThing, - entries: entryThings, + network: { + header: headerResult.thing, + entries: entryResults.map(result => result.thing), + }, + + flat: headerResult.flat.concat(entryResults.flatMap(result => result.flat)), + file: [headerResult.thing, ...entryResults.map(result => result.thing)], + + wikiData, }, - things: [headerThing, ...entryThings], }; } @@ -1410,17 +1479,21 @@ export function processThingsFromDataStep(documents, dataStep) { if (empty(documents) || !documents[0]) throw new Error(`Expected a document, this file is empty`); - const {thing, aggregate} = + const {result, aggregate} = processDocument(documents[0], dataStep.documentThing); - thing[Thing.yamlSourceDocument] = documents[0]; - thing[Thing.yamlSourceDocumentPlacement] = + result.thing[Thing.yamlSourceDocument] = documents[0]; + result.thing[Thing.yamlSourceDocumentPlacement] = [documentModes.onePerFile]; return { aggregate, - result: thing, - things: [thing], + result: { + network: result.thing, + flat: result.flat, + file: [result.thing], + wikiData: result.wikiData, + }, }; } @@ -1521,10 +1594,10 @@ export async function processThingsFromDataSteps(documentLists, fileLists, dataS file: files, documents: documentLists, }).map(({file, documents}) => { - const {result, aggregate, things} = + const {result, aggregate} = processThingsFromDataStep(documents, dataStep); - for (const thing of things) { + for (const thing of result.file) { thing[Thing.yamlSourceFilename] = path.relative(dataPath, file) .split(path.sep) @@ -1551,45 +1624,35 @@ export async function processThingsFromDataSteps(documentLists, fileLists, dataS translucent: true, }).contain(await fileListPromise)); - const thingLists = + const results = aggregate .receive(await Promise.all(dataStepPromises)); - return {aggregate, result: thingLists}; + return {aggregate, result: results}; } -// Flattens a list of *lists* of things for a given data step (each list -// corresponding to one YAML file) into results to be saved on the final -// wikiData object, routing thing lists into the step's save() function. -export function saveThingsFromDataStep(thingLists, dataStep) { +// Runs a data step's connect() function, if present, with representations +// of the results from the YAML files, called "networks" - one network and +// one call to .connect() per YAML file - in order to form data connections +// (direct links) between related objects within a file. +export function connectThingsFromDataStep(results, dataStep) { const {documentMode} = dataStep; switch (documentMode) { - case documentModes.allInOne: { - const things = - (empty(thingLists) - ? [] - : thingLists[0]); - - return dataStep.save(things); - } - - case documentModes.oneDocumentTotal: { - const thing = - (empty(thingLists) - ? {} - : thingLists[0]); - - return dataStep.save(thing); + case documentModes.oneDocumentTotal: + case documentModes.onePerFile: { + // These results are never connected. + return; } - case documentModes.allTogether: { - return dataStep.save(thingLists.flat()); - } + case documentModes.allInOne: + case documentModes.allTogether: + case documentModes.headerAndEntries: { + for (const result of results) { + dataStep.connect?.(result.network); + } - case documentModes.headerAndEntries: - case documentModes.onePerFile: { - return dataStep.save(thingLists); + break; } default: @@ -1597,60 +1660,71 @@ export function saveThingsFromDataStep(thingLists, dataStep) { } } -// Flattens a list of *lists* of things for each data step (each list -// corresponding to one YAML file) into the final wikiData object, -// routing thing lists into each step's save() function. -export function saveThingsFromDataSteps(thingLists, dataSteps) { +export function connectThingsFromDataSteps(processThingResultLists, dataSteps) { const aggregate = openAggregate({ - message: `Errors finalizing things from data files`, + message: `Errors connecting things from data files`, translucent: true, }); - const wikiData = {}; - stitchArrays({ dataStep: dataSteps, - thingLists: thingLists, - }).map(({dataStep, thingLists}) => { + processThingResults: processThingResultLists, + }).forEach(({dataStep, processThingResults}) => { try { - return saveThingsFromDataStep(thingLists, dataStep); + connectThingsFromDataStep(processThingResults, dataStep); } catch (caughtError) { const error = new Error( - `Error finalizing things for data step: ${colors.bright(dataStep.title)}`, + `Error connecting things for data step: ${colors.bright(dataStep.title)}`, {cause: caughtError}); error[Symbol.for('hsmusic.aggregate.translucent')] = true; aggregate.push(error); - - return null; } - }) - .filter(Boolean) - .forEach(saveResult => { - for (const [saveKey, saveValue] of Object.entries(saveResult)) { - if (Object.hasOwn(wikiData, saveKey)) { - if (Array.isArray(wikiData[saveKey])) { - if (Array.isArray(saveValue)) { - wikiData[saveKey].push(...saveValue); - } else { - throw new Error(`${saveKey} already present, expected array of items to push`); - } - } else { - if (Array.isArray(saveValue)) { - throw new Error(`${saveKey} already present and not an array, refusing to overwrite`); - } else { - throw new Error(`${saveKey} already present, refusing to overwrite`); - } - } - } else { - wikiData[saveKey] = saveValue; + }); + + return {result: null, aggregate}; +} + +export function makeWikiDataFromDataSteps(processThingResultLists, _dataSteps) { + const wikiData = {}; + + let found = false; + for (const result of processThingResultLists.flat(2)) { + pushWikiData(wikiData, result.wikiData); + } + + const scanForConstituted = + processThingResultLists.flat(2).flatMap(result => result.flat); + + const exists = new Set(scanForConstituted); + + while (scanForConstituted.length) { + const scanningThing = scanForConstituted.pop(); + + for (const key of scanningThing.constructor[Thing.constitutibleProperties] ?? []) { + const maybeConstitutedThings = + (Array.isArray(scanningThing[key]) + ? scanningThing[key] + : scanningThing[key] + ? [scanningThing[key]] + : []); + + for (const thing of maybeConstitutedThings) { + if (exists.has(thing)) continue; + exists.add(thing); + + if (thing.constructor[Thing.wikiData]) { + pushWikiData(wikiData, {[thing.constructor[Thing.wikiData]]: [thing]}); } + + scanForConstituted.push(thing); } - }); + } + } - return {aggregate, result: wikiData}; + return wikiData; } export async function loadAndProcessDataDocuments(dataSteps, {dataPath}) { @@ -1663,13 +1737,15 @@ export async function loadAndProcessDataDocuments(dataSteps, {dataPath}) { aggregate.receive( await loadYAMLDocumentsFromDataSteps(dataSteps, {dataPath})); - const thingLists = + const processThingResultLists = aggregate.receive( await processThingsFromDataSteps(documentLists, fileLists, dataSteps, {dataPath})); + aggregate.receive( + connectThingsFromDataSteps(processThingResultLists, dataSteps)); + const wikiData = - aggregate.receive( - saveThingsFromDataSteps(thingLists, dataSteps)); + makeWikiDataFromDataSteps(processThingResultLists, dataSteps); return {aggregate, result: wikiData}; } diff --git a/src/upd8.js b/src/upd8.js index 0bceea8d..7ffcc406 100755 --- a/src/upd8.js +++ b/src/upd8.js @@ -105,7 +105,8 @@ import { linkWikiDataArrays, loadYAMLDocumentsFromDataSteps, processThingsFromDataSteps, - saveThingsFromDataSteps, + connectThingsFromDataSteps, + makeWikiDataFromDataSteps, sortWikiDataArrays, } from '#yaml'; @@ -1499,7 +1500,8 @@ async function main() { let loadAggregate, loadResult; let processAggregate, processResult; - let saveAggregate, saveResult; + let connectAggregate; + let makeWikiDataResult; const dataSteps = getAllDataSteps(); @@ -1549,21 +1551,29 @@ async function main() { } try { - ({aggregate: saveAggregate, result: saveResult} = - saveThingsFromDataSteps( + ({aggregate: connectAggregate} = + connectThingsFromDataSteps( processResult, dataSteps)); - saveAggregate.close(); - saveAggregate = undefined; + connectAggregate.close(); } catch (error) { return whoops(error, `finalizing data files`); } + try { + makeWikiDataResult = + makeWikiDataFromDataSteps( + processResult, + dataSteps); + } catch (error) { + return whoops(error, 'preparing wikiData object'); + } + yamlDataSteps = dataSteps; yamlDocumentProcessingAggregate = processAggregate; - Object.assign(wikiData, saveResult); + Object.assign(wikiData, makeWikiDataResult); } { @@ -1798,7 +1808,7 @@ async function main() { if (!paragraph) console.log(''); niceShowAggregate(aggregate); - if (aggregate.errors.find(err => err.message.toLowerCase().includes('duplicate'))) { + if (aggregate.errors?.find(err => err.message.toLowerCase().includes('duplicate'))) { logWarn`The above duplicate directories were detected while reviewing data files.`; logWarn`Since it's impossible to automatically determine which one's directory is`; logWarn`correct, the build can't continue. Specify unique 'Directory' fields in`; |