|
1 | 1 | /** |
2 | | - * Powered by Fuzzy |
3 | | - * https://github.com/myork/fuzzy |
4 | | - * |
5 | | - * Copyright (c) 2012 Matt York |
6 | | - * Licensed under the MIT license. |
7 | | - * |
8 | | - * @description: A standalone fuzzy search / fuzzy filter. provide inquirer usage |
| 2 | + * @description: provide list and checkBox fuzzy search |
9 | 3 | * @author: @Zhengqbbb (zhengqbbb@gmail.com) |
10 | 4 | * @license: MIT |
11 | 5 | */ |
12 | 6 |
|
13 | | -import { FilterFucType, MatchOptions, MatchResult } from "../types"; |
14 | | - |
15 | 7 | /** |
16 | | - * @description: If `pattern` matches `inputString`, wrap each matching character in `opts.pre` |
17 | | - * and `opts.post`. If no match, return null. |
18 | | - * @param {string} pattern inputString |
19 | | - * @param {string} str targetString |
| 8 | + * @description: inputString match targetString return match score |
| 9 | + * @param {string} input input string |
| 10 | + * @param {string} target target string |
| 11 | + * @param {boolean} caseSensitive isCaseSensitive, default: false |
| 12 | + * @return {number | null} match score. if not match return null |
20 | 13 | */ |
21 | 14 | export const fuzzyMatch = ( |
22 | | - pattern: string, |
23 | | - str: string, |
24 | | - opts?: MatchOptions |
25 | | -): MatchResult | null => { |
26 | | - opts = opts || {}; |
27 | | - const result = []; |
28 | | - const len = str.length; |
29 | | - // prefix |
30 | | - const pre = opts.pre || ""; |
31 | | - // suffix |
32 | | - const post = opts.post || ""; |
33 | | - // String to compare against. This might be a lowercase version of the |
34 | | - // raw string |
35 | | - const compareString = (opts.caseSensitive && str) || str.toLowerCase(); |
36 | | - let patternIdx = 0, |
| 15 | + input: string, |
| 16 | + target: string, |
| 17 | + caseSensitive?: boolean |
| 18 | +): number | null => { |
| 19 | + if (typeof input !== "string" || typeof target !== "string") return null; |
| 20 | + const matchResult = []; |
| 21 | + const len = target.length; |
| 22 | + const shimTarget = (caseSensitive && target) || target.toLowerCase(); |
| 23 | + input = (caseSensitive && input) || input.toLowerCase(); |
| 24 | + let inputIndex = 0, |
37 | 25 | totalScore = 0, |
38 | | - currScore = 0, |
39 | | - ch; |
40 | | - |
41 | | - pattern = (opts.caseSensitive && pattern) || pattern.toLowerCase(); |
42 | | - |
43 | | - // For each character in the string, either add it to the result |
44 | | - // or wrap in template if it's the next string in the pattern |
| 26 | + currentScore = 0, |
| 27 | + currentChar; |
45 | 28 | for (let idx = 0; idx < len; idx++) { |
46 | | - ch = str[idx]; |
47 | | - if (compareString[idx] === pattern[patternIdx]) { |
48 | | - ch = pre + ch + post; |
49 | | - patternIdx += 1; |
50 | | - |
51 | | - // consecutive characters should increase the score more than linearly |
52 | | - currScore += 1 + currScore; |
| 29 | + currentChar = input[idx]; |
| 30 | + if (shimTarget[idx] === input[inputIndex]) { |
| 31 | + // consecutive matches will score higher |
| 32 | + inputIndex += 1; |
| 33 | + currentScore += 1 + currentScore; |
53 | 34 | } else { |
54 | | - currScore = 0; |
| 35 | + currentScore = 0; |
55 | 36 | } |
56 | | - totalScore += currScore; |
57 | | - result[result.length] = ch; |
| 37 | + totalScore += currentScore; |
| 38 | + matchResult[matchResult.length] = currentChar; |
58 | 39 | } |
59 | | - |
60 | | - // return rendered string if we have a match for every char |
61 | | - if (patternIdx === pattern.length) { |
62 | | - // if the string is an exact match with pattern, totalScore should be maxed |
63 | | - totalScore = compareString === pattern ? Infinity : totalScore; |
64 | | - return { rendered: result.join(""), score: totalScore }; |
| 40 | + if (inputIndex === input.length) { |
| 41 | + totalScore = shimTarget === input ? Infinity : totalScore; |
| 42 | + return totalScore; |
65 | 43 | } |
66 | 44 |
|
67 | 45 | return null; |
68 | 46 | }; |
69 | | - |
70 | | -/** |
71 | | - * @description: Does `pattern` fuzzy match `inputString`? |
72 | | - * @param {string} pattern inputString |
73 | | - * @param {string} str targetString |
74 | | - * @return {boolean} isMatch |
75 | | - */ |
76 | | -export const fuzzyTest = (pattern: string, str: string): boolean => { |
77 | | - return fuzzyMatch(pattern, str) !== null; |
78 | | -}; |
79 | | - |
80 | | -/** |
81 | | - * @description: The normal entry point. Filters `arr` for matches against `pattern`. |
82 | | - * @param {*} pattern inputString |
83 | | - * @param {*} arr targetArray |
84 | | - * @param {*} opts FilterOptions |
85 | | - */ |
86 | | -export const fuzzyFilter: FilterFucType<any> = (pattern, arr, opts?) => { |
87 | | - if (!arr || arr.length === 0) { |
88 | | - return []; |
89 | | - } |
90 | | - if (typeof pattern !== "string") { |
91 | | - return arr; |
92 | | - } |
93 | | - opts = opts || {}; |
94 | | - return arr |
95 | | - .reduce(function (prev, element, idx) { |
96 | | - let str = element; |
97 | | - if (opts?.extract) { |
98 | | - str = opts?.extract(element); |
99 | | - } |
100 | | - const rendered = fuzzyMatch(pattern, str, opts); |
101 | | - if (rendered != null) { |
102 | | - prev[prev.length] = { |
103 | | - string: rendered.rendered, |
104 | | - score: rendered.score, |
105 | | - index: idx, |
106 | | - original: element |
107 | | - }; |
108 | | - } |
109 | | - return prev; |
110 | | - }, []) |
111 | | - .sort(function (a: any, b: any) { |
112 | | - const compare = b.score - a.score; |
113 | | - if (compare) return compare; |
114 | | - return a.index - b.index; |
115 | | - }); |
116 | | -}; |
117 | | - |
118 | | -/** |
119 | | - * @description: Return all elements of `array` that have a fuzzy match against `pattern`. |
120 | | - * @param {string} pattern inputString |
121 | | - * @param {Array<string>} array targetArray |
122 | | - */ |
123 | | -export const fuzzySimpleFilter = (pattern: string, array: string[]): string[] => { |
124 | | - return array.filter(function (str) { |
125 | | - return fuzzyTest(pattern, str); |
126 | | - }); |
127 | | -}; |
0 commit comments