« get me outta code hell

hsmusic-wiki - HSMusic - static wiki software cataloguing collaborative creation
about summary refs log tree commit diff
path: root/src/find.js
diff options
context:
space:
mode:
Diffstat (limited to 'src/find.js')
-rw-r--r--src/find.js215
1 files changed, 79 insertions, 136 deletions
diff --git a/src/find.js b/src/find.js
index fecf1ab0..81f910d9 100644
--- a/src/find.js
+++ b/src/find.js
@@ -2,8 +2,8 @@ import {inspect} from 'node:util';
 
 import CacheableObject from '#cacheable-object';
 import {colors, logWarn} from '#cli';
+import thingConstructors from '#things';
 import {typeAppearance} from '#sugar';
-import {getKebabCase} from '#wiki-data';
 
 function warnOrThrow(mode, message) {
   if (mode === 'error') {
@@ -149,125 +149,75 @@ function findHelper({
   };
 }
 
-const find = {
-  album: findHelper({
-    referenceTypes: ['album', 'album-commentary', 'album-gallery'],
-  }),
-
-  artist: findHelper({
-    referenceTypes: ['artist', 'artist-gallery'],
-
-    include: artist => !artist.isAlias,
-  }),
+const hardcodedFindSpecs = {
+  // Listings aren't Thing objects, so this find spec isn't provided by any
+  // Thing constructor.
+  listing: {
+    referenceTypes: ['listing'],
+    bindTo: 'listingSpec',
+  },
+};
 
-  artistIncludingAliases: findHelper({
-    referenceTypes: ['artist', 'artist-gallery'],
+export function getAllFindSpecs(key) {
+  try {
+    thingConstructors;
+  } catch (error) {
+    throw new Error(`Thing constructors aren't ready yet, can't get all find specs`);
+  }
 
-    getMatchableDirectories(artist) {
-      // Regular artists are always matchable by their directory.
-      if (!artist.isAlias) {
-        return [artist.directory];
-      }
+  const findSpecs = {...hardcodedFindSpecs};
 
-      const originalArtist = artist.aliasedArtist;
+  for (const thingConstructor of Object.values(thingConstructors)) {
+    const thingFindSpecs = thingConstructor[Symbol.for('Thing.findSpecs')];
+    if (!thingFindSpecs) continue;
 
-      // Aliases never match by the same directory as the original.
-      if (artist.directory === originalArtist.directory) {
-        return [];
-      }
+    Object.assign(findSpecs, thingFindSpecs);
+  }
 
-      // 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 [];
-        }
-      }
+  return findSpecs;
+}
 
-      // 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 [];
-      }
+export function findFindSpec(key) {
+  if (Object.hasOwn(hardcodedFindSpecs, key)) {
+    return hardcodedFindSpecs[key];
+  }
 
-      return [artist.directory];
-    },
-  }),
+  try {
+    thingConstructors;
+  } catch (error) {
+    throw new Error(`Thing constructors aren't ready yet, can't check if "find.${key}" available`);
+  }
 
-  artTag: findHelper({
-    referenceTypes: ['tag'],
+  for (const thingConstructor of Object.values(thingConstructors)) {
+    const thingFindSpecs = thingConstructor[Symbol.for('Thing.findSpecs')];
+    if (!thingFindSpecs) continue;
 
-    getMatchableNames: tag =>
-      (tag.isContentWarning
-        ? [`cw: ${tag.name}`]
-        : [tag.name]),
-  }),
+    if (Object.hasOwn(thingFindSpecs, key)) {
+      return thingFindSpecs[key];
+    }
+  }
 
-  flash: findHelper({
-    referenceTypes: ['flash'],
-  }),
+  throw new Error(`"find.${key}" isn't available`);
+}
 
-  flashAct: findHelper({
-    referenceTypes: ['flash-act'],
-  }),
+export default new Proxy({}, {
+  get: (store, key) => {
+    if (!Object.hasOwn(store, key)) {
+      let behavior = (...args) => {
+        // This will error if the find spec isn't available...
+        const findSpec = findFindSpec(key);
 
-  group: findHelper({
-    referenceTypes: ['group', 'group-gallery'],
-  }),
+        // ...or, if it is available, replace this function with the
+        // ready-for-use find function made out of that find spec.
+        return (behavior = findHelper(findSpec))(...args);
+      };
 
-  listing: findHelper({
-    referenceTypes: ['listing'],
-  }),
-
-  newsEntry: findHelper({
-    referenceTypes: ['news-entry'],
-  }),
-
-  staticPage: findHelper({
-    referenceTypes: ['static'],
-  }),
-
-  track: findHelper({
-    referenceTypes: ['track'],
-
-    getMatchableNames: track =>
-      (track.alwaysReferenceByDirectory
-        ? []
-        : [track.name]),
-  }),
-
-  trackOriginalReleasesOnly: findHelper({
-    referenceTypes: ['track'],
-
-    include: track =>
-      !CacheableObject.getUpdateValue(track, 'originalReleaseTrack'),
-
-    // It's still necessary to check alwaysReferenceByDirectory here, since it
-    // may be set manually (with the `Always Reference By Directory` field), and
-    // these shouldn't be matched by name (as per usual). See the definition for
-    // that property for more information.
-    getMatchableNames: track =>
-      (track.alwaysReferenceByDirectory
-        ? []
-        : [track.name]),
-  }),
-};
+      store[key] = (...args) => behavior(...args);
+    }
 
-export default find;
+    return store[key];
+  },
+});
 
 // Handy utility function for binding the find.thing() functions to a complete
 // wikiData object, optionally taking default options to provide to the find
@@ -275,34 +225,27 @@ export default find;
 // called, so if their values change, you'll have to continue with a fresh call
 // to bindFind.
 export function bindFind(wikiData, opts1) {
-  return Object.fromEntries(
-    Object.entries({
-      album: 'albumData',
-      artist: 'artistData',
-      artTag: 'artTagData',
-      flash: 'flashData',
-      flashAct: 'flashActData',
-      group: 'groupData',
-      listing: 'listingSpec',
-      newsEntry: 'newsData',
-      staticPage: 'staticPageData',
-      track: 'trackData',
-      trackOriginalReleasesOnly: 'trackData',
-    }).map(([key, value]) => {
-      const findFn = find[key];
-      const thingData = wikiData[value];
-      return [
-        key,
-        opts1
-          ? (ref, opts2) =>
-              opts2
-                ? findFn(ref, thingData, {...opts1, ...opts2})
-                : findFn(ref, thingData, opts1)
-          : (ref, opts2) =>
-              opts2
-                ? findFn(ref, thingData, opts2)
-                : findFn(ref, thingData),
-      ];
-    })
-  );
+  const findSpecs = getAllFindSpecs();
+
+  const boundFindFns = {};
+
+  for (const [key, spec] of Object.entries(findSpecs)) {
+    if (!spec.bindTo) continue;
+
+    const findFn = findHelper(spec);
+    const thingData = wikiData[spec.bindTo];
+
+    boundFindFns[key] =
+      (opts1
+        ? (ref, opts2) =>
+            (opts2
+              ? findFn(ref, thingData, {...opts1, ...opts2})
+              : findFn(ref, thingData, opts1))
+        : (ref, opts2) =>
+            (opts2
+              ? findFn(ref, thingData, opts2)
+              : findFn(ref, thingData)));
+  }
+
+  return boundFindFns;
 }