diff options
Diffstat (limited to 'src/data/things/artist.js')
-rw-r--r-- | src/data/things/artist.js | 443 |
1 files changed, 336 insertions, 107 deletions
diff --git a/src/data/things/artist.js b/src/data/things/artist.js index 303f33f..841d652 100644 --- a/src/data/things/artist.js +++ b/src/data/things/artist.js @@ -1,121 +1,230 @@ -import Thing from './thing.js'; - -import find from '../../util/find.js'; +export const ARTIST_DATA_FILE = 'artists.yaml'; + +import {inspect} from 'node:util'; + +import CacheableObject from '#cacheable-object'; +import {colors} from '#cli'; +import {input} from '#composite'; +import find from '#find'; +import {sortAlphabetically} from '#sort'; +import {stitchArrays, unique} from '#sugar'; +import Thing from '#thing'; +import {isName, validateArrayItems} from '#validators'; +import {getKebabCase} from '#wiki-data'; + +import {withReverseContributionList} from '#composite/wiki-data'; + +import { + contentString, + directory, + fileExtension, + flag, + name, + reverseContributionList, + reverseReferenceList, + singleReference, + urls, + wikiData, +} from '#composite/wiki-properties'; export class Artist extends Thing { static [Thing.referenceType] = 'artist'; + static [Thing.wikiDataArray] = 'artistData'; - static [Thing.getPropertyDescriptors] = ({ - Album, - Flash, - Track, - - validators: { - isName, - validateArrayItems, - }, - }) => ({ + static [Thing.getPropertyDescriptors] = ({Album, Flash, Track}) => ({ // Update & expose - name: Thing.common.name('Unnamed Artist'), - directory: Thing.common.directory(), - urls: Thing.common.urls(), - contextNotes: Thing.common.simpleString(), + name: name('Unnamed Artist'), + directory: directory(), + urls: urls(), + + contextNotes: contentString(), - hasAvatar: Thing.common.flag(false), - avatarFileExtension: Thing.common.fileExtension('jpg'), + hasAvatar: flag(false), + avatarFileExtension: fileExtension('jpg'), aliasNames: { flags: {update: true, expose: true}, - update: { - validate: validateArrayItems(isName), - }, + update: {validate: validateArrayItems(isName)}, + expose: {transform: (names) => names ?? []}, }, - isAlias: Thing.common.flag(), - aliasedArtistRef: Thing.common.singleReference(Artist), + isAlias: flag(), - // Update only - - albumData: Thing.common.wikiData(Album), - artistData: Thing.common.wikiData(Artist), - flashData: Thing.common.wikiData(Flash), - trackData: Thing.common.wikiData(Track), + aliasedArtist: singleReference({ + class: input.value(Artist), + find: input.value(find.artist), + data: 'artistData', + }), - // Expose only + // Update only - aliasedArtist: { - flags: {expose: true}, + albumData: wikiData({ + class: input.value(Album), + }), - expose: { - dependencies: ['artistData', 'aliasedArtistRef'], - compute: ({artistData, aliasedArtistRef}) => - aliasedArtistRef && artistData - ? find.artist(aliasedArtistRef, artistData, {mode: 'quiet'}) - : null, - }, - }, + artistData: wikiData({ + class: input.value(Artist), + }), - tracksAsArtist: - Artist.filterByContrib('trackData', 'artistContribs'), - tracksAsContributor: - Artist.filterByContrib('trackData', 'contributorContribs'), - tracksAsCoverArtist: - Artist.filterByContrib('trackData', 'coverArtistContribs'), - - tracksAsAny: { - flags: {expose: true}, - - expose: { - dependencies: ['trackData'], - - compute: ({trackData, [Artist.instance]: artist}) => - trackData?.filter((track) => - [ - ...track.artistContribs, - ...track.contributorContribs, - ...track.coverArtistContribs, - ].some(({who}) => who === artist)) ?? [], - }, - }, + flashData: wikiData({ + class: input.value(Flash), + }), - tracksAsCommentator: { - flags: {expose: true}, + trackData: wikiData({ + class: input.value(Track), + }), - expose: { - dependencies: ['trackData'], + // Expose only - compute: ({trackData, [Artist.instance]: artist}) => - trackData?.filter(({commentatorArtists}) => - commentatorArtists.includes(artist)) ?? [], + tracksAsArtist: reverseContributionList({ + data: 'trackData', + list: input.value('artistContribs'), + }), + + tracksAsContributor: reverseContributionList({ + data: 'trackData', + list: input.value('contributorContribs'), + }), + + tracksAsCoverArtist: reverseContributionList({ + data: 'trackData', + list: input.value('coverArtistContribs'), + }), + + tracksAsAny: [ + withReverseContributionList({ + data: 'trackData', + list: input.value('artistContribs'), + }).outputs({ + '#reverseContributionList': '#tracksAsArtist', + }), + + withReverseContributionList({ + data: 'trackData', + list: input.value('contributorContribs'), + }).outputs({ + '#reverseContributionList': '#tracksAsContributor', + }), + + withReverseContributionList({ + data: 'trackData', + list: input.value('coverArtistContribs'), + }).outputs({ + '#reverseContributionList': '#tracksAsCoverArtist', + }), + + { + dependencies: [ + '#tracksAsArtist', + '#tracksAsContributor', + '#tracksAsCoverArtist', + ], + + compute: ({ + ['#tracksAsArtist']: tracksAsArtist, + ['#tracksAsContributor']: tracksAsContributor, + ['#tracksAsCoverArtist']: tracksAsCoverArtist, + }) => + unique([ + ...tracksAsArtist, + ...tracksAsContributor, + ...tracksAsCoverArtist, + ]), }, - }, - - albumsAsAlbumArtist: - Artist.filterByContrib('albumData', 'artistContribs'), - albumsAsCoverArtist: - Artist.filterByContrib('albumData', 'coverArtistContribs'), - albumsAsWallpaperArtist: - Artist.filterByContrib('albumData', 'wallpaperArtistContribs'), - albumsAsBannerArtist: - Artist.filterByContrib('albumData', 'bannerArtistContribs'), - - albumsAsCommentator: { - flags: {expose: true}, - - expose: { - dependencies: ['albumData'], - - compute: ({albumData, [Artist.instance]: artist}) => - albumData?.filter(({commentatorArtists}) => - commentatorArtists.includes(artist)) ?? [], + ], + + tracksAsCommentator: reverseReferenceList({ + data: 'trackData', + list: input.value('commentatorArtists'), + }), + + albumsAsAlbumArtist: reverseContributionList({ + data: 'albumData', + list: input.value('artistContribs'), + }), + + albumsAsCoverArtist: reverseContributionList({ + data: 'albumData', + list: input.value('coverArtistContribs'), + }), + + albumsAsWallpaperArtist: reverseContributionList({ + data: 'albumData', + list: input.value('wallpaperArtistContribs'), + }), + + albumsAsBannerArtist: reverseContributionList({ + data: 'albumData', + list: input.value('bannerArtistContribs'), + }), + + albumsAsAny: [ + withReverseContributionList({ + data: 'albumData', + list: input.value('artistContribs'), + }).outputs({ + '#reverseContributionList': '#albumsAsArtist', + }), + + withReverseContributionList({ + data: 'albumData', + list: input.value('coverArtistContribs'), + }).outputs({ + '#reverseContributionList': '#albumsAsCoverArtist', + }), + + withReverseContributionList({ + data: 'albumData', + list: input.value('wallpaperArtistContribs'), + }).outputs({ + '#reverseContributionList': '#albumsAsWallpaperArtist', + }), + + withReverseContributionList({ + data: 'albumData', + list: input.value('bannerArtistContribs'), + }).outputs({ + '#reverseContributionList': '#albumsAsBannerArtist', + }), + + { + dependencies: [ + '#albumsAsArtist', + '#albumsAsCoverArtist', + '#albumsAsWallpaperArtist', + '#albumsAsBannerArtist', + ], + + compute: ({ + ['#albumsAsArtist']: albumsAsArtist, + ['#albumsAsCoverArtist']: albumsAsCoverArtist, + ['#albumsAsWallpaperArtist']: albumsAsWallpaperArtist, + ['#albumsAsBannerArtist']: albumsAsBannerArtist, + }) => + unique([ + ...albumsAsArtist, + ...albumsAsCoverArtist, + ...albumsAsWallpaperArtist, + ...albumsAsBannerArtist, + ]), }, - }, - - flashesAsContributor: Artist.filterByContrib( - 'flashData', - 'contributorContribs' - ), + ], + + albumsAsCommentator: reverseReferenceList({ + data: 'albumData', + list: input.value('commentatorArtists'), + }), + + flashesAsContributor: reverseContributionList({ + data: 'flashData', + list: input.value('contributorContribs'), + }), + + flashesAsCommentator: reverseReferenceList({ + data: 'flashData', + list: input.value('commentatorArtists'), + }), }); static [Thing.getSerializeDescriptors] = ({ @@ -145,19 +254,139 @@ export class Artist extends Thing { flashesAsContributor: S.toRefs, }); - static filterByContrib = (thingDataProperty, contribsProperty) => ({ - flags: {expose: true}, + static [Thing.findSpecs] = { + artist: { + referenceTypes: ['artist', 'artist-gallery'], + bindTo: 'artistData', + + include: artist => !artist.isAlias, + }, + + artistAlias: { + referenceTypes: ['artist', 'artist-gallery'], + bindTo: 'artistData', + + include: artist => artist.isAlias, + + getMatchableDirectories(artist) { + 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'}, + 'Directory': {property: 'directory'}, + 'URLs': {property: 'urls'}, + 'Context Notes': {property: 'contextNotes'}, + + 'Has Avatar': {property: 'hasAvatar'}, + 'Avatar File Extension': {property: 'avatarFileExtension'}, + + 'Aliases': {property: 'aliasNames'}, + + 'Dead URLs': {ignore: true}, + + 'Review Points': {ignore: true}, + }, + }; + + static [Thing.getYamlLoadingSpec] = ({ + documentModes: {allInOne}, + thingConstructors: {Artist}, + }) => ({ + title: `Process artists file`, + file: ARTIST_DATA_FILE, + + documentMode: allInOne, + documentThing: Artist, - expose: { - dependencies: [thingDataProperty], + save(results) { + const artists = results; - compute: ({ - [thingDataProperty]: thingData, - [Artist.instance]: artist - }) => - thingData?.filter(thing => - thing[contribsProperty] - .some(contrib => contrib.who === artist)) ?? [], + const artistRefs = + artists.map(artist => Thing.getReference(artist)); + + const artistAliasNames = + artists.map(artist => artist.aliasNames); + + const artistAliases = + stitchArrays({ + originalArtistRef: artistRefs, + aliasNames: artistAliasNames, + }).flatMap(({originalArtistRef, aliasNames}) => + aliasNames.map(name => { + const alias = new Artist(); + alias.name = name; + alias.isAlias = true; + alias.aliasedArtist = originalArtistRef; + return alias; + })); + + const artistData = [...artists, ...artistAliases]; + + return {artistData}; + }, + + sort({artistData}) { + sortAlphabetically(artistData); }, }); + + [inspect.custom]() { + const parts = []; + + parts.push(Thing.prototype[inspect.custom].apply(this)); + + if (CacheableObject.getUpdateValue(this, 'isAlias')) { + parts.unshift(`${colors.yellow('[alias]')} `); + + let aliasedArtist; + try { + aliasedArtist = this.aliasedArtist.name; + } catch (_error) { + aliasedArtist = CacheableObject.getUpdateValue(this, 'aliasedArtist'); + } + + parts.push(` ${colors.yellow(`[of ${aliasedArtist}]`)}`); + } + + return parts.join(''); + } } |