diff options
Diffstat (limited to 'src/data/things')
-rw-r--r-- | src/data/things/composite.js | 32 | ||||
-rw-r--r-- | src/data/things/thing.js | 36 | ||||
-rw-r--r-- | src/data/things/track.js | 670 |
3 files changed, 379 insertions, 359 deletions
diff --git a/src/data/things/composite.js b/src/data/things/composite.js index 21cf365f..bcc52a2a 100644 --- a/src/data/things/composite.js +++ b/src/data/things/composite.js @@ -808,7 +808,7 @@ function _export(mapping) { const mappingEntries = Object.entries(mapping); return { - annotation: `Thing.composite.export`, + annotation: `export`, flags: {expose: true, compose: true}, expose: { @@ -853,7 +853,7 @@ export function exposeDependency(dependency, { update = false, } = {}) { return { - annotation: `Thing.composite.exposeDependency`, + annotation: `exposeDependency`, flags: {expose: true, update: !!update}, expose: { @@ -878,7 +878,7 @@ export function exposeConstant(value, { update = false, } = {}) { return { - annotation: `Thing.composite.exposeConstant`, + annotation: `exposeConstant`, flags: {expose: true, update: !!update}, expose: { @@ -934,7 +934,7 @@ export function withResultOfAvailabilityCheck({ if (fromDependency) { return { - annotation: `Thing.composite.withResultOfAvailabilityCheck.fromDependency`, + annotation: `withResultOfAvailabilityCheck.fromDependency`, flags: {expose: true, compose: true}, expose: { mapDependencies: {from: fromDependency}, @@ -946,7 +946,7 @@ export function withResultOfAvailabilityCheck({ }; } else { return { - annotation: `Thing.composite.withResultOfAvailabilityCheck.fromUpdateValue`, + annotation: `withResultOfAvailabilityCheck.fromUpdateValue`, flags: {expose: true, compose: true}, expose: { mapContinuation: {to}, @@ -963,7 +963,7 @@ export function withResultOfAvailabilityCheck({ export function exposeDependencyOrContinue(dependency, { mode = 'null', } = {}) { - return compositeFrom(`Thing.composite.exposeDependencyOrContinue`, [ + return compositeFrom(`exposeDependencyOrContinue`, [ withResultOfAvailabilityCheck({ fromDependency: dependency, mode, @@ -991,7 +991,7 @@ export function exposeDependencyOrContinue(dependency, { export function exposeUpdateValueOrContinue({ mode = 'null', } = {}) { - return compositeFrom(`Thing.composite.exposeUpdateValueOrContinue`, [ + return compositeFrom(`exposeUpdateValueOrContinue`, [ withResultOfAvailabilityCheck({ fromUpdateValue: true, mode, @@ -1019,7 +1019,7 @@ export function earlyExitIfAvailabilityCheckFailed({ availability = '#availability', value = null, } = {}) { - return compositeFrom(`Thing.composite.earlyExitIfAvailabilityCheckFailed`, [ + return compositeFrom(`earlyExitIfAvailabilityCheckFailed`, [ { mapDependencies: {availability}, compute: ({availability}, continuation) => @@ -1042,7 +1042,7 @@ export function earlyExitWithoutDependency(dependency, { mode = 'null', value = null, } = {}) { - return compositeFrom(`Thing.composite.earlyExitWithoutDependency`, [ + return compositeFrom(`earlyExitWithoutDependency`, [ withResultOfAvailabilityCheck({fromDependency: dependency, mode}), earlyExitIfAvailabilityCheckFailed({value}), ]); @@ -1054,7 +1054,7 @@ export function earlyExitWithoutUpdateValue({ mode = 'null', value = null, } = {}) { - return compositeFrom(`Thing.composite.earlyExitWithoutDependency`, [ + return compositeFrom(`earlyExitWithoutDependency`, [ withResultOfAvailabilityCheck({fromUpdateValue: true, mode}), earlyExitIfAvailabilityCheckFailed({value}), ]); @@ -1067,7 +1067,7 @@ export function raiseWithoutDependency(dependency, { map = {}, raise = {}, } = {}) { - return compositeFrom(`Thing.composite.raiseWithoutDependency`, [ + return compositeFrom(`raiseWithoutDependency`, [ withResultOfAvailabilityCheck({fromDependency: dependency, mode}), { @@ -1094,7 +1094,7 @@ export function raiseWithoutUpdateValue({ map = {}, raise = {}, } = {}) { - return compositeFrom(`Thing.composite.raiseWithoutUpdateValue`, [ + return compositeFrom(`raiseWithoutUpdateValue`, [ withResultOfAvailabilityCheck({fromUpdateValue: true, mode}), { @@ -1121,8 +1121,8 @@ export function raiseWithoutUpdateValue({ // means mapping the "who" reference of each contribution to an artist // object, and filtering out those whose "who" doesn't match any artist. export function withResolvedContribs({from, to}) { - return Thing.composite.from(`Thing.composite.withResolvedContribs`, [ - Thing.composite.raiseWithoutDependency(from, { + return compositeFrom(`withResolvedContribs`, [ + raiseWithoutDependency(from, { mode: 'empty', map: {to}, raise: {to: []}, @@ -1172,7 +1172,7 @@ export function withResolvedReference({ to = '#resolvedReference', earlyExitIfNotFound = false, }) { - return compositeFrom(`Thing.composite.withResolvedReference`, [ + return compositeFrom(`withResolvedReference`, [ raiseWithoutDependency(ref, {map: {to}, raise: {to: null}}), earlyExitWithoutDependency(data), @@ -1210,7 +1210,7 @@ export function withResolvedReferenceList({ throw new TypeError(`Expected notFoundMode to be filter, exit, or null`); } - return compositeFrom(`Thing.composite.withResolvedReferenceList`, [ + return compositeFrom(`withResolvedReferenceList`, [ earlyExitWithoutDependency(data, {value: []}), raiseWithoutDependency(list, { diff --git a/src/data/things/thing.js b/src/data/things/thing.js index 968dd102..0716931a 100644 --- a/src/data/things/thing.js +++ b/src/data/things/thing.js @@ -9,6 +9,15 @@ import {empty} from '#sugar'; import {getKebabCase} from '#wiki-data'; import { + from as compositeFrom, + exposeDependency, + withReverseReferenceList, + withResolvedContribs, + withResolvedReference, + withResolvedReferenceList, +} from '#composite'; + +import { isAdditionalFileList, isBoolean, isCommentary, @@ -27,7 +36,6 @@ import { } from '#validators'; import CacheableObject from './cacheable-object.js'; -import * as composite from './composite.js'; export default class Thing extends CacheableObject { static referenceType = Symbol('Thing.referenceType'); @@ -194,20 +202,20 @@ export default class Thing extends CacheableObject { // in the provided property and searches the specified wiki data for // matching actual Thing-subclass objects. resolvedReferenceList({list, data, find}) { - return Thing.composite.from(`Thing.common.resolvedReferenceList`, [ - Thing.composite.withResolvedReferenceList({ + return compositeFrom(`Thing.common.resolvedReferenceList`, [ + withResolvedReferenceList({ list, data, find, notFoundMode: 'filter', }), - Thing.composite.exposeDependency('#resolvedReferenceList'), + exposeDependency('#resolvedReferenceList'), ]); }, // Corresponding function for a single reference. resolvedReference({ref, data, find}) { - return Thing.composite.from(`Thing.common.resolvedReference`, [ - Thing.composite.withResolvedReference({ref, data, find}), - Thing.composite.exposeDependency('#resolvedReference'), + return compositeFrom(`Thing.common.resolvedReference`, [ + withResolvedReference({ref, data, find}), + exposeDependency('#resolvedReference'), ]); }, @@ -227,13 +235,13 @@ export default class Thing extends CacheableObject { // reference list is somehow messed up, or artistData isn't being provided // properly.) dynamicContribs(contribsByRefProperty) { - return Thing.composite.from(`Thing.common.dynamicContribs`, [ - Thing.composite.withResolvedContribs({ + return compositeFrom(`Thing.common.dynamicContribs`, [ + withResolvedContribs({ from: contribsByRefProperty, to: '#contribs', }), - Thing.composite.exposeDependency('#contribs'), + exposeDependency('#contribs'), ]); }, @@ -257,9 +265,9 @@ export default class Thing extends CacheableObject { // property. Naturally, the passed ref list property is of the things in the // wiki data provided, not the requesting Thing itself. reverseReferenceList({data, list}) { - return Thing.composite.from(`Thing.common.reverseReferenceList`, [ - Thing.composite.withReverseReferenceList({data, list}), - Thing.composite.exposeDependency('#reverseReferenceList'), + return compositeFrom(`Thing.common.reverseReferenceList`, [ + withReverseReferenceList({data, list}), + exposeDependency('#reverseReferenceList'), ]); }, @@ -323,6 +331,4 @@ export default class Thing extends CacheableObject { return `${thing.constructor[Thing.referenceType]}:${thing.directory}`; } - - static composite = composite; } diff --git a/src/data/things/track.js b/src/data/things/track.js index 7d7e8a68..c5e6ff34 100644 --- a/src/data/things/track.js +++ b/src/data/things/track.js @@ -4,6 +4,19 @@ import {color} from '#cli'; import find from '#find'; import {empty} from '#sugar'; +import { + from as compositeFrom, + earlyExitWithoutDependency, + exposeConstant, + exposeDependency, + exposeDependencyOrContinue, + exposeUpdateValueOrContinue, + withResolvedContribs, + withResolvedReference, + withResultOfAvailabilityCheck, + withReverseReferenceList, +} from '#composite'; + import Thing from './thing.js'; export class Track extends Thing { @@ -43,9 +56,9 @@ export class Track extends Thing { sampledTracksByRef: Thing.common.referenceList(Track), artTagsByRef: Thing.common.referenceList(ArtTag), - color: Thing.composite.from(`Track.color`, [ - Thing.composite.exposeUpdateValueOrContinue(), - Track.composite.withContainingTrackSection({earlyExitIfNotFound: false}), + color: compositeFrom(`Track.color`, [ + exposeUpdateValueOrContinue(), + withContainingTrackSection({earlyExitIfNotFound: false}), { dependencies: ['#trackSection'], @@ -59,8 +72,8 @@ export class Track extends Thing { : continuation()), }, - Track.composite.withAlbumProperty('color'), - Thing.composite.exposeDependency('#album.color', { + withAlbumProperty('color'), + exposeDependency('#album.color', { update: {validate: isColor}, }), ]), @@ -75,21 +88,21 @@ export class Track extends Thing { // track's unique cover artwork, if any, and does not inherit the extension // of the album's main artwork. It does inherit trackCoverArtFileExtension, // if present on the album. - coverArtFileExtension: Thing.composite.from(`Track.coverArtFileExtension`, [ + coverArtFileExtension: compositeFrom(`Track.coverArtFileExtension`, [ // No cover art file extension if the track doesn't have unique artwork // in the first place. - Track.composite.withHasUniqueCoverArt(), - Thing.composite.earlyExitWithoutDependency('#hasUniqueCoverArt', {mode: 'falsy'}), + withHasUniqueCoverArt(), + earlyExitWithoutDependency('#hasUniqueCoverArt', {mode: 'falsy'}), // Expose custom coverArtFileExtension update value first. - Thing.composite.exposeUpdateValueOrContinue(), + exposeUpdateValueOrContinue(), // Expose album's trackCoverArtFileExtension if no update value set. - Track.composite.withAlbumProperty('trackCoverArtFileExtension'), - Thing.composite.exposeDependencyOrContinue('#album.trackCoverArtFileExtension'), + withAlbumProperty('trackCoverArtFileExtension'), + exposeDependencyOrContinue('#album.trackCoverArtFileExtension'), // Fallback to 'jpg'. - Thing.composite.exposeConstant('jpg', { + exposeConstant('jpg', { update: {validate: isFileExtension}, }), ]), @@ -98,14 +111,14 @@ export class Track extends Thing { // only the track's own unique cover artwork, if any. This exposes only as // the track's own coverArtDate or its album's trackArtDate, so if neither // is specified, this value is null. - coverArtDate: Thing.composite.from(`Track.coverArtDate`, [ - Track.composite.withHasUniqueCoverArt(), - Thing.composite.earlyExitWithoutDependency('#hasUniqueCoverArt', {mode: 'falsy'}), + coverArtDate: compositeFrom(`Track.coverArtDate`, [ + withHasUniqueCoverArt(), + earlyExitWithoutDependency('#hasUniqueCoverArt', {mode: 'falsy'}), - Thing.composite.exposeUpdateValueOrContinue(), + exposeUpdateValueOrContinue(), - Track.composite.withAlbumProperty('trackArtDate'), - Thing.composite.exposeDependency('#album.trackArtDate', { + withAlbumProperty('trackArtDate'), + exposeDependency('#album.trackArtDate', { update: {validate: isDate}, }), ]), @@ -132,9 +145,9 @@ export class Track extends Thing { commentatorArtists: Thing.common.commentatorArtists(), - album: Thing.composite.from(`Track.album`, [ - Track.composite.withAlbum(), - Thing.composite.exposeDependency('#album'), + album: compositeFrom(`Track.album`, [ + withAlbum(), + exposeDependency('#album'), ]), // Note - this is an internal property used only to help identify a track. @@ -150,10 +163,10 @@ export class Track extends Thing { find: find.album, }), - date: Thing.composite.from(`Track.date`, [ - Thing.composite.exposeDependencyOrContinue('dateFirstReleased'), - Track.composite.withAlbumProperty('date'), - Thing.composite.exposeDependency('#album.date'), + date: compositeFrom(`Track.date`, [ + exposeDependencyOrContinue('dateFirstReleased'), + withAlbumProperty('date'), + exposeDependency('#album.date'), ]), // Whether or not the track has "unique" cover artwork - a cover which is @@ -163,19 +176,19 @@ export class Track extends Thing { // or a placeholder. (This property is named hasUniqueCoverArt instead of // the usual hasCoverArt to emphasize that it does not inherit from the // album.) - hasUniqueCoverArt: Thing.composite.from(`Track.hasUniqueCoverArt`, [ - Track.composite.withHasUniqueCoverArt(), - Thing.composite.exposeDependency('#hasUniqueCoverArt'), + hasUniqueCoverArt: compositeFrom(`Track.hasUniqueCoverArt`, [ + withHasUniqueCoverArt(), + exposeDependency('#hasUniqueCoverArt'), ]), - originalReleaseTrack: Thing.composite.from(`Track.originalReleaseTrack`, [ - Track.composite.withOriginalRelease(), - Thing.composite.exposeDependency('#originalRelease'), + originalReleaseTrack: compositeFrom(`Track.originalReleaseTrack`, [ + withOriginalRelease(), + exposeDependency('#originalRelease'), ]), - otherReleases: Thing.composite.from(`Track.otherReleases`, [ - Thing.composite.earlyExitWithoutDependency('trackData', {mode: 'empty'}), - Track.composite.withOriginalRelease({selfIfOriginal: true}), + otherReleases: compositeFrom(`Track.otherReleases`, [ + earlyExitWithoutDependency('trackData', {mode: 'empty'}), + withOriginalRelease({selfIfOriginal: true}), { flags: {expose: true}, @@ -197,10 +210,10 @@ export class Track extends Thing { }, ]), - artistContribs: Thing.composite.from(`Track.artistContribs`, [ - Track.composite.inheritFromOriginalRelease({property: 'artistContribs'}), + artistContribs: compositeFrom(`Track.artistContribs`, [ + inheritFromOriginalRelease({property: 'artistContribs'}), - Thing.composite.withResolvedContribs({ + withResolvedContribs({ from: 'artistContribsByRef', to: '#artistContribs', }), @@ -213,19 +226,19 @@ export class Track extends Thing { : contribsFromTrack), }, - Track.composite.withAlbumProperty('artistContribs'), - Thing.composite.exposeDependency('#album.artistContribs'), + withAlbumProperty('artistContribs'), + exposeDependency('#album.artistContribs'), ]), - contributorContribs: Thing.composite.from(`Track.contributorContribs`, [ - Track.composite.inheritFromOriginalRelease({property: 'contributorContribs'}), + contributorContribs: compositeFrom(`Track.contributorContribs`, [ + inheritFromOriginalRelease({property: 'contributorContribs'}), Thing.common.dynamicContribs('contributorContribsByRef'), ]), // Cover artists aren't inherited from the original release, since it // typically varies by release and isn't defined by the musical qualities // of the track. - coverArtistContribs: Thing.composite.from(`Track.coverArtistContribs`, [ + coverArtistContribs: compositeFrom(`Track.coverArtistContribs`, [ { dependencies: ['disableUniqueCoverArt'], compute: ({disableUniqueCoverArt}, continuation) => @@ -234,7 +247,7 @@ export class Track extends Thing { : continuation()), }, - Thing.composite.withResolvedContribs({ + withResolvedContribs({ from: 'coverArtistContribsByRef', to: '#coverArtistContribs', }), @@ -247,12 +260,12 @@ export class Track extends Thing { : contribsFromTrack), }, - Track.composite.withAlbumProperty('trackCoverArtistContribs'), - Thing.composite.exposeDependency('#album.trackCoverArtistContribs'), + withAlbumProperty('trackCoverArtistContribs'), + exposeDependency('#album.trackCoverArtistContribs'), ]), - referencedTracks: Thing.composite.from(`Track.referencedTracks`, [ - Track.composite.inheritFromOriginalRelease({property: 'referencedTracks'}), + referencedTracks: compositeFrom(`Track.referencedTracks`, [ + inheritFromOriginalRelease({property: 'referencedTracks'}), Thing.common.resolvedReferenceList({ list: 'referencedTracksByRef', data: 'trackData', @@ -260,8 +273,8 @@ export class Track extends Thing { }), ]), - sampledTracks: Thing.composite.from(`Track.sampledTracks`, [ - Track.composite.inheritFromOriginalRelease({property: 'sampledTracks'}), + sampledTracks: compositeFrom(`Track.sampledTracks`, [ + inheritFromOriginalRelease({property: 'sampledTracks'}), Thing.common.resolvedReferenceList({ list: 'sampledTracksByRef', data: 'trackData', @@ -283,10 +296,10 @@ export class Track extends Thing { // counting the number of times a track has been referenced, for use in // the "Tracks - by Times Referenced" listing page (or other data // processing). - referencedByTracks: Track.composite.trackReverseReferenceList('referencedTracks'), + referencedByTracks: trackReverseReferenceList('referencedTracks'), // For the same reasoning, exclude re-releases from sampled tracks too. - sampledByTracks: Track.composite.trackReverseReferenceList('sampledTracks'), + sampledByTracks: trackReverseReferenceList('sampledTracks'), featuredInFlashes: Thing.common.reverseReferenceList({ data: 'flashData', @@ -294,309 +307,310 @@ export class Track extends Thing { }), }); - static composite = { - // Early exits with a value inherited from the original release, if - // this track is a rerelease, and otherwise continues with no further - // dependencies provided. If allowOverride is true, then the continuation - // will also be called if the original release exposed the requested - // property as null. - inheritFromOriginalRelease({ - property: originalProperty, - allowOverride = false, - }) { - return Thing.composite.from(`Track.composite.inheritFromOriginalRelease`, [ - Track.composite.withOriginalRelease(), - - { - dependencies: ['#originalRelease'], - compute({'#originalRelease': originalRelease}, continuation) { - if (!originalRelease) return continuation.raise(); - - const value = originalRelease[originalProperty]; - if (allowOverride && value === null) return continuation.raise(); - - return continuation.exit(value); - }, - }, - ]); - }, + [inspect.custom](depth) { + const parts = []; - // Gets the track's album. Unless earlyExitIfNotFound is overridden false, - // this will early exit with null in two cases - albumData being missing, - // or not including an album whose .tracks array includes this track. - withAlbum({to = '#album', earlyExitIfNotFound = true} = {}) { - return Thing.composite.from(`Track.composite.withAlbum`, [ - Thing.composite.withResultOfAvailabilityCheck({ - fromDependency: 'albumData', - mode: 'empty', - to: '#albumDataAvailability', - }), + parts.push(Thing.prototype[inspect.custom].apply(this)); - { - dependencies: ['#albumDataAvailability'], - options: {earlyExitIfNotFound}, - mapContinuation: {to}, + if (this.originalReleaseTrackByRef) { + parts.unshift(`${color.yellow('[rerelease]')} `); + } - compute: ({ - '#albumDataAvailability': albumDataAvailability, - '#options': {earlyExitIfNotFound}, - }, continuation) => - (albumDataAvailability - ? continuation() - : (earlyExitIfNotFound - ? continuation.exit(null) - : continuation.raise({to: null}))), - }, + let album; + if (depth >= 0 && (album = this.album ?? this.dataSourceAlbum)) { + const albumName = album.name; + const albumIndex = album.tracks.indexOf(this); + const trackNum = + (albumIndex === -1 + ? '#?' + : `#${albumIndex + 1}`); + parts.push(` (${color.yellow(trackNum)} in ${color.green(albumName)})`); + } - { - dependencies: ['this', 'albumData'], - compute: ({this: track, albumData}, continuation) => - continuation({ - '#album': - albumData.find(album => album.tracks.includes(track)), - }), - }, + return parts.join(''); + } +} - { - dependencies: ['#album'], - options: {earlyExitIfNotFound}, - mapContinuation: {to}, - compute: ({ - '#album': album, - '#options': {earlyExitIfNotFound}, - }, continuation) => - (album - ? continuation.raise({to: album}) - : (earlyExitIfNotFound - ? continuation.exit(null) - : continuation.raise({to: album}))), - }, - ]); +// Early exits with a value inherited from the original release, if +// this track is a rerelease, and otherwise continues with no further +// dependencies provided. If allowOverride is true, then the continuation +// will also be called if the original release exposed the requested +// property as null. +function inheritFromOriginalRelease({ + property: originalProperty, + allowOverride = false, +}) { + return compositeFrom(`inheritFromOriginalRelease`, [ + withOriginalRelease(), + + { + dependencies: ['#originalRelease'], + compute({'#originalRelease': originalRelease}, continuation) { + if (!originalRelease) return continuation.raise(); + + const value = originalRelease[originalProperty]; + if (allowOverride && value === null) return continuation.raise(); + + return continuation.exit(value); + }, }, + ]); +} - // Gets a single property from this track's album, providing it as the same - // property name prefixed with '#album.' (by default). If the track's album - // isn't available, and earlyExitIfNotFound hasn't been set, the property - // will be provided as null. - withAlbumProperty(property, { - to = '#album.' + property, - earlyExitIfNotFound = false, - } = {}) { - return Thing.composite.from(`Track.composite.withAlbumProperty`, [ - Track.composite.withAlbum({earlyExitIfNotFound}), - - { - dependencies: ['#album'], - options: {property}, - mapContinuation: {to}, +// Gets the track's album. Unless earlyExitIfNotFound is overridden false, +// this will early exit with null in two cases - albumData being missing, +// or not including an album whose .tracks array includes this track. +function withAlbum({ + to = '#album', + earlyExitIfNotFound = true, +} = {}) { + return compositeFrom(`withAlbum`, [ + withResultOfAvailabilityCheck({ + fromDependency: 'albumData', + mode: 'empty', + to: '#albumDataAvailability', + }), - compute: ({ - '#album': album, - '#options': {property}, - }, continuation) => - (album - ? continuation.raise({to: album[property]}) - : continuation.raise({to: null})), - }, - ]); + { + dependencies: ['#albumDataAvailability'], + options: {earlyExitIfNotFound}, + mapContinuation: {to}, + + compute: ({ + '#albumDataAvailability': albumDataAvailability, + '#options': {earlyExitIfNotFound}, + }, continuation) => + (albumDataAvailability + ? continuation() + : (earlyExitIfNotFound + ? continuation.exit(null) + : continuation.raise({to: null}))), }, - // Gets the listed properties from this track's album, providing them as - // dependencies (by default) with '#album.' prefixed before each property - // name. If the track's album isn't available, and earlyExitIfNotFound - // hasn't been set, the same dependency names will be provided as null. - withAlbumProperties({ - properties, - prefix = '#album', - earlyExitIfNotFound = false, - }) { - return Thing.composite.from(`Track.composite.withAlbumProperties`, [ - Track.composite.withAlbum({earlyExitIfNotFound}), - - { - dependencies: ['#album'], - options: {properties, prefix}, - - compute({ - '#album': album, - '#options': {properties, prefix}, - }, continuation) { - const raise = {}; - - if (album) { - for (const property of properties) { - raise[prefix + '.' + property] = album[property]; - } - } else { - for (const property of properties) { - raise[prefix + '.' + property] = null; - } - } - - return continuation.raise(raise); - }, - }, - ]); + { + dependencies: ['this', 'albumData'], + compute: ({this: track, albumData}, continuation) => + continuation({ + '#album': + albumData.find(album => album.tracks.includes(track)), + }), }, - // Gets the track section containing this track from its album's track list. - // Unless earlyExitIfNotFound is overridden false, this will early exit if - // the album can't be found or if none of its trackSections includes the - // track for some reason. - withContainingTrackSection({ - to = '#trackSection', - earlyExitIfNotFound = true, - } = {}) { - return Thing.composite.from(`Track.composite.withContainingTrackSection`, [ - Track.composite.withAlbumProperty('trackSections', {earlyExitIfNotFound}), - - { - dependencies: ['this', '#album.trackSections'], - mapContinuation: {to}, - - compute({ - this: track, - '#album.trackSections': trackSections, - }, continuation) { - if (!trackSections) { - return continuation.raise({to: null}); - } - - const trackSection = - trackSections.find(({tracks}) => tracks.includes(track)); - - if (trackSection) { - return continuation.raise({to: trackSection}); - } else if (earlyExitIfNotFound) { - return continuation.exit(null); - } else { - return continuation.raise({to: null}); - } - }, - }, - ]); + { + dependencies: ['#album'], + options: {earlyExitIfNotFound}, + mapContinuation: {to}, + compute: ({ + '#album': album, + '#options': {earlyExitIfNotFound}, + }, continuation) => + (album + ? continuation.raise({to: album}) + : (earlyExitIfNotFound + ? continuation.exit(null) + : continuation.raise({to: album}))), }, + ]); +} - // Just includes the original release of this track as a dependency. - // If this track isn't a rerelease, then it'll provide null, unless the - // {selfIfOriginal} option is set, in which case it'll provide this track - // itself. Note that this will early exit if the original release is - // specified by reference and that reference doesn't resolve to anything. - // Outputs to '#originalRelease' by default. - withOriginalRelease({ - to = '#originalRelease', - selfIfOriginal = false, - } = {}) { - return Thing.composite.from(`Track.composite.withOriginalRelease`, [ - Thing.composite.withResolvedReference({ - ref: 'originalReleaseTrackByRef', - data: 'trackData', - to: '#originalRelease', - find: find.track, - earlyExitIfNotFound: true, - }), - - { - dependencies: ['this', '#originalRelease'], - options: {selfIfOriginal}, - mapContinuation: {to}, - compute: ({ - this: track, - '#originalRelease': originalRelease, - '#options': {selfIfOriginal}, - }, continuation) => - continuation.raise({ - to: - (originalRelease ?? - (selfIfOriginal - ? track - : null)), - }), - }, - ]); +// Gets a single property from this track's album, providing it as the same +// property name prefixed with '#album.' (by default). If the track's album +// isn't available, and earlyExitIfNotFound hasn't been set, the property +// will be provided as null. +function withAlbumProperty(property, { + to = '#album.' + property, + earlyExitIfNotFound = false, +} = {}) { + return compositeFrom(`withAlbumProperty`, [ + withAlbum({earlyExitIfNotFound}), + + { + dependencies: ['#album'], + options: {property}, + mapContinuation: {to}, + + compute: ({ + '#album': album, + '#options': {property}, + }, continuation) => + (album + ? continuation.raise({to: album[property]}) + : continuation.raise({to: null})), }, + ]); +} - // The algorithm for checking if a track has unique cover art is used in a - // couple places, so it's defined in full as a compositional step. - withHasUniqueCoverArt({ - to = '#hasUniqueCoverArt', - } = {}) { - return Thing.composite.from(`Track.composite.withHasUniqueCoverArt`, [ - { - dependencies: ['disableUniqueCoverArt'], - mapContinuation: {to}, - compute: ({disableUniqueCoverArt}, continuation) => - (disableUniqueCoverArt - ? continuation.raise({to: false}) - : continuation()), - }, +// Gets the listed properties from this track's album, providing them as +// dependencies (by default) with '#album.' prefixed before each property +// name. If the track's album isn't available, and earlyExitIfNotFound +// hasn't been set, the same dependency names will be provided as null. +function withAlbumProperties({ + properties, + prefix = '#album', + earlyExitIfNotFound = false, +}) { + return compositeFrom(`withAlbumProperties`, [ + withAlbum({earlyExitIfNotFound}), + + { + dependencies: ['#album'], + options: {properties, prefix}, + + compute({ + '#album': album, + '#options': {properties, prefix}, + }, continuation) { + const raise = {}; + + if (album) { + for (const property of properties) { + raise[prefix + '.' + property] = album[property]; + } + } else { + for (const property of properties) { + raise[prefix + '.' + property] = null; + } + } + + return continuation.raise(raise); + }, + }, + ]); +} - Thing.composite.withResolvedContribs({ - from: 'coverArtistContribsByRef', - to: '#coverArtistContribs', - }), +// Gets the track section containing this track from its album's track list. +// Unless earlyExitIfNotFound is overridden false, this will early exit if +// the album can't be found or if none of its trackSections includes the +// track for some reason. +function withContainingTrackSection({ + to = '#trackSection', + earlyExitIfNotFound = true, +} = {}) { + return compositeFrom(`withContainingTrackSection`, [ + withAlbumProperty('trackSections', {earlyExitIfNotFound}), + + { + dependencies: ['this', '#album.trackSections'], + mapContinuation: {to}, + + compute({ + this: track, + '#album.trackSections': trackSections, + }, continuation) { + if (!trackSections) { + return continuation.raise({to: null}); + } + + const trackSection = + trackSections.find(({tracks}) => tracks.includes(track)); + + if (trackSection) { + return continuation.raise({to: trackSection}); + } else if (earlyExitIfNotFound) { + return continuation.exit(null); + } else { + return continuation.raise({to: null}); + } + }, + }, + ]); +} - { - dependencies: ['#coverArtistContribs'], - mapContinuation: {to}, - compute: ({'#coverArtistContribs': contribsFromTrack}, continuation) => - (empty(contribsFromTrack) - ? continuation() - : continuation.raise({to: true})), - }, +// Just includes the original release of this track as a dependency. +// If this track isn't a rerelease, then it'll provide null, unless the +// {selfIfOriginal} option is set, in which case it'll provide this track +// itself. Note that this will early exit if the original release is +// specified by reference and that reference doesn't resolve to anything. +// Outputs to '#originalRelease' by default. +function withOriginalRelease({ + to = '#originalRelease', + selfIfOriginal = false, +} = {}) { + return compositeFrom(`withOriginalRelease`, [ + withResolvedReference({ + ref: 'originalReleaseTrackByRef', + data: 'trackData', + to: '#originalRelease', + find: find.track, + earlyExitIfNotFound: true, + }), - Track.composite.withAlbumProperty('trackCoverArtistContribs'), + { + dependencies: ['this', '#originalRelease'], + options: {selfIfOriginal}, + mapContinuation: {to}, + compute: ({ + this: track, + '#originalRelease': originalRelease, + '#options': {selfIfOriginal}, + }, continuation) => + continuation.raise({ + to: + (originalRelease ?? + (selfIfOriginal + ? track + : null)), + }), + }, + ]); +} - { - dependencies: ['#album.trackCoverArtistContribs'], - mapContinuation: {to}, - compute: ({'#album.trackCoverArtistContribs': contribsFromAlbum}, continuation) => - (empty(contribsFromAlbum) - ? continuation.raise({to: false}) - : continuation.raise({to: true})), - }, - ]); +// The algorithm for checking if a track has unique cover art is used in a +// couple places, so it's defined in full as a compositional step. +function withHasUniqueCoverArt({ + to = '#hasUniqueCoverArt', +} = {}) { + return compositeFrom(`withHasUniqueCoverArt`, [ + { + dependencies: ['disableUniqueCoverArt'], + mapContinuation: {to}, + compute: ({disableUniqueCoverArt}, continuation) => + (disableUniqueCoverArt + ? continuation.raise({to: false}) + : continuation()), }, - trackReverseReferenceList(refListProperty) { - return Thing.composite.from(`Track.composite.trackReverseReferenceList`, [ - Thing.composite.withReverseReferenceList({ - data: 'trackData', - list: refListProperty, - }), + withResolvedContribs({ + from: 'coverArtistContribsByRef', + to: '#coverArtistContribs', + }), - { - flags: {expose: true}, - expose: { - dependencies: ['#reverseReferenceList'], - compute: ({'#reverseReferenceList': reverseReferenceList}) => - reverseReferenceList.filter(track => !track.originalReleaseTrack), - }, - }, - ]); + { + dependencies: ['#coverArtistContribs'], + mapContinuation: {to}, + compute: ({'#coverArtistContribs': contribsFromTrack}, continuation) => + (empty(contribsFromTrack) + ? continuation() + : continuation.raise({to: true})), }, - }; - - [inspect.custom](depth) { - const parts = []; - parts.push(Thing.prototype[inspect.custom].apply(this)); + withAlbumProperty('trackCoverArtistContribs'), - if (this.originalReleaseTrackByRef) { - parts.unshift(`${color.yellow('[rerelease]')} `); - } + { + dependencies: ['#album.trackCoverArtistContribs'], + mapContinuation: {to}, + compute: ({'#album.trackCoverArtistContribs': contribsFromAlbum}, continuation) => + (empty(contribsFromAlbum) + ? continuation.raise({to: false}) + : continuation.raise({to: true})), + }, + ]); +} - let album; - if (depth >= 0 && (album = this.album ?? this.dataSourceAlbum)) { - const albumName = album.name; - const albumIndex = album.tracks.indexOf(this); - const trackNum = - (albumIndex === -1 - ? '#?' - : `#${albumIndex + 1}`); - parts.push(` (${color.yellow(trackNum)} in ${color.green(albumName)})`); - } +function trackReverseReferenceList(refListProperty) { + return compositeFrom(`trackReverseReferenceList`, [ + withReverseReferenceList({ + data: 'trackData', + list: refListProperty, + }), - return parts.join(''); - } + { + flags: {expose: true}, + expose: { + dependencies: ['#reverseReferenceList'], + compute: ({'#reverseReferenceList': reverseReferenceList}) => + reverseReferenceList.filter(track => !track.originalReleaseTrack), + }, + }, + ]); } |