« 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.js200
1 files changed, 96 insertions, 104 deletions
diff --git a/src/find.js b/src/find.js
index d647419a..8f2170d4 100644
--- a/src/find.js
+++ b/src/find.js
@@ -5,6 +5,16 @@ import {compareObjects, stitchArrays, typeAppearance} from '#sugar';
 import thingConstructors from '#things';
 import {isFunction, validateArrayItems} from '#validators';
 
+import * as fr from './find-reverse.js';
+
+import {
+  tokenKey as findTokenKey,
+  boundData as boundFindData,
+  boundOptions as boundFindOptions,
+} from './find-reverse.js';
+
+export {findTokenKey, boundFindData, boundFindOptions};
+
 function warnOrThrow(mode, message) {
   if (mode === 'error') {
     throw new Error(message);
@@ -24,7 +34,7 @@ export function processAvailableMatchesByName(data, {
   include = _thing => true,
 
   getMatchableNames = thing =>
-    (Object.hasOwn(thing, 'name')
+    (thing.constructor.hasPropertyDescriptor('name')
       ? [thing.name]
       : []),
 
@@ -32,7 +42,7 @@ export function processAvailableMatchesByName(data, {
   multipleNameMatches = Object.create(null),
 }) {
   for (const thing of data) {
-    if (!include(thing)) continue;
+    if (!include(thing, thingConstructors)) continue;
 
     for (const name of getMatchableNames(thing)) {
       if (typeof name !== 'string') {
@@ -46,11 +56,14 @@ export function processAvailableMatchesByName(data, {
         if (normalizedName in multipleNameMatches) {
           multipleNameMatches[normalizedName].push(thing);
         } else {
-          multipleNameMatches[normalizedName] = [results[normalizedName], thing];
+          multipleNameMatches[normalizedName] = [
+            results[normalizedName].thing,
+            thing,
+          ];
           results[normalizedName] = null;
         }
       } else {
-        results[normalizedName] = thing;
+        results[normalizedName] = {thing, name};
       }
     }
   }
@@ -62,14 +75,14 @@ export function processAvailableMatchesByDirectory(data, {
   include = _thing => true,
 
   getMatchableDirectories = thing =>
-    (Object.hasOwn(thing, 'directory')
+    (thing.constructor.hasPropertyDescriptor('directory')
       ? [thing.directory]
       : [null]),
 
   results = Object.create(null),
 }) {
   for (const thing of data) {
-    if (!include(thing)) continue;
+    if (!include(thing, thingConstructors)) continue;
 
     for (const directory of getMatchableDirectories(thing)) {
       if (typeof directory !== 'string') {
@@ -77,7 +90,7 @@ export function processAvailableMatchesByDirectory(data, {
         continue;
       }
 
-      results[directory] = thing;
+      results[directory] = {thing, directory};
     }
   }
 
@@ -107,13 +120,50 @@ function oopsMultipleNameMatches(mode, {
     `Returning null for this reference.`);
 }
 
+function oopsNameCapitalizationMismatch(mode, {
+  matchingName,
+  matchedName,
+}) {
+  if (matchingName.length === matchedName.length) {
+    let a = '', b = '';
+    for (let i = 0; i < matchingName.length; i++) {
+      if (
+        matchingName[i] === matchedName[i] ||
+        matchingName[i].toLowerCase() !== matchingName[i].toLowerCase()
+      ) {
+        a += matchingName[i];
+        b += matchedName[i];
+      } else {
+        a += colors.bright(colors.red(matchingName[i]));
+        b += colors.bright(colors.green(matchedName[i]));
+      }
+    }
+
+    matchingName = a;
+    matchedName = b;
+  }
+
+  return warnOrThrow(mode,
+    `Provided capitalization differs from the matched name. Please resolve:\n` +
+    `- provided: ${matchingName}\n` +
+    `- should be: ${matchedName}\n` +
+    `Returning null for this reference.`);
+}
+
 export function prepareMatchByName(mode, {byName, multipleNameMatches}) {
   return (name) => {
     const normalizedName = name.toLowerCase();
     const match = byName[normalizedName];
 
     if (match) {
-      return match;
+      if (name === match.name) {
+        return match.thing;
+      } else {
+        return oopsNameCapitalizationMismatch(mode, {
+          matchingName: name,
+          matchedName: match.name,
+        });
+      }
     } else if (multipleNameMatches[normalizedName]) {
       return oopsMultipleNameMatches(mode, {
         name,
@@ -144,7 +194,13 @@ export function prepareMatchByDirectory(mode, {referenceTypes, byDirectory}) {
       });
     }
 
-    return byDirectory[directory];
+    const match = byDirectory[directory];
+
+    if (match) {
+      return match.thing;
+    } else {
+      return null;
+    }
   };
 }
 
@@ -240,6 +296,14 @@ const hardcodedFindSpecs = {
   },
 };
 
+const findReverseHelperConfig = {
+  word: `find`,
+  constructorKey: Symbol.for('Thing.findSpecs'),
+
+  hardcodedSpecs: hardcodedFindSpecs,
+  postprocessSpec: postprocessFindSpec,
+};
+
 export function postprocessFindSpec(spec, {thingConstructor}) {
   const newSpec = {...spec};
 
@@ -248,9 +312,9 @@ export function postprocessFindSpec(spec, {thingConstructor}) {
   if (spec[Symbol.for('Thing.findThisThingOnly')] !== false) {
     if (spec.include) {
       const oldInclude = spec.include;
-      newSpec.include = thing =>
+      newSpec.include = (thing, ...args) =>
         thing instanceof thingConstructor &&
-        oldInclude(thing);
+        oldInclude(thing, ...args);
     } else {
       newSpec.include = thing =>
         thing instanceof thingConstructor;
@@ -261,58 +325,13 @@ export function postprocessFindSpec(spec, {thingConstructor}) {
 }
 
 export function getAllFindSpecs() {
-  try {
-    thingConstructors;
-  } catch (error) {
-    throw new Error(`Thing constructors aren't ready yet, can't get all find specs`);
-  }
-
-  const findSpecs = {...hardcodedFindSpecs};
-
-  for (const thingConstructor of Object.values(thingConstructors)) {
-    const thingFindSpecs = thingConstructor[Symbol.for('Thing.findSpecs')];
-    if (!thingFindSpecs) continue;
-
-    for (const [key, spec] of Object.entries(thingFindSpecs)) {
-      findSpecs[key] =
-        postprocessFindSpec(spec, {
-          thingConstructor,
-        });
-    }
-  }
-
-  return findSpecs;
+  return fr.getAllSpecs(findReverseHelperConfig);
 }
 
 export function findFindSpec(key) {
-  if (Object.hasOwn(hardcodedFindSpecs, key)) {
-    return hardcodedFindSpecs[key];
-  }
-
-  try {
-    thingConstructors;
-  } catch (error) {
-    throw new Error(`Thing constructors aren't ready yet, can't check if "find.${key}" available`);
-  }
-
-  for (const thingConstructor of Object.values(thingConstructors)) {
-    const thingFindSpecs = thingConstructor[Symbol.for('Thing.findSpecs')];
-    if (!thingFindSpecs) continue;
-
-    if (Object.hasOwn(thingFindSpecs, key)) {
-      return postprocessFindSpec(thingFindSpecs[key], {
-        thingConstructor,
-      });
-    }
-  }
-
-  throw new Error(`"find.${key}" isn't available`);
+  return fr.findSpec(key, findReverseHelperConfig);
 }
 
-export const findTokenKey = Symbol.for('find.findTokenKey');
-export const boundFindData = Symbol.for('find.boundFindData');
-export const boundFindOptions = Symbol.for('find.boundFindOptions');
-
 function findMixedHelper(config) {
   const
     keys = Object.keys(config),
@@ -372,7 +391,13 @@ function findMixedHelper(config) {
           });
         }
 
-        return byDirectory[referenceType][directory];
+        const match = byDirectory[referenceType][directory];
+
+        if (match) {
+          return match.thing;
+        } else {
+          return null;
+        }
       },
 
       matchByName:
@@ -425,27 +450,14 @@ export function findMixed(config) {
   return findMixedStore.get(config);
 }
 
-export default new Proxy({}, {
-  get: (store, key) => {
+export default fr.tokenProxy({
+  findSpec: findFindSpec,
+  prepareBehavior: findHelper,
+
+  handle(key) {
     if (key === 'mixed') {
       return findMixed;
     }
-
-    if (!Object.hasOwn(store, key)) {
-      let behavior = (...args) => {
-        // This will error if the find spec isn't available...
-        const findSpec = findFindSpec(key);
-
-        // ...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);
-      };
-
-      store[key] = (...args) => behavior(...args);
-      store[key][findTokenKey] = key;
-    }
-
-    return store[key];
   },
 });
 
@@ -454,33 +466,13 @@ export default new Proxy({}, {
 // function. Note that this caches the arrays read from wikiData right when it's
 // called, so if their values change, you'll have to continue with a fresh call
 // to bindFind.
-export function bindFind(wikiData, opts1) {
-  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)));
-
-    boundFindFns[key][boundFindData] = thingData;
-    boundFindFns[key][boundFindOptions] = opts1 ?? {};
-  }
+export function bindFind(wikiData, opts) {
+  const boundFind = fr.bind(wikiData, opts, {
+    getAllSpecs: getAllFindSpecs,
+    prepareBehavior: findHelper,
+  });
 
-  boundFindFns.mixed = findMixed;
+  boundFind.mixed = findMixed;
 
-  return boundFindFns;
+  return boundFind;
 }