a.length)&&(b=a.length);for(var c=0,d=Array(b);cl.length)return 0;for(var o=d&&d.length,p=m.length;0h.maxIterations)return 0;k++;var q=g.substring(m.location,m.location+p),r=w(f,q,new y(l.location,l.length-m.length+p));if(r.isValid()){n.location=n.isValid()?b(n.location,r.location):r.location,n.max(r.max()),d&&d.push(r.toArray());var s=new y(r.max(),l.max()-r.max()),t=new y(m.location+p,m.length-p),u=e(s,t,n);if(u){var v=s.location-l.location,x=!0,z=h.useSkipReduction(a,c,u,s,l,s,r,n);if(r.location>l.location)if(z&&-1=l.location;A--)-1=l.location;B--)-1%._=&[]+ \t\n\r",uppercaseLetters:function(){for(var a=[],b=0;26>b;b++)a.push(String.fromCharCode(65+b));return a.join("")}(),ignoredScore:.9,skippedScore:.15,emptyQueryScore:0,maxIterations:Math.pow(2,16)},A={longStringLength:150,maxMatchStartPct:.15,minMatchDensityPct:.75,maxMatchDensityPct:.95,beginningOfStringPct:.1},B=function(){function a(b){d(this,a),Object.assign(this,z,b)}return f(a,[{key:"useSkipReduction",value:function(){return!0}},{key:"adjustRemainingScore",value:function(a,b,c,d,e,f){return c*f.length}}]),a}(),C=function(a){function c(a){return d(this,c),e.call(this,Object.assign({},A,a))}g(c,a);var e=m(c);return f(c,[{key:"useSkipReduction",value:function(a,b,c,d,e,f,g){var h=a.length,i=h<=this.longStringLength,j=g.location/h;return i||j=this.minMatchDensityPct?1:l,m=l>=this.maxMatchDensityPct?1:m),d*b(g.length,this.longStringLength)*l*m}}]),c}(B),D=u(),E=new B,F=new B({emptyQueryScore:.9,adjustRemainingScore:function(a,b,c,d,e,f,g){var h=c*f.length;return d||(h+=(g.location-e.location)/2),h}});v.createConfig=u;var G=function(){function a(){var b=0g&&b.push({item:n,score:q,matches:p,_:o})}else for(var r=0;rx&&(x=J,y=D,z=H)}}x>g&&(u.score=x,u.scoreKey=y,u.scoreValue=z,b.push(u))}return b.sort(this.compareScoredStrings),b}},{key:"setKeys",value:function(a,b){if(this.keys=a.slice(),this.sortKey=b,this.keys.length){var c=this.scorer;this.keys=this.keys.map(function(a){var b=a.length?{name:a,scorer:c}:a;if(!Array.isArray(b.name))-10){o.items.push({score:i,id:t})}})}else{t.iterator(t.items,function(r,t){o.items.push({score:1,id:t})})}u=t.getSortFunction(o,e);if(u)o.items.sort(u);o.total=o.items.length;if(typeof e.limit==="number"){o.items=o.items.slice(0,e.limit)}return o};var h=function(r,t){if(typeof r==="number"&&typeof t==="number"){return r>t?1:rt)return 1;if(t>r)return-1;return 0};var o=function(r,t){var e,n,i,o;for(e=1,n=arguments.length;e 0) {
60 | output += separator + '@' + empty;
61 | empty = 0;
62 | separator = ',';
63 | }
64 |
65 | output += separator + element;
66 | separator = ',';
67 | }
68 | }
69 |
70 | return output + ']';
71 | }
72 |
73 | var parse = function(str) {
74 | var arr = [];
75 | var stack = [arr];
76 | var currentIndex = 1;
77 |
78 | while(stack.length !== 0) {
79 | var currentArr = stack[stack.length - 1];
80 | var element = '';
81 |
82 | for(; currentIndex < str.length; currentIndex++) {
83 | var char = str[currentIndex];
84 | if(char === ',') {
85 | if(element.length !== 0) {
86 | if(element[0] === '@') {
87 | var elementInt = parseInt(element.substring(1));
88 | for(var i = 0; i < elementInt; i++) {
89 | currentArr.push(undefined);
90 | }
91 | } else {
92 | currentArr.push(parseFloat(element));
93 | }
94 | element = '';
95 | }
96 | } else if(char === '[') {
97 | var childArr = [];
98 | currentArr.push(childArr);
99 | stack.push(childArr);
100 | currentIndex++;
101 | break;
102 | } else if(char === ']') {
103 | stack.pop();
104 | currentIndex++;
105 | break;
106 | } else {
107 | element += char;
108 | }
109 | }
110 |
111 | if(element.length !== 0) {
112 | currentArr.push(parseInt(element));
113 | }
114 | }
115 |
116 | return arr;
117 | }
118 |
119 | var getTerms = function(entry) {
120 | if (entry.length === 0) {
121 | return [];
122 | }
123 |
124 | var terms = entry.split(whitespaceRE);
125 |
126 | if(terms[0].length === 0) {
127 | terms.shift();
128 | }
129 |
130 | if(terms[terms.length - 1].length === 0) {
131 | terms.pop();
132 | }
133 |
134 | return terms;
135 | }
136 |
137 | var processEntry = function(entry) {
138 | if(entry.length === 0) {
139 | return entry;
140 | } else {
141 | var processors = config.processors;
142 |
143 | for(var i = 0; i < processors.length; i++) {
144 | entry = processors[i](entry);
145 | }
146 |
147 | return entry;
148 | }
149 | }
150 |
151 | var update = function(results, resultIndexes, increment, data) {
152 | var relevance = data[1];
153 | for(var i = 2; i < data.length; i++) {
154 | var index = data[i];
155 | var resultIndex = resultIndexes[index];
156 | if(resultIndex === undefined) {
157 | var lastIndex = results.length;
158 | resultIndexes[index] = lastIndex;
159 | results[lastIndex] = {
160 | index: index,
161 | score: relevance * increment
162 | };
163 | } else {
164 | results[resultIndex].score += relevance * increment;
165 | }
166 | }
167 | }
168 |
169 | var Wade = function(data) {
170 | var search = function(query) {
171 | var index = search.index;
172 | var processed = processEntry(query);
173 | var results = [];
174 | var resultIndexes = {};
175 |
176 | if(processed.length === 0) {
177 | return results;
178 | } else {
179 | var terms = getTerms(processed);
180 | var termsLength = terms.length;
181 | var exactTermsLength = termsLength - 1;
182 | var increment = 1 / termsLength;
183 |
184 | exactOuter: for(var i = 0; i < exactTermsLength; i++) {
185 | var term = terms[i];
186 | var termLength = term.length - 1;
187 | var node = index;
188 |
189 | for(var j = 0; j <= termLength; j++) {
190 | var termOffset = node[0][0];
191 | var termIndex = term.charCodeAt(j) + termOffset;
192 |
193 | if(termIndex < 1 || (termOffset === undefined && j === termLength) || node[termIndex] === undefined) {
194 | continue exactOuter;
195 | }
196 |
197 | node = node[termIndex];
198 | }
199 |
200 | var nodeData = node[0];
201 | if(nodeData.length !== 1) {
202 | update(results, resultIndexes, increment, nodeData);
203 | }
204 | }
205 |
206 | var lastTerm = terms[exactTermsLength];
207 | var lastTermLength = lastTerm.length - 1;
208 | var node$1 = index;
209 |
210 | for(var i$1 = 0; i$1 <= lastTermLength; i$1++) {
211 | var lastTermOffset = node$1[0][0];
212 | var lastTermIndex = lastTerm.charCodeAt(i$1) + lastTermOffset;
213 |
214 | if(lastTermIndex < 1 || (lastTermOffset === undefined && i$1 === lastTermLength) || node$1[lastTermIndex] === undefined) {
215 | break;
216 | }
217 |
218 | node$1 = node$1[lastTermIndex];
219 | }
220 |
221 | if(node$1 !== undefined) {
222 | var nodes = [node$1];
223 | for(var i$2 = 0; i$2 < nodes.length; i$2++) {
224 | var childNode = nodes[i$2];
225 | var childNodeData = childNode[0];
226 |
227 | if(childNodeData.length !== 1) {
228 | update(results, resultIndexes, increment, childNodeData);
229 | }
230 |
231 | for(var j$1 = 1; j$1 < childNode.length; j$1++) {
232 | var grandChildNode = childNode[j$1];
233 | if(grandChildNode !== undefined) {
234 | nodes.push(grandChildNode);
235 | }
236 | }
237 | }
238 | }
239 |
240 | return results;
241 | }
242 | }
243 |
244 | if(Array.isArray(data)) {
245 | search.index = Wade.index(data);
246 | } else {
247 | search.index = parse(data);
248 | }
249 |
250 | return search;
251 | }
252 |
253 | Wade.index = function(data) {
254 | var dataLength = 0;
255 | var ranges = {};
256 | var processed = [];
257 |
258 | for(var i = 0; i < data.length; i++) {
259 | var entry = processEntry(data[i]);
260 |
261 | if(entry.length !== 0) {
262 | var terms = getTerms(entry);
263 | var termsLength = terms.length;
264 |
265 | for(var j = 0; j < termsLength; j++) {
266 | var term = terms[j];
267 | var processedTerm = [];
268 | var currentRanges = ranges;
269 |
270 | for(var n = 0; n < term.length; n++) {
271 | var char = term.charCodeAt(n);
272 | var highByte = char >>> 8;
273 | var lowByte = char & 0xFF;
274 |
275 | if(highByte !== 0) {
276 | if(currentRanges.minimum === undefined || highByte < currentRanges.minimum) {
277 | currentRanges.minimum = highByte;
278 | }
279 |
280 | if(currentRanges.maximum === undefined || highByte > currentRanges.maximum) {
281 | currentRanges.maximum = highByte;
282 | }
283 |
284 | var nextRanges = currentRanges[highByte];
285 | if(nextRanges === undefined) {
286 | currentRanges = currentRanges[highByte] = {};
287 | } else {
288 | currentRanges = nextRanges;
289 | }
290 |
291 | processedTerm.push(highByte);
292 | }
293 |
294 | if(currentRanges.minimum === undefined || lowByte < currentRanges.minimum) {
295 | currentRanges.minimum = lowByte;
296 | }
297 |
298 | if(currentRanges.maximum === undefined || lowByte > currentRanges.maximum) {
299 | currentRanges.maximum = lowByte;
300 | }
301 |
302 | var nextRanges$1 = currentRanges[lowByte];
303 | if(nextRanges$1 === undefined) {
304 | currentRanges = currentRanges[lowByte] = {};
305 | } else {
306 | currentRanges = nextRanges$1;
307 | }
308 |
309 | processedTerm.push(lowByte);
310 | }
311 |
312 | processed.push(i);
313 | processed.push(termsLength);
314 | processed.push(processedTerm);
315 | }
316 | }
317 |
318 | dataLength++;
319 | }
320 |
321 | var indexMinimum = ranges.minimum;
322 | var indexMaximum = ranges.maximum;
323 | var indexSize = 1;
324 | var indexOffset;
325 |
326 | if(indexMinimum !== undefined && indexMaximum !== undefined) {
327 | indexSize = indexMaximum - indexMinimum + 2;
328 | indexOffset = 1 - indexMinimum;
329 | }
330 |
331 | var nodeDataSets = [];
332 | var index = new Array(indexSize);
333 | index[0] = [indexOffset];
334 |
335 | for(var i$1 = 0; i$1 < processed.length; i$1 += 3) {
336 | var dataIndex = processed[i$1];
337 | var termsLength$1 = processed[i$1 + 1];
338 | var processedTerm$1 = processed[i$1 + 2];
339 | var processedTermLength = processedTerm$1.length - 1;
340 | var node = index;
341 | var termRanges = ranges;
342 |
343 | for(var j$1 = 0; j$1 < processedTermLength; j$1++) {
344 | var char$1 = processedTerm$1[j$1];
345 | var charIndex = char$1 + node[0][0];
346 | var termNode = node[charIndex];
347 | termRanges = termRanges[char$1];
348 |
349 | if(termNode === undefined) {
350 | var termMinimum = termRanges.minimum;
351 | var termMaximum = termRanges.maximum;
352 | termNode = node[charIndex] = new Array(termMaximum - termMinimum + 2);
353 | termNode[0] = [1 - termMinimum];
354 | }
355 |
356 | node = termNode;
357 | }
358 |
359 | var lastChar = processedTerm$1[processedTermLength];
360 | var lastCharIndex = lastChar + node[0][0]
361 | var lastTermNode = node[lastCharIndex];
362 | termRanges = termRanges[lastChar];
363 |
364 | if(lastTermNode === undefined) {
365 | var lastTermMinimum = termRanges.minimum;
366 | var lastTermMaximum = termRanges.maximum;
367 | var lastTermSize = 1;
368 | var lastTermOffset = (void 0);
369 |
370 | if(lastTermMinimum !== undefined && lastTermMaximum !== undefined) {
371 | lastTermSize = lastTermMaximum - lastTermMinimum + 2;
372 | lastTermOffset = 1 - lastTermMinimum;
373 | }
374 |
375 | lastTermNode = node[lastCharIndex] = new Array(lastTermSize);
376 | nodeDataSets.push(lastTermNode[0] = [lastTermOffset, 1 / termsLength$1, dataIndex]);
377 | } else {
378 | var nodeData = lastTermNode[0];
379 |
380 | if(nodeData.length === 1) {
381 | nodeData.push(1 / termsLength$1);
382 | nodeData.push(dataIndex);
383 | nodeDataSets.push(nodeData);
384 | } else {
385 | nodeData[1] += 1 / termsLength$1;
386 | nodeData.push(dataIndex);
387 | }
388 | }
389 | }
390 |
391 | for(var i$2 = 0; i$2 < nodeDataSets.length; i$2++) {
392 | var nodeData$1 = nodeDataSets[i$2];
393 | nodeData$1[1] = 1.5 - (nodeData$1[1] / dataLength);
394 | }
395 |
396 | return index;
397 | }
398 |
399 | Wade.save = function(search) {
400 | return stringify(search.index);
401 | }
402 |
403 | Wade.config = config;
404 |
405 | Wade.version = "0.3.3";
406 |
407 | return Wade;
408 | }));
409 |
--------------------------------------------------------------------------------
/dist/uFuzzy.d.ts:
--------------------------------------------------------------------------------
1 | declare class uFuzzy {
2 | constructor(opts?: uFuzzy.Options);
3 |
4 | /** search API composed of filter/info/sort, with a info/ranking threshold (1e3) and fast outOfOrder impl */
5 | search(
6 | haystack: string[],
7 | needle: string,
8 | /** limit how many terms will be permuted, default = 0; 5 will result in up to 5! (120) search iterations. be careful with this! */
9 | outOfOrder?: number,
10 | /** default = 1e3 */
11 | infoThresh?: number,
12 | preFiltered?: uFuzzy.HaystackIdxs | null
13 | ): uFuzzy.SearchResult;
14 |
15 | /** initial haystack filter, can accept idxs from previous prefix/typeahead match as optimization */
16 | filter(
17 | haystack: string[],
18 | needle: string,
19 | idxs?: uFuzzy.HaystackIdxs
20 | ): uFuzzy.HaystackIdxs | null;
21 |
22 | /** collects stats about pre-filtered matches, does additional filtering based on term boundary settings, finds highlight ranges */
23 | info(
24 | idxs: uFuzzy.HaystackIdxs,
25 | haystack: string[],
26 | needle: string
27 | ): uFuzzy.Info;
28 |
29 | /** performs final result sorting via Array.sort(), relying on Info */
30 | sort(
31 | info: uFuzzy.Info,
32 | haystack: string[],
33 | needle: string
34 | ): uFuzzy.InfoIdxOrder;
35 |
36 | /** utility for splitting needle into terms following defined interSplit/intraSplit opts. useful for out-of-order permutes */
37 | split(needle: string, keepCase?: boolean): uFuzzy.Terms;
38 |
39 | /** util for creating out-of-order permutations of a needle terms array */
40 | static permute(arr: unknown[]): unknown[][];
41 |
42 | /** util for replacing common diacritics/accents */
43 | static latinize(strings: T): T;
44 |
45 | /** util for highlighting matched substr parts of a result */
46 | static highlight(
47 | match: string,
48 | ranges: number[],
49 |
50 | mark?: (part: string, matched: boolean) => TMarkedPart,
51 | accum?: TAccum,
52 | append?: (accum: TAccum, part: TMarkedPart) => TAccum | undefined
53 | ): TAccum;
54 | }
55 |
56 | export default uFuzzy;
57 |
58 | declare namespace uFuzzy {
59 | /** needle's terms */
60 | export type Terms = string[];
61 |
62 | /** subset of idxs of a haystack array */
63 | export type HaystackIdxs = number[];
64 |
65 | /** sorted order in which info facets should be iterated */
66 | export type InfoIdxOrder = number[];
67 |
68 | export type AbortedResult = [null, null, null];
69 |
70 | export type FilteredResult = [uFuzzy.HaystackIdxs, null, null];
71 |
72 | export type RankedResult = [
73 | uFuzzy.HaystackIdxs,
74 | uFuzzy.Info,
75 | uFuzzy.InfoIdxOrder
76 | ];
77 |
78 | export type SearchResult = FilteredResult | RankedResult | AbortedResult;
79 |
80 | /** partial RegExp */
81 | type PartialRegExp = string;
82 |
83 | /** what should be considered acceptable term bounds */
84 | export const enum BoundMode {
85 | /** will match 'man' substr anywhere. e.g. tasmania */
86 | Any = 0,
87 | /** will match 'man' at whitespace, punct, case-change, and alpha-num boundaries. e.g. mantis, SuperMan, fooManBar, 0007man */
88 | Loose = 1,
89 | /** will match 'man' at whitespace, punct boundaries only. e.g. mega man, walk_man, man-made, foo.man.bar */
90 | Strict = 2,
91 | }
92 |
93 | export const enum IntraMode {
94 | /** allows any number of extra char insertions within a term, but all term chars must be present for a match */
95 | MultiInsert = 0,
96 | /** allows for a single-char substitution, transposition, insertion, or deletion within terms (excluding first and last chars) */
97 | SingleError = 1,
98 | }
99 |
100 | export type IntraSliceIdxs = [from: number, to: number];
101 |
102 | type CompareFn = (a: string, b: string) => number;
103 |
104 | export interface Options {
105 | // whether regexps use a /u unicode flag
106 | unicode?: boolean; // false
107 |
108 | /** @deprecated renamed to opts.alpha */
109 | letters?: PartialRegExp | null; // a-z
110 |
111 | // regexp character class [] of chars which should be treated as letters (case insensitive)
112 | alpha?: PartialRegExp | null; // a-z
113 |
114 | /** term segmentation & punct/whitespace merging */
115 | interSplit?: PartialRegExp; // '[^A-Za-z\\d']+'
116 | intraSplit?: PartialRegExp | null; // '[a-z][A-Z]'
117 |
118 | /** inter bounds that will be used to increase lft2/rgt2 info counters */
119 | interBound?: PartialRegExp | null; // '[^A-Za-z\\d]'
120 | /** intra bounds that will be used to increase lft1/rgt1 info counters */
121 | intraBound?: PartialRegExp | null; // '[A-Za-z][0-9]|[0-9][A-Za-z]|[a-z][A-Z]'
122 |
123 | /** inter-term modes, during .info() can discard matches when bounds conditions are not met */
124 | interLft?: BoundMode; // 0
125 | interRgt?: BoundMode; // 0
126 |
127 | /** allowance between terms */
128 | interChars?: PartialRegExp; // '.'
129 | interIns?: number; // Infinity
130 |
131 | /** allowance between chars within terms */
132 | intraChars?: PartialRegExp; // '[a-z\\d]'
133 | intraIns?: number; // 0
134 |
135 | /** contractions detection */
136 | intraContr?: PartialRegExp; // "'[a-z]{1,2}\\b"
137 |
138 | /** error tolerance mode within terms. will clamp intraIns to 1 when set to SingleError */
139 | intraMode?: IntraMode; // 0
140 |
141 | /** which part of each term should tolerate errors (when intraMode: 1) */
142 | intraSlice?: IntraSliceIdxs; // [1, Infinity]
143 |
144 | /** max substitutions (when intraMode: 1) */
145 | intraSub?: 0 | 1; // 0
146 | /** max transpositions (when intraMode: 1) */
147 | intraTrn?: 0 | 1; // 0
148 | /** max omissions/deletions (when intraMode: 1) */
149 | intraDel?: 0 | 1; // 0
150 |
151 | /** can dynamically adjust error tolerance rules per term in needle (when intraMode: 1) */
152 | intraRules?: (term: string) => {
153 | intraSlice?: IntraSliceIdxs;
154 | intraIns: 0 | 1;
155 | intraSub: 0 | 1;
156 | intraTrn: 0 | 1;
157 | intraDel: 0 | 1;
158 | };
159 |
160 | /** post-filters matches during .info() based on cmp of term in needle vs partial match */
161 | intraFilt?: (term: string, match: string, index: number) => boolean; // should this also accept WIP info?
162 |
163 | /** default: toLocaleUpperCase() */
164 | toUpper?: (str: string) => string;
165 |
166 | /** default: toLocaleLowerCase() */
167 | toLower?: (str: string) => string;
168 |
169 | /** final sorting cmp when all other match metrics are equal */
170 | compare?: CompareFn;
171 |
172 | sort?: (info: Info, haystack: string[], needle: string, compare?: CompareFn) => InfoIdxOrder;
173 | }
174 |
175 | export interface Info {
176 | /** matched idxs from haystack */
177 | idx: HaystackIdxs;
178 |
179 | /** match offsets */
180 | start: number[];
181 |
182 | /** number of left BoundMode.Strict term boundaries found */
183 | interLft2: number[];
184 | /** number of right BoundMode.Strict term boundaries found */
185 | interRgt2: number[];
186 | /** number of left BoundMode.Loose term boundaries found */
187 | interLft1: number[];
188 | /** number of right BoundMode.Loose term boundaries found */
189 | interRgt1: number[];
190 |
191 | /** total number of extra chars matched within all terms. higher = matched terms have more fuzz in them */
192 | intraIns: number[];
193 | /** total number of chars found in between matched terms. higher = terms are more sparse, have more fuzz in between them */
194 | interIns: number[];
195 |
196 | /** total number of matched contiguous chars (substrs but not necessarily full terms) */
197 | chars: number[];
198 |
199 | /** number of exactly-matched terms (intra = 0) where both lft and rgt landed on a BoundMode.Loose or BoundMode.Strict boundary */
200 | terms: number[];
201 |
202 | /** number of needle terms with case-sensitive partial matches */
203 | cases: number[];
204 |
205 | /** offset ranges within match for highlighting: [startIdx0, endIdx0, startIdx1, endIdx1,...] */
206 | ranges: number[][];
207 | }
208 | }
209 |
210 | export as namespace uFuzzy;
211 |
--------------------------------------------------------------------------------
/dist/uFuzzy.iife.min.js:
--------------------------------------------------------------------------------
1 | /*! https://github.com/leeoniya/uFuzzy (v1.0.18) */
2 | var uFuzzy=function(){"use strict";const e=(e,t)=>e>t?1:t>e?-1:0,t=1/0,l=e=>e.replace(/[.*+?^${}()|[\]\\]/g,"\\$&"),n="eexxaacctt",r=/\p{P}/gu,i=["en",{numeric:!0,sensitivity:"base"}],s=(e,t,l)=>e.replace("A-Z",t).replace("a-z",l),a={unicode:!1,alpha:null,interSplit:"[^A-Za-z\\d']+",intraSplit:"[a-z][A-Z]",interBound:"[^A-Za-z\\d]",intraBound:"[A-Za-z]\\d|\\d[A-Za-z]|[a-z][A-Z]",interLft:0,interRgt:0,interChars:".",interIns:t,intraChars:"[a-z\\d']",intraIns:null,intraContr:"'[a-z]{1,2}\\b",intraMode:0,intraSlice:[1,t],intraSub:null,intraTrn:null,intraDel:null,intraFilt:()=>!0,toUpper:e=>e.toLocaleUpperCase(),toLower:e=>e.toLocaleLowerCase(),compare:null,sort:(t,l,n,r=e)=>{let{idx:i,chars:s,terms:a,interLft2:u,interLft1:g,start:f,intraIns:c,interIns:h,cases:o}=t;return i.map(((e,t)=>t)).sort(((e,t)=>s[t]-s[e]||c[e]-c[t]||a[t]+u[t]+.5*g[t]-(a[e]+u[e]+.5*g[e])||h[e]-h[t]||f[e]-f[t]||o[t]-o[e]||r(l[i[e]],l[i[t]])))}},u=(e,l)=>0==l?"":1==l?e+"??":l==t?e+"*?":e+`{0,${l}}?`,g="(?:\\b|_)";function f(t){t=Object.assign({},a,t);let{unicode:f,interLft:c,interRgt:o,intraMode:p,intraSlice:d,intraIns:m,intraSub:x,intraTrn:b,intraDel:R,intraContr:A,intraSplit:I,interSplit:S,intraBound:y,interBound:z,intraChars:E,toUpper:L,toLower:k,compare:C}=t;m??=p,x??=p,b??=p,R??=p,C??="undefined"==typeof Intl?e:new Intl.Collator(...i).compare;let j=t.letters??t.alpha;if(null!=j){let e=L(j),t=k(j);S=s(S,e,t),I=s(I,e,t),z=s(z,e,t),y=s(y,e,t),E=s(E,e,t),A=s(A,e,t)}let Z=f?"u":"";const $='".+?"',w=RegExp($,"gi"+Z),M=RegExp(`(?:\\s+|^)-(?:${E}+|${$})`,"gi"+Z);let{intraRules:B}=t;null==B&&(B=e=>{let t=a.intraSlice,l=0,n=0,r=0,i=0;if(/[^\d]/.test(e)){let s=e.length;s>4?(t=d,l=m,n=x,r=b,i=R):3>s||(r=Math.min(b,1),4==s&&(l=Math.min(m,1)))}return{intraSlice:t,intraIns:l,intraSub:n,intraTrn:r,intraDel:i}});let D=!!I,T=RegExp(I,"g"+Z),U=RegExp(S,"g"+Z),F=RegExp("^"+S+"|"+S+"$","g"+Z),O=RegExp(A,"gi"+Z);const v=(e,t=!1)=>{let l=[];e=(e=e.replace(w,(e=>(l.push(e),n)))).replace(F,""),t||(e=k(e)),D&&(e=e.replace(T,(e=>e[0]+" "+e[1])));let r=0;return e.split(U).filter((e=>""!=e)).map((e=>e===n?l[r++]:e))},N=/[^\d]+|\d+/g,P=(e,n=0,r=!1)=>{let i=v(e);if(0==i.length)return[];let s,a=Array(i.length).fill("");if(i=i.map(((e,t)=>e.replace(O,(e=>(a[t]=e,""))))),1==p)s=i.map(((e,t)=>{if('"'===e[0])return l(e.slice(1,-1));let n="";for(let l of e.matchAll(N)){let e=l[0],{intraSlice:r,intraIns:i,intraSub:s,intraTrn:g,intraDel:f}=B(e);if(i+s+g+f==0)n+=e+a[t];else{let[l,c]=r,h=e.slice(0,l),o=e.slice(c),p=e.slice(l,c);1==i&&1==h.length&&h!=p[0]&&(h+="(?!"+h+")");let d=p.length,m=[e];if(s)for(let e=0;d>e;e++)m.push(h+p.slice(0,e)+E+p.slice(e+1)+o);if(g)for(let e=0;d-1>e;e++)p[e]!=p[e+1]&&m.push(h+p.slice(0,e)+p[e+1]+p[e]+p.slice(e+2)+o);if(f)for(let e=0;d>e;e++)m.push(h+p.slice(0,e+1)+"?"+p.slice(e+1)+o);if(i){let e=u(E,1);for(let t=0;d>t;t++)m.push(h+p.slice(0,t)+e+p.slice(t)+o)}n+="(?:"+m.join("|")+")"+a[t]}}return n}));else{let e=u(E,m);2==n&&m>0&&(e=")("+e+")("),s=i.map(((t,n)=>'"'===t[0]?l(t.slice(1,-1)):t.split("").map(((e,t,l)=>(1==m&&0==t&&l.length>1&&e!=l[t+1]&&(e+="(?!"+e+")"),e))).join(e)+a[n]))}let f=2==c?g:"",h=2==o?g:"",d=h+u(t.interChars,t.interIns)+f;return n>0?r?s=f+"("+s.join(")"+h+"|"+f+"(")+")"+h:(s="("+s.join(")("+d+")(")+")",s="(.??"+f+")"+s+"("+h+".*)"):(s=s.join(d),s=f+s+h),[RegExp(s,"i"+Z),i,a]},_=(e,t,l)=>{let[n]=P(t);if(null==n)return null;let r=[];if(null!=l)for(let t=0;l.length>t;t++){let i=l[t];n.test(e[i])&&r.push(i)}else for(let t=0;e.length>t;t++)n.test(e[t])&&r.push(t);return r};let q=!!y,G=RegExp(z,Z),H=RegExp(y,Z);const J=(e,l,n)=>{let[r,i,s]=P(n,1),a=v(n,!0),[u]=P(n,2),g=i.length,f=Array(g),h=Array(g);for(let e=0;g>e;e++){let t=i[e],l=a[e],n='"'==t[0]?t.slice(1,-1):t+s[e],r='"'==l[0]?l.slice(1,-1):l+s[e];f[e]=n,h[e]=r}let p=e.length,d=Array(p).fill(0),m={idx:Array(p),start:d.slice(),chars:d.slice(),cases:d.slice(),terms:d.slice(),interIns:d.slice(),intraIns:d.slice(),interLft2:d.slice(),interRgt2:d.slice(),interLft1:d.slice(),interRgt1:d.slice(),ranges:Array(p)},x=1==c||1==o,b=0;for(let n=0;e.length>n;n++){let i=l[e[n]],s=i.match(r),a=s.index+s[1].length,p=a,d=!1,R=0,A=0,I=0,S=0,y=0,z=0,E=0,L=0,C=0,j=[];for(let e=0,l=2;g>e;e++,l+=2){let n=k(s[l]),r=f[e],u=r.length,m=n.length,b=n==r;if(s[l]==h[e]&&E++,!b&&s[l+1].length>=u){let t=k(s[l+1]).indexOf(r);t>-1&&(j.push(p,m,t,u),p+=K(s,l,t,u),n=r,m=u,b=!0,0==e&&(a=p))}if(x||b){let t=p-1,g=p+m,f=!1,h=!1;if(-1==t||G.test(i[t]))b&&R++,f=!0;else{if(2==c){d=!0;break}if(q&&H.test(i[t]+i[t+1]))b&&A++,f=!0;else if(1==c){let t=s[l+1],g=p+m;if(t.length>=u){let c,h=0,o=!1,d=RegExp(r,"ig"+Z);for(;c=d.exec(t);){h=c.index;let e=g+h,t=e-1;if(-1==t||G.test(i[t])){R++,o=!0;break}if(H.test(i[t]+i[e])){A++,o=!0;break}}o&&(f=!0,j.push(p,m,h,u),p+=K(s,l,h,u),n=r,m=u,b=!0,0==e&&(a=p))}if(!f){d=!0;break}}}if(g==i.length||G.test(i[g]))b&&I++,h=!0;else{if(2==o){d=!0;break}if(q&&H.test(i[g-1]+i[g]))b&&S++,h=!0;else if(1==o){d=!0;break}}b&&(y+=u,f&&h&&z++)}if(m>u&&(C+=m-u),e>0&&(L+=s[l-1].length),!t.intraFilt(r,n,p)){d=!0;break}g-1>e&&(p+=m+s[l+1].length)}if(!d){m.idx[b]=e[n],m.interLft2[b]=R,m.interLft1[b]=A,m.interRgt2[b]=I,m.interRgt1[b]=S,m.chars[b]=y,m.terms[b]=z,m.cases[b]=E,m.interIns[b]=L,m.intraIns[b]=C,m.start[b]=a;let t=i.match(u),l=t.index+t[1].length,r=j.length,s=r>0?0:1/0,g=r-4;for(let e=2;t.length>e;)if(s>g||j[s]!=l)l+=t[e].length,e++;else{let n=j[s+1],r=j[s+2],i=j[s+3],a=e,u="";for(let e=0;n>e;a++)u+=t[a],e+=t[a].length;t.splice(e,a-e,u),l+=K(t,e,r,i),s+=4}l=t.index+t[1].length;let f=m.ranges[b]=[],c=l,h=l;for(let e=2;t.length>e;e++){let n=t[e].length;l+=n,e%2==0?h=l:n>0&&(f.push(c,h),c=h=l)}h>c&&f.push(c,h),b++}}if(e.length>b)for(let e in m)m[e]=m[e].slice(0,b);return m},K=(e,t,l,n)=>{let r=e[t]+e[t+1].slice(0,l);return e[t-1]+=r,e[t]=e[t+1].slice(l,l+n),e[t+1]=e[t+1].slice(l+n),r.length};return{search:(...e)=>((e,n,i,s=1e3,a)=>{i=i?!0===i?5:i:0;let u=null,g=null,f=[];n=n.replace(M,(e=>{let t=e.trim().slice(1);return t='"'===t[0]?l(t.slice(1,-1)):t.replace(r,""),""!=t&&f.push(t),""}));let c,o=v(n);if(f.length>0){if(c=RegExp(f.join("|"),"i"+Z),0==o.length){let t=[];for(let l=0;e.length>l;l++)c.test(e[l])||t.push(l);return[t,null,null]}}else if(0==o.length)return[null,null,null];if(i>0){let t=v(n);if(t.length>1){let l=t.slice().sort(((e,t)=>t.length-e.length));for(let t=0;l.length>t;t++){if(0==a?.length)return[[],null,null];a=_(e,l[t],a)}if(t.length>i)return[a,null,null];u=h(t).map((e=>e.join(" "))),g=[];let n=new Set;for(let t=0;u.length>t;t++)if(a.length>n.size){let l=a.filter((e=>!n.has(e))),r=_(e,u[t],l);for(let e=0;r.length>e;e++)n.add(r[e]);g.push(r)}else g.push([])}}null==u&&(u=[n],g=[a?.length>0?a:_(e,n)]);let p=null,d=null;if(f.length>0&&(g=g.map((t=>t.filter((t=>!c.test(e[t])))))),s>=g.reduce(((e,t)=>e+t.length),0)){p={},d=[];for(let l=0;g.length>l;l++){let n=g[l];if(null==n||0==n.length)continue;let r=u[l],i=J(n,e,r),s=t.sort(i,e,r,C);if(l>0)for(let e=0;s.length>e;e++)s[e]+=d.length;for(let e in i)p[e]=(p[e]??[]).concat(i[e]);d=d.concat(s)}}return[[].concat(...g),p,d]})(...e),split:v,filter:_,info:J,sort:t.sort}}const c=(()=>{let e={A:"ÁÀÃÂÄĄ",a:"áàãâäą",E:"ÉÈÊËĖ",e:"éèêëę",I:"ÍÌÎÏĮ",i:"íìîïį",O:"ÓÒÔÕÖ",o:"óòôõö",U:"ÚÙÛÜŪŲ",u:"úùûüūų",C:"ÇČĆ",c:"çčć",L:"Ł",l:"ł",N:"ÑŃ",n:"ñń",S:"ŠŚ",s:"šś",Z:"ŻŹ",z:"żź"},t=new Map,l="";for(let n in e)e[n].split("").forEach((e=>{l+=e,t.set(e,n)}));let n=RegExp(`[${l}]`,"g"),r=e=>t.get(e);return e=>{if("string"==typeof e)return e.replace(n,r);let t=Array(e.length);for(let l=0;e.length>l;l++)t[l]=e[l].replace(n,r);return t}})();function h(e){let t,l,n=(e=e.slice()).length,r=[e.slice()],i=Array(n).fill(0),s=1;for(;n>s;)s>i[s]?(t=s%2&&i[s],l=e[s],e[s]=e[t],e[t]=l,++i[s],s=1,r.push(e.slice())):(i[s]=0,++s);return r}const o=(e,t)=>t?`${e}`:e,p=(e,t)=>e+t;return f.latinize=c,f.permute=e=>h([...Array(e.length).keys()]).sort(((e,t)=>{for(let l=0;e.length>l;l++)if(e[l]!=t[l])return e[l]-t[l];return 0})).map((t=>t.map((t=>e[t])))),f.highlight=function(e,t,l=o,n="",r=p){n=r(n,l(e.substring(0,t[0]),!1))??n;for(let i=0;t.length>i;i+=2)n=r(n,l(e.substring(t[i],t[i+1]),!0))??n,t.length-3>i&&(n=r(n,l(e.substring(t[i+1],t[i+2]),!1))??n);return r(n,l(e.substring(t[t.length-1]),!1))??n},f}();
3 |
--------------------------------------------------------------------------------
/jsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "checkJs": true,
4 | "target": "ES2020"
5 | },
6 | "include": ["src/*", "dist/*"],
7 | "exclude": ["node_modules"]
8 | }
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@leeoniya/ufuzzy",
3 | "version": "1.0.18",
4 | "description": "A tiny, efficient fuzzy matcher that doesn't suck",
5 | "main": "./dist/uFuzzy.cjs",
6 | "module": "./dist/uFuzzy.mjs",
7 | "types": "./dist/uFuzzy.d.ts",
8 | "type": "module",
9 | "scripts": {
10 | "build": "rollup -c",
11 | "test": "echo \"Error: no test specified\" && exit 1"
12 | },
13 | "repository": {
14 | "type": "git",
15 | "url": "git+https://github.com/leeoniya/uFuzzy.git"
16 | },
17 | "files": [
18 | "package.json",
19 | "README.md",
20 | "LICENSE",
21 | "dist"
22 | ],
23 | "keywords": [
24 | "micro",
25 | "fast",
26 | "fuzzy",
27 | "match",
28 | "search",
29 | "filter",
30 | "hint",
31 | "autocomplete",
32 | "typeahead",
33 | "sort"
34 | ],
35 | "author": "Leon Sorokin ",
36 | "license": "MIT",
37 | "bugs": {
38 | "url": "https://github.com/leeoniya/uFuzzy/issues"
39 | },
40 | "homepage": "https://github.com/leeoniya/uFuzzy#readme",
41 | "devDependencies": {
42 | "@rollup/plugin-terser": "^0.4.4",
43 | "rollup": "^4.30.1"
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/rollup.config.js:
--------------------------------------------------------------------------------
1 | import * as fs from 'fs';
2 |
3 | import terser from '@rollup/plugin-terser';
4 |
5 | const pkg = JSON.parse(fs.readFileSync('./package.json', 'utf8'));
6 | const ver = "v" + pkg.version;
7 | const urlVer = "https://github.com/leeoniya/uFuzzy (" + ver + ")";
8 | const banner = [
9 | "/**",
10 | "* Copyright (c) " + new Date().getFullYear() + ", Leon Sorokin",
11 | "* All rights reserved. (MIT Licensed)",
12 | "*",
13 | "* uFuzzy.js (μFuzzy)",
14 | "* A tiny, efficient fuzzy matcher that doesn't suck",
15 | "* " + urlVer,
16 | "*/",
17 | "",
18 | ].join("\n");
19 |
20 | function bannerlessESM() {
21 | return {
22 | name: 'stripBanner',
23 | resolveId(importee) {
24 | if (importee == 'uFuzzy')
25 | return importee;
26 | return null;
27 | },
28 | load(id) {
29 | if (id == 'uFuzzy')
30 | return fs.readFileSync('./dist/uFuzzy.mjs', 'utf8').replace(/\/\*\*.*?\*\//gms, '');
31 | return null;
32 | }
33 | };
34 | }
35 |
36 | const terserOpts = {
37 | compress: {
38 | inline: 0,
39 | passes: 2,
40 | keep_fargs: false,
41 | pure_getters: true,
42 | unsafe: true,
43 | unsafe_comps: true,
44 | unsafe_math: true,
45 | unsafe_undefined: true,
46 | },
47 | output: {
48 | comments: /^!/
49 | }
50 | };
51 |
52 | export default [
53 | {
54 | input: './src/uFuzzy.mjs',
55 | output: {
56 | name: 'uFuzzy',
57 | file: './dist/uFuzzy.mjs',
58 | format: 'es',
59 | banner,
60 | },
61 | },
62 | {
63 | input: './src/uFuzzy.mjs',
64 | output: {
65 | name: 'uFuzzy',
66 | file: './dist/uFuzzy.cjs',
67 | format: 'cjs',
68 | exports: "auto",
69 | banner,
70 | },
71 | },
72 | {
73 | input: 'uFuzzy',
74 | output: {
75 | name: 'uFuzzy',
76 | file: './dist/uFuzzy.iife.js',
77 | format: 'iife',
78 | esModule: false,
79 | banner,
80 | },
81 | plugins: [
82 | bannerlessESM(),
83 | ]
84 | },
85 | {
86 | input: 'uFuzzy',
87 | output: {
88 | name: 'uFuzzy',
89 | file: './dist/uFuzzy.iife.min.js',
90 | format: 'iife',
91 | esModule: false,
92 | banner: "/*! " + urlVer + " */",
93 | },
94 | plugins: [
95 | bannerlessESM(),
96 | terser(terserOpts),
97 | ]
98 | },
99 | ];
100 |
--------------------------------------------------------------------------------
/uFuzzy.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/leeoniya/uFuzzy/0c8bc57d1c1150307f1a61ce27c4b843bf9a0ee3/uFuzzy.png
--------------------------------------------------------------------------------