diff options
Diffstat (limited to 'src/find.js')
| -rw-r--r-- | src/find.js | 166 |
1 files changed, 134 insertions, 32 deletions
diff --git a/src/find.js b/src/find.js index e7f5cda1..7b605e97 100644 --- a/src/find.js +++ b/src/find.js @@ -4,6 +4,7 @@ import {colors, logWarn} from '#cli'; import {compareObjects, stitchArrays, typeAppearance} from '#sugar'; import thingConstructors from '#things'; import {isFunction, validateArrayItems} from '#validators'; +import {getCaseSensitiveKebabCase} from '#wiki-data'; import * as fr from './find-reverse.js'; @@ -30,7 +31,34 @@ function warnOrThrow(mode, message) { export const keyRefRegex = new RegExp(String.raw`^(?:(?<key>[a-z-]*):(?=\S))?(?<ref>.*)$`); -export function processAvailableMatchesByName(data, { +function getFuzzHash(fuzz = {}) { + if (!fuzz) { + return 0; + } + + return ( + fuzz.capitalization << 0 + + fuzz.kebab << 1 + ); +} + +export function fuzzName(name, fuzz = {}) { + if (!fuzz) { + return name; + } + + if (fuzz.capitalization) { + name = name.toLowerCase(); + } + + if (fuzz.kebab) { + name = getCaseSensitiveKebabCase(name); + } + + return name; +} + +export function processAvailableMatchesByName(data, fuzz, { include = _thing => true, getMatchableNames = thing => @@ -50,17 +78,20 @@ export function processAvailableMatchesByName(data, { continue; } - const normalizedName = name.toLowerCase(); + const normalizedName = fuzzName(name, fuzz); if (normalizedName in results) { 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}; } } } @@ -87,16 +118,16 @@ export function processAvailableMatchesByDirectory(data, { continue; } - results[directory] = thing; + results[directory] = {thing, directory}; } } return {results}; } -export function processAllAvailableMatches(data, spec) { +export function processAllAvailableMatches(data, fuzz, spec) { const {results: byName, multipleNameMatches} = - processAvailableMatchesByName(data, spec); + processAvailableMatchesByName(data, fuzz, spec); const {results: byDirectory} = processAvailableMatchesByDirectory(data, spec); @@ -109,21 +140,69 @@ function oopsMultipleNameMatches(mode, { normalizedName, multipleNameMatches, }) { + try { + return warnOrThrow(mode, + `Multiple matches for reference "${name}". Please resolve:\n` + + multipleNameMatches[normalizedName] + .map(match => `- ${inspect(match)}\n`) + .join('') + + `Returning null for this reference.`); + } catch (caughtError) { + throw Object.assign(caughtError, { + [Symbol.for('hsmusic.find.multipleNameMatches')]: + multipleNameMatches[normalizedName], + }); + } +} + +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, - `Multiple matches for reference "${name}". Please resolve:\n` + - multipleNameMatches[normalizedName] - .map(match => `- ${inspect(match)}\n`) - .join('') + + `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}) { +export function prepareMatchByName(mode, fuzz, {byName, multipleNameMatches}) { return (name) => { - const normalizedName = name.toLowerCase(); + const normalizedName = fuzzName(name, fuzz); const match = byName[normalizedName]; if (match) { - return match; + if ( + !fuzz?.capitalization && + name !== match.name && + name.toLowerCase === match.name.toLowerCase() + ) { + return oopsNameCapitalizationMismatch(mode, { + matchingName: name, + matchedName: match.name, + }); + } else { + return match.thing; + } } else if (multipleNameMatches[normalizedName]) { return oopsMultipleNameMatches(mode, { name, @@ -154,7 +233,13 @@ export function prepareMatchByDirectory(mode, {referenceTypes, byDirectory}) { }); } - return byDirectory[directory]; + const match = byDirectory[directory]; + + if (match) { + return match.thing; + } else { + return null; + } }; } @@ -196,11 +281,18 @@ function findHelper({ // hasn't changed! const cache = new WeakMap(); - // The mode argument here may be 'warn', 'error', or 'quiet'. 'error' throws - // errors for null matches (with details about the error), while 'warn' and - // 'quiet' both return null, with 'warn' logging details directly to the - // console. - return (fullRef, data, {mode = 'warn'} = {}) => { + return (fullRef, data, { + // The mode argument here may be 'warn', 'error', or 'quiet'. 'error' throws + // errors for null matches (with details about the error), while 'warn' and + // 'quiet' both return null, with 'warn' logging details directly to the + // console. + mode = 'warn', + + fuzz = { + capitalization: false, + kebab: false, + }, + } = {}) => { if (!fullRef) return null; if (typeof fullRef !== 'string') { @@ -211,19 +303,23 @@ function findHelper({ throw new TypeError(`Expected data to be present`); } - let subcache = cache.get(data); - if (!subcache) { - subcache = - processAllAvailableMatches(data, { + let dataSubcache = cache.get(data); + if (!dataSubcache) { + cache.set(data, dataSubcache = new Map()); + } + + const fuzzHash = getFuzzHash(fuzz); + let fuzzSubcache = dataSubcache.get(fuzzHash); + if (!fuzzSubcache) { + dataSubcache.set(fuzzHash, fuzzSubcache = + processAllAvailableMatches(data, fuzz, { include, getMatchableNames, getMatchableDirectories, - }); - - cache.set(data, subcache); + })); } - const {byDirectory, byName, multipleNameMatches} = subcache; + const {byDirectory, byName, multipleNameMatches} = fuzzSubcache; return matchHelper(fullRef, mode, { matchByDirectory: @@ -233,7 +329,7 @@ function findHelper({ }), matchByName: - prepareMatchByName(mode, { + prepareMatchByName(mode, fuzz, { byName, multipleNameMatches, }), @@ -312,7 +408,7 @@ function findMixedHelper(config) { const multipleNameMatches = Object.create(null); for (const spec of specs) { - processAvailableMatchesByName(data, { + processAvailableMatchesByName(data, null, { ...spec, results: byName, @@ -345,11 +441,17 @@ function findMixedHelper(config) { }); } - return byDirectory[referenceType][directory]; + const match = byDirectory[referenceType][directory]; + + if (match) { + return match.thing; + } else { + return null; + } }, matchByName: - prepareMatchByName(mode, { + prepareMatchByName(mode, null, { byName, multipleNameMatches, }), |