« 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/find.js236
1 files changed, 188 insertions, 48 deletions
diff --git a/src/find.js b/src/find.js
index 66f705e4..2959ed56 100644
--- a/src/find.js
+++ b/src/find.js
@@ -32,85 +32,223 @@ function findHelper(keys, findFns = {}) {
   // console.
   return (fullRef, data, {mode = 'warn'} = {}) => {
     if (!fullRef) return null;
-    if (typeof fullRef !== 'string') {
-      throw new Error(`Got a reference that is ${typeof fullRef}, not string: ${fullRef}`);
+
+    if (typeof fullRef !== 'string' && !Array.isArray(fullRef)) {
+      throw new Error(`Got a reference that is ${typeof fullRef}, not string or array: ${fullRef}`);
     }
 
     if (!data) {
       throw new Error(`Expected data to be present`);
     }
 
-    if (!Array.isArray(data) && data.wikiData) {
-      throw new Error(`Old {wikiData: {...}} format provided`);
-    }
-
     let cacheForThisData = cache.get(data);
-    const cachedValue = cacheForThisData?.[fullRef];
-    if (cachedValue) {
-      globalThis.NUM_CACHE = (globalThis.NUM_CACHE || 0) + 1;
-      return cachedValue;
-    }
     if (!cacheForThisData) {
       cacheForThisData = Object.create(null);
       cache.set(data, cacheForThisData);
     }
 
-    const match = fullRef.match(keyRefRegex);
-    if (!match) {
-      return warnOrThrow(mode, `Malformed link reference: "${fullRef}"`);
-    }
+    const parseFullRef = fullRef => {
+      const regexMatch = fullRef.match(keyRefRegex);
+      if (!regexMatch) {
+        warnOrThrow(mode, `Malformed link reference: "${fullRef[i]}"`);
+        return {error: true, key: null, ref: null};
+      }
+
+      const key = regexMatch[1];
+      const ref = regexMatch[2];
 
-    const key = match[1];
-    const ref = match[2];
+      return {error: false, key, ref};
+    };
 
-    const found = key ? byDirectory(ref, data, mode) : byName(ref, data, mode);
+    if (typeof fullRef === 'string') {
+      const cachedMatch = cacheForThisData[fullRef];
+      if (cachedMatch) return cachedMatch;
 
-    if (!found) {
-      warnOrThrow(mode, `Didn't match anything for ${colors.bright(fullRef)}`);
+      const {error: regexError, key, ref} = parseFullRef(fullRef);
+      if (regexError) return null;
+
+      const match =
+        (key
+          ? byDirectory(ref, data, mode)
+          : byName(ref, data, mode));
+
+      if (!match) {
+        warnOrThrow(mode, `Didn't match anything for ${colors.bright(fullRef)}`);
+      }
+
+      cacheForThisData[fullRef] = match;
+
+      return match;
     }
 
-    cacheForThisData[fullRef] = found;
+    const fullRefList = fullRef;
+    if (Array.isArray(fullRefList)) {
+      const byDirectoryUncachedIndices = [];
+      const byDirectoryUncachedRefs = [];
+      const byNameUncachedIndices = [];
+      const byNameUncachedRefs = [];
+
+      for (let index = 0; index < fullRefList.length; index++) {
+        const cachedMatch = cacheForThisData[fullRefList[index]];
+        if (cachedMatch) return cachedMatch;
 
-    return found;
+        const {error: regexError, key, ref} = parseFullRef(fullRefList[index]);
+        if (regexError) return null;
+
+        if (key) {
+          byDirectoryUncachedIndices.push(index);
+          byDirectoryUncachedRefs.push(ref);
+        } else {
+          byNameUncachedIndices.push(index);
+          byNameUncachedRefs.push(ref);
+        }
+      }
+
+      const byDirectoryMatches = byDirectory(byDirectoryUncachedRefs, data, mode);
+      const byNameMatches = byName(byNameUncachedRefs, data, mode);
+
+      const results = [];
+
+      const processMatch = (match, sourceIndex) => {
+        if (match) {
+          cacheForThisData[fullRefList[sourceIndex]] = match;
+          results[sourceIndex] = match;
+        } else {
+          // TODO: Aggregate errors
+          warnOrThrow(mode, `Didn't match anything for ${fullRefList[sourceIndex]}`);
+          results[sourceIndex] = null;
+        }
+      };
+
+      for (let index = 0; index < byDirectoryMatches.length; index++) {
+        const sourceIndex = byDirectoryUncachedIndices[index];
+        const match = byDirectoryMatches[index];
+        processMatch(match, sourceIndex);
+      }
+
+      for (let index = 0; index < byNameMatches.length; index++) {
+        const sourceIndex = byNameUncachedIndices[index];
+        const match = byNameMatches[index];
+        processMatch(match, sourceIndex);
+      }
+
+      return results;
+    }
   };
 }
 
 function matchDirectory(ref, data) {
-  return data.find(({directory}) => directory === ref);
+  if (typeof ref === 'string') {
+    return data.find(({directory}) => directory === ref);
+  }
+
+  const refList = ref;
+  if (Array.isArray(refList)) {
+    const refSet = new Set(refList);
+    const refMap = new Map();
+
+    for (const thing of data) {
+      const {directory} = thing;
+      if (refSet.has(directory)) {
+        refMap.set(directory, thing);
+      }
+    }
+
+    return refList.map(ref => refMap.get(ref) ?? null);
+  }
 }
 
 function matchName(ref, data, mode) {
-  const matches =
-    data
-      .filter(({name}) => name.toLowerCase() === ref.toLowerCase())
-      .filter(thing =>
-        (Object.hasOwn(thing, 'alwaysReferenceByDirectory')
-          ? !thing.alwaysReferenceByDirectory
-          : true));
-
-  if (matches.length > 1) {
-    return warnOrThrow(mode,
-      `Multiple matches for reference "${ref}". Please resolve:\n` +
-      matches.map(match => `- ${inspect(match)}\n`).join('') +
-      `Returning null for this reference.`);
-  }
+  if (typeof ref === 'string') {
+    const matches =
+      data
+        .filter(({name}) => name.toLowerCase() === ref.toLowerCase())
+        .filter(thing =>
+          (Object.hasOwn(thing, 'alwaysReferenceByDirectory')
+            ? !thing.alwaysReferenceByDirectory
+            : true));
 
-  if (matches.length === 0) {
-    return null;
-  }
+    if (matches.length > 1) {
+      return warnOrThrow(mode,
+        `Multiple matches for reference "${ref}". Please resolve:\n` +
+        matches.map(match => `- ${inspect(match)}\n`).join('') +
+        `Returning null for this reference.`);
+    }
 
-  const thing = matches[0];
+    if (matches.length === 0) {
+      return null;
+    }
+
+    const match = matches[0];
 
-  if (ref !== thing.name) {
-    warnOrThrow(mode,
-      `Bad capitalization: ${colors.red(ref)} -> ${colors.green(thing.name)}`);
+    if (ref !== match.name) {
+      warnOrThrow(mode,
+        `Bad capitalization: ${colors.red(ref)} -> ${colors.green(match.name)}`);
+    }
+
+    return match;
   }
 
-  return thing;
+  const refList = ref;
+  if (Array.isArray(refList)) {
+    const refSet = new Set(refList.map(name => name.toLowerCase()));
+    const refMap = new Map();
+    const multipleMatchesMap = new Map();
+
+    for (const thing of data) {
+      if (thing.alwaysReferenceByDirectory) continue;
+      const name = thing.name.toLowerCase();
+      if (refSet.has(name)) {
+        if (refMap.has(name)) {
+          refMap.set(name, null); // .has() will still return true
+          if (multipleMatchesMap.has(name)) {
+            multipleMatchesMap.get(name).push(thing);
+          } else {
+            multipleMatchesMap.set(name, [thing]);
+          }
+        } else {
+          refMap.set(name, thing);
+        }
+      }
+    }
+
+    // TODO: Aggregate errors
+    for (const [name, matches] of multipleMatchesMap.entries()) {
+      warnOrThrow(mode,
+        `Multiple matches for reference "${ref}". Please resolve:\n` +
+        matches.map(match => `- ${inspect(match)}\n`).join('') +
+        `Returning null for this reference.`);
+    }
+
+    return refList.map(ref => {
+      const match = refMap.get(ref);
+      if (!match) return null;
+
+      // TODO: Aggregate errors
+      if (ref !== match.name) {
+        warnOrThrow(mode,
+          `Bad capitalization: ${colors.red(ref)} -> ${colors.green(match.name)}`);
+      }
+
+      return match;
+    });
+  }
 }
 
-function matchTagName(ref, data, quiet) {
-  return matchName(ref.startsWith('cw: ') ? ref.slice(4) : ref, data, quiet);
+function matchTagName(ref, data, mode) {
+  if (typeof ref === 'string') {
+    return matchName(
+      ref.startsWith('cw: ') ? ref.slice(4) : ref,
+      data,
+      mode);
+  }
+
+  if (Array.isArray(ref)) {
+    return matchName(
+      ref.map(ref => ref.startsWith('cw: ') ? ref.slice(4) : ref),
+      data,
+      mode);
+  }
 }
 
 const find = {
@@ -155,7 +293,9 @@ export function bindFind(wikiData, opts1) {
                 ? findFn(ref, thingData, {...opts1, ...opts2})
                 : findFn(ref, thingData, opts1)
           : (ref, opts2) =>
-              opts2 ? findFn(ref, thingData, opts2) : findFn(ref, thingData),
+              opts2
+                ? findFn(ref, thingData, opts2)
+                : findFn(ref, thingData),
       ];
     })
   );