From d30bc62956358637522d636b4454aee39e7b3d03 Mon Sep 17 00:00:00 2001 From: "(quasar) nebula" Date: Mon, 2 Oct 2023 09:28:07 -0300 Subject: find: accept arrays... experimentally... --- src/find.js | 236 +++++++++++++++++++++++++++++++++++++++++++++++------------- 1 file changed, 188 insertions(+), 48 deletions(-) (limited to 'src') 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), ]; }) ); -- cgit 1.3.0-6-gf8a5