From 934494b5f3ee610d6473b3ca4c21c80731c213af Mon Sep 17 00:00:00 2001 From: "(quasar) nebula" Date: Sun, 21 Sep 2025 12:46:39 -0300 Subject: find: kebab-fuzzy matches in content text Outside content text, this doesn't cause miscapitalized references to slip past, but it does stop them from being specially reported i.e. highlighting which letters need to be updated or treating as more than just "nothing matches for this reference" generic errors. That's a TODO. Sorry! Gyeep! --- src/find.js | 93 ++++++++++++++++++++++++++++++++++++++++++++----------------- 1 file changed, 68 insertions(+), 25 deletions(-) (limited to 'src/find.js') diff --git a/src/find.js b/src/find.js index 8f2170d4..b44c1bb2 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`^(?:(?[a-z-]*):(?=\S))?(?.*)$`); -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,7 +78,7 @@ export function processAvailableMatchesByName(data, { continue; } - const normalizedName = name.toLowerCase(); + const normalizedName = fuzzName(name, fuzz); if (normalizedName in results) { if (normalizedName in multipleNameMatches) { @@ -97,9 +125,9 @@ export function processAvailableMatchesByDirectory(data, { 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); @@ -150,19 +178,23 @@ function oopsNameCapitalizationMismatch(mode, { `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) { - if (name === match.name) { - return match.thing; - } else { + 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, { @@ -242,11 +274,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') { @@ -257,19 +296,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: @@ -279,7 +322,7 @@ function findHelper({ }), matchByName: - prepareMatchByName(mode, { + prepareMatchByName(mode, fuzz, { byName, multipleNameMatches, }), @@ -358,7 +401,7 @@ function findMixedHelper(config) { const multipleNameMatches = Object.create(null); for (const spec of specs) { - processAvailableMatchesByName(data, { + processAvailableMatchesByName(data, null, { ...spec, results: byName, @@ -401,7 +444,7 @@ function findMixedHelper(config) { }, matchByName: - prepareMatchByName(mode, { + prepareMatchByName(mode, null, { byName, multipleNameMatches, }), -- cgit 1.3.0-6-gf8a5