diff options
-rw-r--r-- | src/data/things/album.js | 1 | ||||
-rw-r--r-- | src/data/things/composite.js | 14 | ||||
-rw-r--r-- | src/data/things/homepage-layout.js | 1 | ||||
-rw-r--r-- | src/data/things/thing.js | 72 | ||||
-rw-r--r-- | src/data/things/track.js | 299 |
5 files changed, 244 insertions, 143 deletions
diff --git a/src/data/things/album.js b/src/data/things/album.js index 805d177d..c0042ae2 100644 --- a/src/data/things/album.js +++ b/src/data/things/album.js @@ -8,6 +8,7 @@ import { exitWithoutUpdateValue, exposeDependency, exposeUpdateValueOrContinue, + input, fillMissingListItems, withFlattenedArray, withPropertiesFromList, diff --git a/src/data/things/composite.js b/src/data/things/composite.js index cd713169..f2ca2c7c 100644 --- a/src/data/things/composite.js +++ b/src/data/things/composite.js @@ -420,7 +420,7 @@ export function templateCompositeFrom(description) { const missingCallsToInput = []; const wrongCallsToInput = []; - for (const [name, value] of Object.entries(description.inputs)) { + for (const [name, value] of Object.entries(description.inputs ?? {})) { if (!isInputToken(value)) { missingCallsToInput.push(name); continue; @@ -533,7 +533,7 @@ export function templateCompositeFrom(description) { ? Object.keys(description.outputs) : []); - return (inputOptions = {}) => { + const instantiate = (inputOptions = {}) => { const inputOptionsAggregate = openAggregate({message: `Errors in input options passed to ${compositeName}`}); const providedInputNames = Object.keys(inputOptions); @@ -593,7 +593,7 @@ export function templateCompositeFrom(description) { const misplacedOutputNames = []; const wrongTypeOutputNames = []; - const notPrivateOutputNames = []; + // const notPrivateOutputNames = []; for (const [name, value] of Object.entries(providedOptions)) { if (!expectedOutputNames.includes(name)) { @@ -606,10 +606,12 @@ export function templateCompositeFrom(description) { continue; } + /* if (!value.startsWith('#')) { notPrivateOutputNames.push(name); continue; } + */ } if (!empty(misplacedOutputNames)) { @@ -621,10 +623,12 @@ export function templateCompositeFrom(description) { outputOptionsAggregate.push(new Error(`${name}: Expected string, got ${type}`)); } + /* for (const name of notPrivateOutputNames) { const into = providedOptions[name]; outputOptionsAggregate.push(new Error(`${name}: Expected "#" at start, got ${into}`)); } + */ outputOptionsAggregate.close(); @@ -715,6 +719,10 @@ export function templateCompositeFrom(description) { return instantiatedTemplate; }; + + instantiate.inputs = instantiate; + + return instantiate; } templateCompositeFrom.symbol = Symbol(); diff --git a/src/data/things/homepage-layout.js b/src/data/things/homepage-layout.js index 1d86f4d0..007e0236 100644 --- a/src/data/things/homepage-layout.js +++ b/src/data/things/homepage-layout.js @@ -3,6 +3,7 @@ import find from '#find'; import { compositeFrom, exposeDependency, + input, } from '#composite'; import { diff --git a/src/data/things/thing.js b/src/data/things/thing.js index d1a8fdc1..45e91238 100644 --- a/src/data/things/thing.js +++ b/src/data/things/thing.js @@ -35,6 +35,7 @@ import { isFileExtension, isName, isString, + isType, isURL, validateArrayItems, validateInstanceOf, @@ -211,8 +212,7 @@ export function contributionList() { update: {validate: isContributionList}, steps: () => [ - withUpdateValueAsDependency(), - withResolvedContribs({from: '#updateValue'}), + withResolvedContribs({from: input.updateValue()}), exposeDependencyOrContinue({dependency: '#resolvedContribs'}), exposeConstant({value: []}), ], @@ -261,27 +261,61 @@ export function additionalFiles() { // 'artist' or 'track', but this utility keeps from having to hard-code the // string in multiple places by referencing the value saved on the class // instead. +export const referenceList = templateCompositeFrom({ + annotation: `referenceList`, + + compose: false, + + inputs: { + class: input({ + validate(thingClass) { + isType(thingClass, 'function'); + + if (!Object.hasOwn(thingClass, Thing.referenceType)) { + throw new TypeError(`Expected a Thing constructor, missing Thing.referenceType`); + } + + return true; + }, + }), + + find: input({type: 'function'}), + + // todo: validate + data: input(), + }, + + update: { + dependencies: [ + input.staticValue('class'), + ], + + compute({ + [input.staticValue('class')]: thingClass, + }) { + const {[Thing.referenceType]: referenceType} = thingClass; + return {validate: validateReferenceList(referenceType)}; + }, + }, + + steps: () => [ + withResolvedReferenceList({ + list: '#updateValue', + data: '#composition.data', + find: '#composition.findFunction', + }), + + exposeDependency({dependency: '#resolvedReferenceList'}), + ], +}) export function referenceList({ class: thingClass, data, find: findFunction, }) { - if (!thingClass) { - throw new TypeError(`Expected a Thing class`); - } - - const {[Thing.referenceType]: referenceType} = thingClass; - if (!referenceType) { - throw new Error(`The passed constructor ${thingClass.name} doesn't define Thing.referenceType!`); - } - return compositeFrom({ annotation: `referenceList`, - update: { - validate: validateReferenceList(referenceType), - }, - mapDependencies: { '#composition.data': data, }, @@ -292,14 +326,6 @@ export function referenceList({ steps: () => [ withUpdateValueAsDependency(), - - withResolvedReferenceList({ - list: '#updateValue', - data: '#composition.data', - find: '#composition.findFunction', - }), - - exposeDependency({dependency: '#resolvedReferenceList'}), ], }); } diff --git a/src/data/things/track.js b/src/data/things/track.js index ccfbc357..870b9913 100644 --- a/src/data/things/track.js +++ b/src/data/things/track.js @@ -5,7 +5,6 @@ import find from '#find'; import {empty} from '#sugar'; import { - compositeFrom, exitWithoutDependency, exposeConstant, exposeDependency, @@ -15,7 +14,6 @@ import { raiseOutputWithoutDependency, templateCompositeFrom, withPropertyFromObject, - withResultOfAvailabilityCheck, } from '#composite'; import { @@ -142,9 +140,9 @@ export class Track extends Thing { artistContribs: [ inheritFromOriginalRelease({property: 'artistContribs'}), - withResolvedContribs({ - from: input.updateValue(), - }).outputs({into: '#artistContribs'}), + withResolvedContribs + .inputs({from: input.updateValue()}) + .outputs({into: '#artistContribs'}), exposeDependencyOrContinue({dependency: '#artistContribs'}), @@ -166,9 +164,9 @@ export class Track extends Thing { coverArtistContribs: [ exitWithoutUniqueCoverArt(), - withResolvedContribs({ - from: input.updateValue(), - }).outputs({into: '#coverArtistContribs'}), + withResolvedContribs + .inputs({from: input.updateValue()}) + .outputs({into: '#coverArtistContribs'}), exposeDependencyOrContinue({dependency: '#coverArtistContribs'}), @@ -271,12 +269,12 @@ export class Track extends Thing { // the "Tracks - by Times Referenced" listing page (or other data // processing). referencedByTracks: trackReverseReferenceList({ - property: 'referencedTracks', + list: 'referencedTracks', }), // For the same reasoning, exclude re-releases from sampled tracks too. sampledByTracks: trackReverseReferenceList({ - property: 'sampledTracks', + list: 'sampledTracks', }), featuredInFlashes: reverseReferenceList({ @@ -309,33 +307,44 @@ export class Track extends Thing { } } -/* // 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`, [ +export const inheritFromOriginalRelease = templateCompositeFrom({ + annotation: `Track.inheritFromOriginalRelease`, + + inputs: { + property: input({type: 'string'}), + allowOverride: input({type: 'boolean', defaultValue: false}), + }, + + steps: () => [ withOriginalRelease(), { - dependencies: ['#originalRelease'], - compute({'#originalRelease': originalRelease}, continuation) { - if (!originalRelease) return continuation.raise(); + dependencies: [ + '#originalRelease', + input('property'), + input('allowOverride'), + ], + + compute: (continuation, { + ['#originalRelease']: originalRelease, + [input('property')]: originalProperty, + [input('allowOverride')]: allowOverride, + }) => { + if (!originalRelease) return continuation(); const value = originalRelease[originalProperty]; - if (allowOverride && value === null) return continuation.raise(); + if (allowOverride && value === null) return continuation(); return continuation.exit(value); }, }, - ]); -} -*/ + ], +}); // Gets the track's album. This will early exit if albumData is missing. // By default, if there's no album whose list of tracks includes this track, @@ -383,64 +392,95 @@ export const withAlbum = templateCompositeFrom({ ], }); -/* // 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, then by default, the property will be provided as null; // set {notFoundMode: 'exit'} to early exit instead. -function withPropertyFromAlbum({ - property, - into = '#album.' + property, - notFoundMode = 'null', -}) { - return compositeFrom(`withPropertyFromAlbum`, [ - withAlbum({notFoundMode}), - withPropertyFromObject({object: '#album', property, into}), - ]); -} +export const withPropertyFromAlbum = templateCompositeFrom({ + annotation: `withPropertyFromAlbum`, + + inputs: { + property: input({type: 'string'}), + + notFoundMode: input({ + validate: oneOf('exit', 'null'), + defaultValue: 'null', + }), + }, + + outputs: { + into: { + dependencies: [input.staticValue('property')], + default: ({ + [input.staticValue('property')]: property, + }) => '#album.' + property, + }, + }, + + steps: () => [ + withAlbum({ + notFoundMode: input('notFoundMode'), + }), + + withPropertyFromObject + .inputs({object: '#album', property: input('property')}) + .outputs({into: 'into'}), + ], +}); // Gets the track section containing this track from its album's track list. // If notFoundMode is set to 'exit', 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({ - into = '#trackSection', - notFoundMode = 'null', -} = {}) { - if (!['exit', 'null'].includes(notFoundMode)) { - throw new TypeError(`Expected notFoundMode to be exit or null`); - } +export const withContainingTrackSection = templateCompositeFrom({ + annotation: `withContainingTrackSection`, + + inputs: { + notFoundMode: input({ + validate: oneOf('exit', 'null'), + defaultValue: 'null', + }), + }, - return compositeFrom(`withContainingTrackSection`, [ - withPropertyFromAlbum({property: 'trackSections', notFoundMode}), + outputs: { + into: '#trackSection', + }, + + steps: () => [ + withPropertyFromAlbum({ + property: input.value('trackSections'), + notFoundMode: input('notFoundMode'), + }), { - dependencies: ['this', '#album.trackSections'], - options: {notFoundMode}, - mapContinuation: {into}, - - compute({ - this: track, - '#album.trackSections': trackSections, - '#options': {notFoundMode}, - }, continuation) { + dependencies: [ + input.myself(), + input('notFoundMode'), + '#album.trackSections', + ], + + compute(continuation, { + [input.myself()]: track, + [input('notFoundMode')]: notFoundMode, + ['#album.trackSections']: trackSections, + }) { if (!trackSections) { - return continuation.raise({into: null}); + return continuation({into: null}); } const trackSection = trackSections.find(({tracks}) => tracks.includes(track)); if (trackSection) { - return continuation.raise({into: trackSection}); + return continuation({into: trackSection}); } else if (notFoundMode === 'exit') { return continuation.exit(null); } else { - return continuation.raise({into: null}); + return continuation({into: null}); } }, }, - ]); -} + ], +}); // 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 @@ -448,29 +488,40 @@ function withContainingTrackSection({ // 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({ - into = '#originalRelease', - selfIfOriginal = false, -} = {}) { - return compositeFrom(`withOriginalRelease`, [ - withResolvedReference({ - ref: 'originalReleaseTrack', - data: 'trackData', - into: '#originalRelease', - find: find.track, - notFoundMode: 'exit', - }), +export const withOriginalRelease = templateCompositeFrom({ + annotation: `withOriginalRelease`, + + inputs: { + selfIfOriginal: input({type: 'boolean', defaultValue: false}), + }, + + outputs: { + into: '#originalRelease', + }, + + steps: () => [ + withResolvedReference + .inputs({ + ref: 'originalReleaseTrack', + data: 'trackData', + find: input.value(find.track), + notFoundMode: input.value('exit'), + }) + .outputs({into: '#originalRelease'}), { - dependencies: ['this', '#originalRelease'], - options: {selfIfOriginal}, - mapContinuation: {into}, - compute: ({ - this: track, - '#originalRelease': originalRelease, - '#options': {selfIfOriginal}, - }, continuation) => - continuation.raise({ + dependencies: [ + input.myself(), + input('selfIfOriginal'), + '#originalRelease', + ], + + compute: (continuation, { + [input.myself()]: track, + [input('selfIfOriginal')]: selfIfOriginal, + ['#originalRelease']: originalRelease, + }) => + continuation({ into: (originalRelease ?? (selfIfOriginal @@ -478,83 +529,97 @@ function withOriginalRelease({ : 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. -function withHasUniqueCoverArt({ - into = '#hasUniqueCoverArt', -} = {}) { - return compositeFrom(`withHasUniqueCoverArt`, [ +export const withHasUniqueCoverArt = templateCompositeFrom({ + annotation: 'withHasUniqueCoverArt', + + outputs: { + into: '#hasUniqueCoverArt', + }, + + steps: () => [ { dependencies: ['disableUniqueCoverArt'], - mapContinuation: {into}, - compute: ({disableUniqueCoverArt}, continuation) => + compute: (continuation, {disableUniqueCoverArt}) => (disableUniqueCoverArt - ? continuation.raise({into: false}) + ? continuation.raiseOutput({into: false}) : continuation()), }, - withResolvedContribs({ - from: 'coverArtistContribs', - into: '#coverArtistContribs', - }), + withResolvedContribs + .inputs({from: 'coverArtistContribs'}) + .outputs({into: '#coverArtistContribs'}), { dependencies: ['#coverArtistContribs'], - mapContinuation: {into}, - compute: ({'#coverArtistContribs': contribsFromTrack}, continuation) => + compute: (continuation, { + ['#coverArtistContribs']: contribsFromTrack, + }) => (empty(contribsFromTrack) ? continuation() - : continuation.raise({into: true})), + : continuation.raiseOutput({into: true})), }, withPropertyFromAlbum({property: 'trackCoverArtistContribs'}), { dependencies: ['#album.trackCoverArtistContribs'], - mapContinuation: {into}, - compute: ({'#album.trackCoverArtistContribs': contribsFromAlbum}, continuation) => - (empty(contribsFromAlbum) - ? continuation.raise({into: false}) - : continuation.raise({into: true})), + compute: (continuation, { + ['#album.trackCoverArtistContribs']: contribsFromAlbum, + }) => + continuation({ + into: !empty(contribsFromAlbum), + }), }, - ]); -} + ], +}); // Shorthand for checking if the track has unique cover art and exposing a // fallback value if it isn't. -function exitWithoutUniqueCoverArt({ - value = null, -} = {}) { - return compositeFrom(`exitWithoutUniqueCoverArt`, [ +export const exitWithoutUniqueCoverArt = templateCompositeFrom({ + annotation: `exitWithoutUniqueCoverArt`, + + inputs: { + value: input({null: true}), + }, + + steps: () => [ withHasUniqueCoverArt(), + exitWithoutDependency({ dependency: '#hasUniqueCoverArt', mode: 'falsy', - value, + value: input('value'), }), - ]); -} + ], +}); + +export const trackReverseReferenceList = templateCompositeFrom({ + annotation: `trackReverseReferenceList`, + + inputs: { + list: input({type: 'string'}), + }, -function trackReverseReferenceList({ - property: refListProperty, -}) { - return compositeFrom(`trackReverseReferenceList`, [ + steps: () => [ withReverseReferenceList({ data: 'trackData', - list: refListProperty, + list: input('list'), }), { flags: {expose: true}, expose: { dependencies: ['#reverseReferenceList'], - compute: ({'#reverseReferenceList': reverseReferenceList}) => + compute: ({ + ['#reverseReferenceList']: reverseReferenceList, + }) => reverseReferenceList.filter(track => !track.originalReleaseTrack), }, }, - ]); -} -*/ + ], +}); |