« get me outta code hell

hsmusic-wiki - HSMusic - static wiki software cataloguing collaborative creation
about summary refs log tree commit diff
diff options
context:
space:
mode:
-rw-r--r--src/data/things/album.js1
-rw-r--r--src/data/things/composite.js14
-rw-r--r--src/data/things/homepage-layout.js1
-rw-r--r--src/data/things/thing.js72
-rw-r--r--src/data/things/track.js299
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),
       },
     },
-  ]);
-}
-*/
+  ],
+});