@nexucis/fuzzy
Advanced tools
+1
| v18.16.0 |
+250
| "use strict"; | ||
| // MIT License | ||
| // | ||
| // Copyright (c) 2020 Augustin Husson | ||
| // | ||
| // Permission is hereby granted, free of charge, to any person obtaining a copy | ||
| // of this software and associated documentation files (the "Software"), to deal | ||
| // in the Software without restriction, including without limitation the rights | ||
| // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | ||
| // copies of the Software, and to permit persons to whom the Software is | ||
| // furnished to do so, subject to the following conditions: | ||
| // | ||
| // The above copyright notice and this permission notice shall be included in all | ||
| // copies or substantial portions of the Software. | ||
| // | ||
| // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | ||
| // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | ||
| // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | ||
| // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | ||
| // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | ||
| // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE | ||
| // SOFTWARE. | ||
| Object.defineProperty(exports, "__esModule", { value: true }); | ||
| exports.Fuzzy = void 0; | ||
| exports.score = score; | ||
| exports.filter = filter; | ||
| exports.match = match; | ||
| exports.render = render; | ||
| function escapeHTML(text) { | ||
| return text.replace(/[&<>"']/g, (m) => { | ||
| switch (m) { | ||
| case '&': | ||
| return '&'; | ||
| case '<': | ||
| return '<'; | ||
| case '>': | ||
| return '>'; | ||
| case '"': | ||
| return '"'; | ||
| default: | ||
| return '''; | ||
| } | ||
| }); | ||
| } | ||
| function intervalSize(interval) { | ||
| return interval.to - interval.from + 1; | ||
| } | ||
| function calculatePreviousNotMatchingInterval(intervals, idx) { | ||
| const currentInterval = intervals[idx]; | ||
| let previousNotMatchingInterval = null; | ||
| if (idx === 0 && currentInterval.from !== 0) { | ||
| previousNotMatchingInterval = { from: 0, to: currentInterval.from - 1 }; | ||
| } | ||
| if (idx > 0) { | ||
| previousNotMatchingInterval = { from: intervals[idx - 1].to + 1, to: currentInterval.from - 1 }; | ||
| } | ||
| return previousNotMatchingInterval; | ||
| } | ||
| // score should be used to calculate the score based on the intervals created during the matching step. | ||
| // Here is how the score is determinate: | ||
| // 1. Consecutive characters should increase the score more than linearly | ||
| // 2. More there is a distance between the characters, higher it reduces the score. | ||
| // For example, for the pattern 'abc', the following strings are sorted by the highest score | ||
| // abcdef > defabc > abec > defabec | ||
| // Note: this function is exported only for testing purpose. | ||
| function score(intervals, strLength) { | ||
| let result = 0; | ||
| for (let i = 0; i < intervals.length; i++) { | ||
| const currentInterval = intervals[i]; | ||
| const previousNotMatchingInterval = calculatePreviousNotMatchingInterval(intervals, i); | ||
| if (previousNotMatchingInterval !== null) { | ||
| result = result - intervalSize(previousNotMatchingInterval) / strLength; | ||
| } | ||
| result = result + intervalSize(currentInterval) ** 2; | ||
| } | ||
| return result; | ||
| } | ||
| class Fuzzy { | ||
| constructor(conf) { | ||
| this.conf = { | ||
| caseSensitive: (conf === null || conf === void 0 ? void 0 : conf.caseSensitive) === undefined ? false : conf.caseSensitive, | ||
| excludedChars: (conf === null || conf === void 0 ? void 0 : conf.excludedChars) === undefined ? [] : conf.excludedChars, | ||
| includeMatches: (conf === null || conf === void 0 ? void 0 : conf.includeMatches) === undefined ? false : conf.includeMatches, | ||
| shouldSort: (conf === null || conf === void 0 ? void 0 : conf.shouldSort) === undefined ? false : conf.shouldSort, | ||
| shouldRender: (conf === null || conf === void 0 ? void 0 : conf.shouldRender) === undefined ? true : conf.shouldRender, | ||
| escapeHTML: (conf === null || conf === void 0 ? void 0 : conf.escapeHTML) === undefined ? false : conf.escapeHTML, | ||
| pre: (conf === null || conf === void 0 ? void 0 : conf.pre) === undefined ? '' : conf.pre, | ||
| post: (conf === null || conf === void 0 ? void 0 : conf.post) === undefined ? '' : conf.post, | ||
| }; | ||
| } | ||
| // filter is the method to use to filter a string list | ||
| // list of result return can be sort if parameter `shouldSort` is set. | ||
| filter(pattern, list, conf) { | ||
| const shouldSort = (conf === null || conf === void 0 ? void 0 : conf.shouldSort) !== undefined ? conf.shouldSort : this.conf.shouldSort; | ||
| let result = []; | ||
| for (let i = 0; i < list.length; i++) { | ||
| const matchedText = this.match(pattern, list[i], conf); | ||
| if (matchedText !== null) { | ||
| matchedText.index = i; | ||
| result.push(matchedText); | ||
| } | ||
| } | ||
| if (shouldSort) { | ||
| result = result.sort((a, b) => { | ||
| return b.score - a.score; | ||
| }); | ||
| } | ||
| return result; | ||
| } | ||
| // match will return a result if `pattern` is matching `text`, | ||
| match(pattern, text, conf) { | ||
| let localPattern = pattern; | ||
| let localText = text; | ||
| const caseSensitive = (conf === null || conf === void 0 ? void 0 : conf.caseSensitive) !== undefined ? conf.caseSensitive : this.conf.caseSensitive; | ||
| const includeMatches = (conf === null || conf === void 0 ? void 0 : conf.includeMatches) !== undefined ? conf.includeMatches : this.conf.includeMatches; | ||
| const shouldRender = (conf === null || conf === void 0 ? void 0 : conf.shouldRender) !== undefined ? conf.shouldRender : this.conf.shouldRender; | ||
| if (!caseSensitive) { | ||
| localPattern = localPattern.toLowerCase(); | ||
| localText = localText.toLowerCase(); | ||
| } | ||
| // in case it's a perfect match, no need to loop to find which char is matching | ||
| if (localPattern === localText) { | ||
| const intervals = [{ from: 0, to: pattern.length - 1 }]; | ||
| const result = { | ||
| original: text, | ||
| rendered: shouldRender ? this.render(text, intervals, conf) : text, | ||
| score: Infinity, | ||
| }; | ||
| if (includeMatches) { | ||
| result.intervals = intervals; | ||
| } | ||
| return result; | ||
| } | ||
| // otherwise, let's calculate the different indices that will then be used to calculate the score | ||
| let intervals = []; | ||
| let score = 0; | ||
| for (let i = 0; i < localText.length - localPattern.length + 1; i++) { | ||
| // Each time a char is matching the first char of the pattern | ||
| // loop other the rest of the text to generate the different matching interval. | ||
| // Like that, we will be able to find the best matching possibility. | ||
| // For example, given the pattern `bac` and the text `babac` | ||
| // instead of matching `<ba>ba<c>, it will match ba<bac> which has a better score than the previous one. | ||
| if (localText[i] === localPattern[0]) { | ||
| const matchingResult = this.generateMatchingInterval(localPattern, localText, i, conf); | ||
| if (matchingResult === null) { | ||
| break; | ||
| } | ||
| if (matchingResult.score > score) { | ||
| score = matchingResult.score; | ||
| intervals = matchingResult.intervals; | ||
| } | ||
| } | ||
| } | ||
| if (intervals.length === 0) { | ||
| return null; | ||
| } | ||
| const result = { | ||
| original: text, | ||
| rendered: shouldRender ? this.render(text, intervals, conf) : text, | ||
| score: score, | ||
| }; | ||
| if (includeMatches) { | ||
| result.intervals = intervals; | ||
| } | ||
| return result; | ||
| } | ||
| // render will modify the text according to the different parameter set in the conf. | ||
| // If nothing is set, then it will return the text not modified. | ||
| render(text, intervals, conf) { | ||
| if (intervals.length == 0) { | ||
| return text; | ||
| } | ||
| let rendered = ''; | ||
| const pre = (conf === null || conf === void 0 ? void 0 : conf.pre) ? conf.pre : this.conf.pre; | ||
| const post = (conf === null || conf === void 0 ? void 0 : conf.post) ? conf.post : this.conf.post; | ||
| for (let i = 0; i < intervals.length; i++) { | ||
| const currentInterval = intervals[i]; | ||
| const previousNotMatchingInterval = calculatePreviousNotMatchingInterval(intervals, i); | ||
| let previousStr = ''; | ||
| if (previousNotMatchingInterval !== null) { | ||
| previousStr = this.extractSubString(text, previousNotMatchingInterval, conf); | ||
| } | ||
| const currentStr = this.extractSubString(text, currentInterval, conf); | ||
| rendered = `${rendered}${previousStr}${pre}${currentStr}${post}`; | ||
| } | ||
| // check if the last interval contains the end of the string. Otherwise, add it | ||
| const lastInterval = intervals[intervals.length - 1]; | ||
| if (lastInterval.to < text.length - 1) { | ||
| rendered = rendered + this.extractSubString(text, { from: lastInterval.to + 1, to: text.length }, conf); | ||
| } | ||
| return rendered; | ||
| } | ||
| extractSubString(text, interval, conf) { | ||
| const shouldEscape = (conf === null || conf === void 0 ? void 0 : conf.escapeHTML) !== undefined ? conf.escapeHTML : this.conf.escapeHTML; | ||
| let str = text.substr(interval.from, intervalSize(interval)); | ||
| if (shouldEscape) { | ||
| str = escapeHTML(str); | ||
| } | ||
| return str; | ||
| } | ||
| // generateMatchingInterval will iterate other the given text to find the different char that matched the given pattern | ||
| generateMatchingInterval(pattern, text, idxText, conf) { | ||
| let excludedChars = []; | ||
| if ((conf === null || conf === void 0 ? void 0 : conf.excludedChars) !== undefined) { | ||
| excludedChars = conf.excludedChars; | ||
| } | ||
| else if (this.conf.excludedChars !== undefined) { | ||
| excludedChars = this.conf.excludedChars; | ||
| } | ||
| let patternIdx = 0; | ||
| const intervals = []; | ||
| for (let i = idxText; i < text.length && patternIdx < pattern.length;) { | ||
| if (excludedChars.includes(text[i])) { | ||
| i++; | ||
| continue; | ||
| } | ||
| if (excludedChars.includes(pattern[patternIdx])) { | ||
| patternIdx++; | ||
| continue; | ||
| } | ||
| if (text[i] === pattern[patternIdx]) { | ||
| const interval = { from: i, to: i }; | ||
| patternIdx++; | ||
| i++; | ||
| for (let j = i; j < text.length && patternIdx < pattern.length && text[j] === pattern[patternIdx]; j++) { | ||
| interval.to = j; | ||
| patternIdx++; | ||
| i = j; | ||
| } | ||
| intervals.push(interval); | ||
| } | ||
| i++; | ||
| } | ||
| if (intervals.length === 0 || patternIdx !== pattern.length) { | ||
| return null; | ||
| } | ||
| return { score: score(intervals, text.length), intervals: intervals }; | ||
| } | ||
| } | ||
| exports.Fuzzy = Fuzzy; | ||
| const fuz = new Fuzzy(); | ||
| function filter(pattern, list, conf) { | ||
| return fuz.filter(pattern, list, conf); | ||
| } | ||
| function match(pattern, text, conf) { | ||
| return fuz.match(pattern, text, conf); | ||
| } | ||
| function render(text, intervals, conf) { | ||
| return fuz.render(text, intervals, conf); | ||
| } |
+34
| export declare function score(intervals: FuzzyMatchingInterval[], strLength: number): number; | ||
| export interface FuzzyConfiguration { | ||
| caseSensitive?: boolean; | ||
| excludedChars?: string[]; | ||
| includeMatches?: boolean; | ||
| shouldSort?: boolean; | ||
| shouldRender?: boolean; | ||
| escapeHTML?: boolean; | ||
| pre?: string; | ||
| post?: string; | ||
| } | ||
| export interface FuzzyMatchingInterval { | ||
| from: number; | ||
| to: number; | ||
| } | ||
| export interface FuzzyResult { | ||
| rendered: string; | ||
| original: string; | ||
| index: number; | ||
| score: number; | ||
| intervals?: FuzzyMatchingInterval[]; | ||
| } | ||
| export declare class Fuzzy { | ||
| private readonly conf; | ||
| constructor(conf?: FuzzyConfiguration); | ||
| filter(pattern: string, list: string[], conf?: FuzzyConfiguration): FuzzyResult[]; | ||
| match(pattern: string, text: string, conf?: FuzzyConfiguration): FuzzyResult | null; | ||
| render(text: string, intervals: FuzzyMatchingInterval[], conf?: FuzzyConfiguration): string; | ||
| private extractSubString; | ||
| private generateMatchingInterval; | ||
| } | ||
| export declare function filter(pattern: string, list: string[], conf?: FuzzyConfiguration): FuzzyResult[]; | ||
| export declare function match(pattern: string, text: string, conf?: FuzzyConfiguration): FuzzyResult | null; | ||
| export declare function render(text: string, intervals: FuzzyMatchingInterval[], conf?: FuzzyConfiguration): string; |
+242
| // MIT License | ||
| // | ||
| // Copyright (c) 2020 Augustin Husson | ||
| // | ||
| // Permission is hereby granted, free of charge, to any person obtaining a copy | ||
| // of this software and associated documentation files (the "Software"), to deal | ||
| // in the Software without restriction, including without limitation the rights | ||
| // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | ||
| // copies of the Software, and to permit persons to whom the Software is | ||
| // furnished to do so, subject to the following conditions: | ||
| // | ||
| // The above copyright notice and this permission notice shall be included in all | ||
| // copies or substantial portions of the Software. | ||
| // | ||
| // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | ||
| // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | ||
| // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | ||
| // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | ||
| // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | ||
| // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE | ||
| // SOFTWARE. | ||
| function escapeHTML(text) { | ||
| return text.replace(/[&<>"']/g, (m) => { | ||
| switch (m) { | ||
| case '&': | ||
| return '&'; | ||
| case '<': | ||
| return '<'; | ||
| case '>': | ||
| return '>'; | ||
| case '"': | ||
| return '"'; | ||
| default: | ||
| return '''; | ||
| } | ||
| }); | ||
| } | ||
| function intervalSize(interval) { | ||
| return interval.to - interval.from + 1; | ||
| } | ||
| function calculatePreviousNotMatchingInterval(intervals, idx) { | ||
| const currentInterval = intervals[idx]; | ||
| let previousNotMatchingInterval = null; | ||
| if (idx === 0 && currentInterval.from !== 0) { | ||
| previousNotMatchingInterval = { from: 0, to: currentInterval.from - 1 }; | ||
| } | ||
| if (idx > 0) { | ||
| previousNotMatchingInterval = { from: intervals[idx - 1].to + 1, to: currentInterval.from - 1 }; | ||
| } | ||
| return previousNotMatchingInterval; | ||
| } | ||
| // score should be used to calculate the score based on the intervals created during the matching step. | ||
| // Here is how the score is determinate: | ||
| // 1. Consecutive characters should increase the score more than linearly | ||
| // 2. More there is a distance between the characters, higher it reduces the score. | ||
| // For example, for the pattern 'abc', the following strings are sorted by the highest score | ||
| // abcdef > defabc > abec > defabec | ||
| // Note: this function is exported only for testing purpose. | ||
| export function score(intervals, strLength) { | ||
| let result = 0; | ||
| for (let i = 0; i < intervals.length; i++) { | ||
| const currentInterval = intervals[i]; | ||
| const previousNotMatchingInterval = calculatePreviousNotMatchingInterval(intervals, i); | ||
| if (previousNotMatchingInterval !== null) { | ||
| result = result - intervalSize(previousNotMatchingInterval) / strLength; | ||
| } | ||
| result = result + intervalSize(currentInterval) ** 2; | ||
| } | ||
| return result; | ||
| } | ||
| export class Fuzzy { | ||
| constructor(conf) { | ||
| this.conf = { | ||
| caseSensitive: (conf === null || conf === void 0 ? void 0 : conf.caseSensitive) === undefined ? false : conf.caseSensitive, | ||
| excludedChars: (conf === null || conf === void 0 ? void 0 : conf.excludedChars) === undefined ? [] : conf.excludedChars, | ||
| includeMatches: (conf === null || conf === void 0 ? void 0 : conf.includeMatches) === undefined ? false : conf.includeMatches, | ||
| shouldSort: (conf === null || conf === void 0 ? void 0 : conf.shouldSort) === undefined ? false : conf.shouldSort, | ||
| shouldRender: (conf === null || conf === void 0 ? void 0 : conf.shouldRender) === undefined ? true : conf.shouldRender, | ||
| escapeHTML: (conf === null || conf === void 0 ? void 0 : conf.escapeHTML) === undefined ? false : conf.escapeHTML, | ||
| pre: (conf === null || conf === void 0 ? void 0 : conf.pre) === undefined ? '' : conf.pre, | ||
| post: (conf === null || conf === void 0 ? void 0 : conf.post) === undefined ? '' : conf.post, | ||
| }; | ||
| } | ||
| // filter is the method to use to filter a string list | ||
| // list of result return can be sort if parameter `shouldSort` is set. | ||
| filter(pattern, list, conf) { | ||
| const shouldSort = (conf === null || conf === void 0 ? void 0 : conf.shouldSort) !== undefined ? conf.shouldSort : this.conf.shouldSort; | ||
| let result = []; | ||
| for (let i = 0; i < list.length; i++) { | ||
| const matchedText = this.match(pattern, list[i], conf); | ||
| if (matchedText !== null) { | ||
| matchedText.index = i; | ||
| result.push(matchedText); | ||
| } | ||
| } | ||
| if (shouldSort) { | ||
| result = result.sort((a, b) => { | ||
| return b.score - a.score; | ||
| }); | ||
| } | ||
| return result; | ||
| } | ||
| // match will return a result if `pattern` is matching `text`, | ||
| match(pattern, text, conf) { | ||
| let localPattern = pattern; | ||
| let localText = text; | ||
| const caseSensitive = (conf === null || conf === void 0 ? void 0 : conf.caseSensitive) !== undefined ? conf.caseSensitive : this.conf.caseSensitive; | ||
| const includeMatches = (conf === null || conf === void 0 ? void 0 : conf.includeMatches) !== undefined ? conf.includeMatches : this.conf.includeMatches; | ||
| const shouldRender = (conf === null || conf === void 0 ? void 0 : conf.shouldRender) !== undefined ? conf.shouldRender : this.conf.shouldRender; | ||
| if (!caseSensitive) { | ||
| localPattern = localPattern.toLowerCase(); | ||
| localText = localText.toLowerCase(); | ||
| } | ||
| // in case it's a perfect match, no need to loop to find which char is matching | ||
| if (localPattern === localText) { | ||
| const intervals = [{ from: 0, to: pattern.length - 1 }]; | ||
| const result = { | ||
| original: text, | ||
| rendered: shouldRender ? this.render(text, intervals, conf) : text, | ||
| score: Infinity, | ||
| }; | ||
| if (includeMatches) { | ||
| result.intervals = intervals; | ||
| } | ||
| return result; | ||
| } | ||
| // otherwise, let's calculate the different indices that will then be used to calculate the score | ||
| let intervals = []; | ||
| let score = 0; | ||
| for (let i = 0; i < localText.length - localPattern.length + 1; i++) { | ||
| // Each time a char is matching the first char of the pattern | ||
| // loop other the rest of the text to generate the different matching interval. | ||
| // Like that, we will be able to find the best matching possibility. | ||
| // For example, given the pattern `bac` and the text `babac` | ||
| // instead of matching `<ba>ba<c>, it will match ba<bac> which has a better score than the previous one. | ||
| if (localText[i] === localPattern[0]) { | ||
| const matchingResult = this.generateMatchingInterval(localPattern, localText, i, conf); | ||
| if (matchingResult === null) { | ||
| break; | ||
| } | ||
| if (matchingResult.score > score) { | ||
| score = matchingResult.score; | ||
| intervals = matchingResult.intervals; | ||
| } | ||
| } | ||
| } | ||
| if (intervals.length === 0) { | ||
| return null; | ||
| } | ||
| const result = { | ||
| original: text, | ||
| rendered: shouldRender ? this.render(text, intervals, conf) : text, | ||
| score: score, | ||
| }; | ||
| if (includeMatches) { | ||
| result.intervals = intervals; | ||
| } | ||
| return result; | ||
| } | ||
| // render will modify the text according to the different parameter set in the conf. | ||
| // If nothing is set, then it will return the text not modified. | ||
| render(text, intervals, conf) { | ||
| if (intervals.length == 0) { | ||
| return text; | ||
| } | ||
| let rendered = ''; | ||
| const pre = (conf === null || conf === void 0 ? void 0 : conf.pre) ? conf.pre : this.conf.pre; | ||
| const post = (conf === null || conf === void 0 ? void 0 : conf.post) ? conf.post : this.conf.post; | ||
| for (let i = 0; i < intervals.length; i++) { | ||
| const currentInterval = intervals[i]; | ||
| const previousNotMatchingInterval = calculatePreviousNotMatchingInterval(intervals, i); | ||
| let previousStr = ''; | ||
| if (previousNotMatchingInterval !== null) { | ||
| previousStr = this.extractSubString(text, previousNotMatchingInterval, conf); | ||
| } | ||
| const currentStr = this.extractSubString(text, currentInterval, conf); | ||
| rendered = `${rendered}${previousStr}${pre}${currentStr}${post}`; | ||
| } | ||
| // check if the last interval contains the end of the string. Otherwise, add it | ||
| const lastInterval = intervals[intervals.length - 1]; | ||
| if (lastInterval.to < text.length - 1) { | ||
| rendered = rendered + this.extractSubString(text, { from: lastInterval.to + 1, to: text.length }, conf); | ||
| } | ||
| return rendered; | ||
| } | ||
| extractSubString(text, interval, conf) { | ||
| const shouldEscape = (conf === null || conf === void 0 ? void 0 : conf.escapeHTML) !== undefined ? conf.escapeHTML : this.conf.escapeHTML; | ||
| let str = text.substr(interval.from, intervalSize(interval)); | ||
| if (shouldEscape) { | ||
| str = escapeHTML(str); | ||
| } | ||
| return str; | ||
| } | ||
| // generateMatchingInterval will iterate other the given text to find the different char that matched the given pattern | ||
| generateMatchingInterval(pattern, text, idxText, conf) { | ||
| let excludedChars = []; | ||
| if ((conf === null || conf === void 0 ? void 0 : conf.excludedChars) !== undefined) { | ||
| excludedChars = conf.excludedChars; | ||
| } | ||
| else if (this.conf.excludedChars !== undefined) { | ||
| excludedChars = this.conf.excludedChars; | ||
| } | ||
| let patternIdx = 0; | ||
| const intervals = []; | ||
| for (let i = idxText; i < text.length && patternIdx < pattern.length;) { | ||
| if (excludedChars.includes(text[i])) { | ||
| i++; | ||
| continue; | ||
| } | ||
| if (excludedChars.includes(pattern[patternIdx])) { | ||
| patternIdx++; | ||
| continue; | ||
| } | ||
| if (text[i] === pattern[patternIdx]) { | ||
| const interval = { from: i, to: i }; | ||
| patternIdx++; | ||
| i++; | ||
| for (let j = i; j < text.length && patternIdx < pattern.length && text[j] === pattern[patternIdx]; j++) { | ||
| interval.to = j; | ||
| patternIdx++; | ||
| i = j; | ||
| } | ||
| intervals.push(interval); | ||
| } | ||
| i++; | ||
| } | ||
| if (intervals.length === 0 || patternIdx !== pattern.length) { | ||
| return null; | ||
| } | ||
| return { score: score(intervals, text.length), intervals: intervals }; | ||
| } | ||
| } | ||
| const fuz = new Fuzzy(); | ||
| export function filter(pattern, list, conf) { | ||
| return fuz.filter(pattern, list, conf); | ||
| } | ||
| export function match(pattern, text, conf) { | ||
| return fuz.match(pattern, text, conf); | ||
| } | ||
| export function render(text, intervals, conf) { | ||
| return fuz.render(text, intervals, conf); | ||
| } |
+13
-13
| { | ||
| "name": "@nexucis/fuzzy", | ||
| "version": "0.4.1", | ||
| "version": "0.5.0", | ||
| "description": "small, standalone fuzzy search / fuzzy filter. browser or node", | ||
@@ -33,17 +33,17 @@ "module": "dist/index.js", | ||
| "devDependencies": { | ||
| "@types/chai": "^4.3.1", | ||
| "@types/mocha": "^9.1.1", | ||
| "@typescript-eslint/eslint-plugin": "^5.25.0", | ||
| "@typescript-eslint/parser": "^5.25.0", | ||
| "chai": "^4.3.6", | ||
| "@types/chai": "^4.3.16", | ||
| "@types/mocha": "^10.0.7", | ||
| "@typescript-eslint/eslint-plugin": "^7.16.1", | ||
| "@typescript-eslint/parser": "^7.16.1", | ||
| "chai": "^4.4.1", | ||
| "codecov": "^3.8.3", | ||
| "eslint": "^8.15.0", | ||
| "eslint": "^8.57.0", | ||
| "eslint-plugin-flowtype": "^8.0.3", | ||
| "eslint-plugin-import": "^2.26.0", | ||
| "mocha": "^9.2.2", | ||
| "nyc": "^15.1.0", | ||
| "ts-mocha": "^9.0.2", | ||
| "ts-node": "^10.7.0", | ||
| "typescript": "^4.6.4" | ||
| "eslint-plugin-import": "^2.29.1", | ||
| "mocha": "^10.6.0", | ||
| "nyc": "^17.0.0", | ||
| "ts-mocha": "^10.0.0", | ||
| "ts-node": "^10.9.2", | ||
| "typescript": "^5.5.3" | ||
| } | ||
| } |
+17
-0
@@ -88,2 +88,9 @@ Fuzzy | ||
| ### excludedChars | ||
| * **Type**: `array of string` | ||
| * **Default**: `[]` | ||
| List of characters that should be ignored in the pattern or in the word used for matching | ||
| ### includeMatches | ||
@@ -104,2 +111,12 @@ | ||
| ### shouldRender | ||
| * **Type**: `boolean` | ||
| * **Default**: `true` | ||
| If true, then the strings matched will be automatically rendered using the config pre/post and escapeHTML. | ||
| In case you want to render it yourself, set it to false, and set `includeMatches` to true. | ||
| You will need the intervals to call the method render. | ||
| ### escapeHTML | ||
@@ -106,0 +123,0 @@ |
| "use strict"; | ||
| // MIT License | ||
| // | ||
| // Copyright (c) 2020 Augustin Husson | ||
| // | ||
| // Permission is hereby granted, free of charge, to any person obtaining a copy | ||
| // of this software and associated documentation files (the "Software"), to deal | ||
| // in the Software without restriction, including without limitation the rights | ||
| // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | ||
| // copies of the Software, and to permit persons to whom the Software is | ||
| // furnished to do so, subject to the following conditions: | ||
| // | ||
| // The above copyright notice and this permission notice shall be included in all | ||
| // copies or substantial portions of the Software. | ||
| // | ||
| // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | ||
| // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | ||
| // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | ||
| // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | ||
| // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | ||
| // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE | ||
| // SOFTWARE. | ||
| Object.defineProperty(exports, "__esModule", { value: true }); | ||
| exports.render = exports.match = exports.filter = exports.Fuzzy = exports.score = void 0; | ||
| function escapeHTML(text) { | ||
| return text.replace(/[&<>"']/g, (m) => { | ||
| switch (m) { | ||
| case '&': | ||
| return '&'; | ||
| case '<': | ||
| return '<'; | ||
| case '>': | ||
| return '>'; | ||
| case '"': | ||
| return '"'; | ||
| default: | ||
| return '''; | ||
| } | ||
| }); | ||
| } | ||
| function intervalSize(interval) { | ||
| return interval.to - interval.from + 1; | ||
| } | ||
| function calculatePreviousNotMatchingInterval(intervals, idx) { | ||
| const currentInterval = intervals[idx]; | ||
| let previousNotMatchingInterval = null; | ||
| if (idx === 0 && currentInterval.from !== 0) { | ||
| previousNotMatchingInterval = { from: 0, to: currentInterval.from - 1 }; | ||
| } | ||
| if (idx > 0) { | ||
| previousNotMatchingInterval = { from: intervals[idx - 1].to + 1, to: currentInterval.from - 1 }; | ||
| } | ||
| return previousNotMatchingInterval; | ||
| } | ||
| // score should be used to calculate the score based on the intervals created during the matching step. | ||
| // Here is how the score is determinated: | ||
| // 1. Consecutive characters should increase the score more than linearly | ||
| // 2. More there is a distance between the characters, higher it reduces the score | ||
| // For example, for the pattern 'abc', the following string are sorted by the highest score | ||
| // abcdef > defabc > abec > defabec | ||
| // Note: this function is exported only for testing purpose. | ||
| function score(intervals, strLength) { | ||
| let result = 0; | ||
| for (let i = 0; i < intervals.length; i++) { | ||
| const currentInterval = intervals[i]; | ||
| const previousNotMatchingInterval = calculatePreviousNotMatchingInterval(intervals, i); | ||
| if (previousNotMatchingInterval !== null) { | ||
| result = result - intervalSize(previousNotMatchingInterval) / strLength; | ||
| } | ||
| result = result + intervalSize(currentInterval) ** 2; | ||
| } | ||
| return result; | ||
| } | ||
| exports.score = score; | ||
| // generateMatchingInterval will iterate other the given text to find the different char that matched the given pattern | ||
| function generateMatchingInterval(pattern, text, idxText) { | ||
| let patternIdx = 0; | ||
| const intervals = []; | ||
| for (let i = idxText; i < text.length && patternIdx < pattern.length;) { | ||
| if (text[i] === pattern[patternIdx]) { | ||
| const interval = { from: i, to: i }; | ||
| patternIdx++; | ||
| i++; | ||
| for (let j = i; j < text.length && patternIdx < pattern.length && text[j] === pattern[patternIdx]; j++) { | ||
| interval.to = j; | ||
| patternIdx++; | ||
| i = j; | ||
| } | ||
| intervals.push(interval); | ||
| } | ||
| i++; | ||
| } | ||
| if (intervals.length === 0 || patternIdx !== pattern.length) { | ||
| return null; | ||
| } | ||
| return { score: score(intervals, text.length), intervals: intervals }; | ||
| } | ||
| class Fuzzy { | ||
| constructor(conf) { | ||
| this.conf = { | ||
| caseSensitive: (conf === null || conf === void 0 ? void 0 : conf.caseSensitive) === undefined ? false : conf.caseSensitive, | ||
| includeMatches: (conf === null || conf === void 0 ? void 0 : conf.includeMatches) === undefined ? false : conf.includeMatches, | ||
| shouldSort: (conf === null || conf === void 0 ? void 0 : conf.shouldSort) === undefined ? false : conf.shouldSort, | ||
| escapeHTML: (conf === null || conf === void 0 ? void 0 : conf.escapeHTML) === undefined ? false : conf.escapeHTML, | ||
| pre: (conf === null || conf === void 0 ? void 0 : conf.pre) === undefined ? '' : conf.pre, | ||
| post: (conf === null || conf === void 0 ? void 0 : conf.post) === undefined ? '' : conf.post, | ||
| }; | ||
| } | ||
| // filter is the method to use to filter a string list | ||
| // list of result return can be sort if parameter `shouldSort` is set. | ||
| filter(pattern, list, conf) { | ||
| const shouldSort = (conf === null || conf === void 0 ? void 0 : conf.shouldSort) !== undefined ? conf.shouldSort : this.conf.shouldSort; | ||
| let result = []; | ||
| for (let i = 0; i < list.length; i++) { | ||
| const matchedText = this.match(pattern, list[i], conf); | ||
| if (matchedText !== null) { | ||
| matchedText.index = i; | ||
| result.push(matchedText); | ||
| } | ||
| } | ||
| if (shouldSort) { | ||
| result = result.sort((a, b) => { | ||
| return b.score - a.score; | ||
| }); | ||
| } | ||
| return result; | ||
| } | ||
| // match will return a result if `pattern` is matching `text`, | ||
| match(pattern, text, conf) { | ||
| let localPattern = pattern; | ||
| let localText = text; | ||
| const caseSensitive = (conf === null || conf === void 0 ? void 0 : conf.caseSensitive) !== undefined ? conf.caseSensitive : this.conf.caseSensitive; | ||
| const includeMatches = (conf === null || conf === void 0 ? void 0 : conf.includeMatches) !== undefined ? conf.includeMatches : this.conf.includeMatches; | ||
| if (!caseSensitive) { | ||
| localPattern = localPattern.toLowerCase(); | ||
| localText = localText.toLowerCase(); | ||
| } | ||
| // in case it's a perfect match, no need to loop to find which char is matching | ||
| if (localPattern === localText) { | ||
| const intervals = [{ from: 0, to: pattern.length - 1 }]; | ||
| const result = { | ||
| original: text, | ||
| rendered: this.render(text, intervals, conf), | ||
| score: Infinity, | ||
| }; | ||
| if (includeMatches) { | ||
| result.intervals = intervals; | ||
| } | ||
| return result; | ||
| } | ||
| // otherwise let's calculate the different indices that will then be used to calculate the score | ||
| let intervals = []; | ||
| let score = 0; | ||
| for (let i = 0; i < localText.length - localPattern.length + 1; i++) { | ||
| // Each time a char is matching the first char of the pattern | ||
| // loop other the rest of the text to generate the different matching interval. | ||
| // Like that we will be able to find the best matching possibility. | ||
| // For example: given the pattern `bac` and the text `babac` | ||
| // instead of matching `<ba>ba<c>, it will match ba<bac> which has a better score than the previous one. | ||
| if (localText[i] === localPattern[0]) { | ||
| const matchingResult = generateMatchingInterval(localPattern, localText, i); | ||
| if (matchingResult === null) { | ||
| break; | ||
| } | ||
| if (matchingResult.score > score) { | ||
| score = matchingResult.score; | ||
| intervals = matchingResult.intervals; | ||
| } | ||
| } | ||
| } | ||
| if (intervals.length === 0) { | ||
| return null; | ||
| } | ||
| const result = { | ||
| original: text, | ||
| rendered: this.render(text, intervals, conf), | ||
| score: score, | ||
| }; | ||
| if (includeMatches) { | ||
| result.intervals = intervals; | ||
| } | ||
| return result; | ||
| } | ||
| // render will modify the text according to the different parameter set in the conf. | ||
| // If nothing is set, then it will return the text not modified. | ||
| render(text, intervals, conf) { | ||
| if (intervals.length == 0) { | ||
| return text; | ||
| } | ||
| let rendered = ''; | ||
| const pre = (conf === null || conf === void 0 ? void 0 : conf.pre) ? conf.pre : this.conf.pre; | ||
| const post = (conf === null || conf === void 0 ? void 0 : conf.post) ? conf.post : this.conf.post; | ||
| for (let i = 0; i < intervals.length; i++) { | ||
| const currentInterval = intervals[i]; | ||
| const previousNotMatchingInterval = calculatePreviousNotMatchingInterval(intervals, i); | ||
| let previousStr = ''; | ||
| if (previousNotMatchingInterval !== null) { | ||
| previousStr = this.extractSubString(text, previousNotMatchingInterval, conf); | ||
| } | ||
| const currentStr = this.extractSubString(text, currentInterval, conf); | ||
| rendered = `${rendered}${previousStr}${pre}${currentStr}${post}`; | ||
| } | ||
| // check if the last interval contains the end of the string. Otherwise, add it | ||
| const lastInterval = intervals[intervals.length - 1]; | ||
| if (lastInterval.to < text.length - 1) { | ||
| rendered = rendered + this.extractSubString(text, { from: lastInterval.to + 1, to: text.length }, conf); | ||
| } | ||
| return rendered; | ||
| } | ||
| extractSubString(text, interval, conf) { | ||
| const shouldEscape = (conf === null || conf === void 0 ? void 0 : conf.escapeHTML) !== undefined ? conf.escapeHTML : this.conf.escapeHTML; | ||
| let str = text.substr(interval.from, intervalSize(interval)); | ||
| if (shouldEscape) { | ||
| str = escapeHTML(str); | ||
| } | ||
| return str; | ||
| } | ||
| } | ||
| exports.Fuzzy = Fuzzy; | ||
| const fuz = new Fuzzy(); | ||
| function filter(pattern, list, conf) { | ||
| return fuz.filter(pattern, list, conf); | ||
| } | ||
| exports.filter = filter; | ||
| function match(pattern, text, conf) { | ||
| return fuz.match(pattern, text, conf); | ||
| } | ||
| exports.match = match; | ||
| function render(text, intervals, conf) { | ||
| return fuz.render(text, intervals, conf); | ||
| } | ||
| exports.render = render; |
| export declare function score(intervals: FuzzyMatchingInterval[], strLength: number): number; | ||
| export interface FuzzyConfiguration { | ||
| caseSensitive?: boolean; | ||
| includeMatches?: boolean; | ||
| shouldSort?: boolean; | ||
| escapeHTML?: boolean; | ||
| pre?: string; | ||
| post?: string; | ||
| } | ||
| export interface FuzzyMatchingInterval { | ||
| from: number; | ||
| to: number; | ||
| } | ||
| export interface FuzzyResult { | ||
| rendered: string; | ||
| original: string; | ||
| index: number; | ||
| score: number; | ||
| intervals?: FuzzyMatchingInterval[]; | ||
| } | ||
| export declare class Fuzzy { | ||
| private readonly conf; | ||
| constructor(conf?: FuzzyConfiguration); | ||
| filter(pattern: string, list: string[], conf?: FuzzyConfiguration): FuzzyResult[]; | ||
| match(pattern: string, text: string, conf?: FuzzyConfiguration): FuzzyResult | null; | ||
| render(text: string, intervals: FuzzyMatchingInterval[], conf?: FuzzyConfiguration): string; | ||
| private extractSubString; | ||
| } | ||
| export declare function filter(pattern: string, list: string[], conf?: FuzzyConfiguration): FuzzyResult[]; | ||
| export declare function match(pattern: string, text: string, conf?: FuzzyConfiguration): FuzzyResult | null; | ||
| export declare function render(text: string, intervals: FuzzyMatchingInterval[], conf?: FuzzyConfiguration): string; |
-224
| // MIT License | ||
| // | ||
| // Copyright (c) 2020 Augustin Husson | ||
| // | ||
| // Permission is hereby granted, free of charge, to any person obtaining a copy | ||
| // of this software and associated documentation files (the "Software"), to deal | ||
| // in the Software without restriction, including without limitation the rights | ||
| // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | ||
| // copies of the Software, and to permit persons to whom the Software is | ||
| // furnished to do so, subject to the following conditions: | ||
| // | ||
| // The above copyright notice and this permission notice shall be included in all | ||
| // copies or substantial portions of the Software. | ||
| // | ||
| // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | ||
| // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | ||
| // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | ||
| // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | ||
| // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | ||
| // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE | ||
| // SOFTWARE. | ||
| function escapeHTML(text) { | ||
| return text.replace(/[&<>"']/g, (m) => { | ||
| switch (m) { | ||
| case '&': | ||
| return '&'; | ||
| case '<': | ||
| return '<'; | ||
| case '>': | ||
| return '>'; | ||
| case '"': | ||
| return '"'; | ||
| default: | ||
| return '''; | ||
| } | ||
| }); | ||
| } | ||
| function intervalSize(interval) { | ||
| return interval.to - interval.from + 1; | ||
| } | ||
| function calculatePreviousNotMatchingInterval(intervals, idx) { | ||
| const currentInterval = intervals[idx]; | ||
| let previousNotMatchingInterval = null; | ||
| if (idx === 0 && currentInterval.from !== 0) { | ||
| previousNotMatchingInterval = { from: 0, to: currentInterval.from - 1 }; | ||
| } | ||
| if (idx > 0) { | ||
| previousNotMatchingInterval = { from: intervals[idx - 1].to + 1, to: currentInterval.from - 1 }; | ||
| } | ||
| return previousNotMatchingInterval; | ||
| } | ||
| // score should be used to calculate the score based on the intervals created during the matching step. | ||
| // Here is how the score is determinated: | ||
| // 1. Consecutive characters should increase the score more than linearly | ||
| // 2. More there is a distance between the characters, higher it reduces the score | ||
| // For example, for the pattern 'abc', the following string are sorted by the highest score | ||
| // abcdef > defabc > abec > defabec | ||
| // Note: this function is exported only for testing purpose. | ||
| export function score(intervals, strLength) { | ||
| let result = 0; | ||
| for (let i = 0; i < intervals.length; i++) { | ||
| const currentInterval = intervals[i]; | ||
| const previousNotMatchingInterval = calculatePreviousNotMatchingInterval(intervals, i); | ||
| if (previousNotMatchingInterval !== null) { | ||
| result = result - intervalSize(previousNotMatchingInterval) / strLength; | ||
| } | ||
| result = result + intervalSize(currentInterval) ** 2; | ||
| } | ||
| return result; | ||
| } | ||
| // generateMatchingInterval will iterate other the given text to find the different char that matched the given pattern | ||
| function generateMatchingInterval(pattern, text, idxText) { | ||
| let patternIdx = 0; | ||
| const intervals = []; | ||
| for (let i = idxText; i < text.length && patternIdx < pattern.length;) { | ||
| if (text[i] === pattern[patternIdx]) { | ||
| const interval = { from: i, to: i }; | ||
| patternIdx++; | ||
| i++; | ||
| for (let j = i; j < text.length && patternIdx < pattern.length && text[j] === pattern[patternIdx]; j++) { | ||
| interval.to = j; | ||
| patternIdx++; | ||
| i = j; | ||
| } | ||
| intervals.push(interval); | ||
| } | ||
| i++; | ||
| } | ||
| if (intervals.length === 0 || patternIdx !== pattern.length) { | ||
| return null; | ||
| } | ||
| return { score: score(intervals, text.length), intervals: intervals }; | ||
| } | ||
| export class Fuzzy { | ||
| constructor(conf) { | ||
| this.conf = { | ||
| caseSensitive: (conf === null || conf === void 0 ? void 0 : conf.caseSensitive) === undefined ? false : conf.caseSensitive, | ||
| includeMatches: (conf === null || conf === void 0 ? void 0 : conf.includeMatches) === undefined ? false : conf.includeMatches, | ||
| shouldSort: (conf === null || conf === void 0 ? void 0 : conf.shouldSort) === undefined ? false : conf.shouldSort, | ||
| escapeHTML: (conf === null || conf === void 0 ? void 0 : conf.escapeHTML) === undefined ? false : conf.escapeHTML, | ||
| pre: (conf === null || conf === void 0 ? void 0 : conf.pre) === undefined ? '' : conf.pre, | ||
| post: (conf === null || conf === void 0 ? void 0 : conf.post) === undefined ? '' : conf.post, | ||
| }; | ||
| } | ||
| // filter is the method to use to filter a string list | ||
| // list of result return can be sort if parameter `shouldSort` is set. | ||
| filter(pattern, list, conf) { | ||
| const shouldSort = (conf === null || conf === void 0 ? void 0 : conf.shouldSort) !== undefined ? conf.shouldSort : this.conf.shouldSort; | ||
| let result = []; | ||
| for (let i = 0; i < list.length; i++) { | ||
| const matchedText = this.match(pattern, list[i], conf); | ||
| if (matchedText !== null) { | ||
| matchedText.index = i; | ||
| result.push(matchedText); | ||
| } | ||
| } | ||
| if (shouldSort) { | ||
| result = result.sort((a, b) => { | ||
| return b.score - a.score; | ||
| }); | ||
| } | ||
| return result; | ||
| } | ||
| // match will return a result if `pattern` is matching `text`, | ||
| match(pattern, text, conf) { | ||
| let localPattern = pattern; | ||
| let localText = text; | ||
| const caseSensitive = (conf === null || conf === void 0 ? void 0 : conf.caseSensitive) !== undefined ? conf.caseSensitive : this.conf.caseSensitive; | ||
| const includeMatches = (conf === null || conf === void 0 ? void 0 : conf.includeMatches) !== undefined ? conf.includeMatches : this.conf.includeMatches; | ||
| if (!caseSensitive) { | ||
| localPattern = localPattern.toLowerCase(); | ||
| localText = localText.toLowerCase(); | ||
| } | ||
| // in case it's a perfect match, no need to loop to find which char is matching | ||
| if (localPattern === localText) { | ||
| const intervals = [{ from: 0, to: pattern.length - 1 }]; | ||
| const result = { | ||
| original: text, | ||
| rendered: this.render(text, intervals, conf), | ||
| score: Infinity, | ||
| }; | ||
| if (includeMatches) { | ||
| result.intervals = intervals; | ||
| } | ||
| return result; | ||
| } | ||
| // otherwise let's calculate the different indices that will then be used to calculate the score | ||
| let intervals = []; | ||
| let score = 0; | ||
| for (let i = 0; i < localText.length - localPattern.length + 1; i++) { | ||
| // Each time a char is matching the first char of the pattern | ||
| // loop other the rest of the text to generate the different matching interval. | ||
| // Like that we will be able to find the best matching possibility. | ||
| // For example: given the pattern `bac` and the text `babac` | ||
| // instead of matching `<ba>ba<c>, it will match ba<bac> which has a better score than the previous one. | ||
| if (localText[i] === localPattern[0]) { | ||
| const matchingResult = generateMatchingInterval(localPattern, localText, i); | ||
| if (matchingResult === null) { | ||
| break; | ||
| } | ||
| if (matchingResult.score > score) { | ||
| score = matchingResult.score; | ||
| intervals = matchingResult.intervals; | ||
| } | ||
| } | ||
| } | ||
| if (intervals.length === 0) { | ||
| return null; | ||
| } | ||
| const result = { | ||
| original: text, | ||
| rendered: this.render(text, intervals, conf), | ||
| score: score, | ||
| }; | ||
| if (includeMatches) { | ||
| result.intervals = intervals; | ||
| } | ||
| return result; | ||
| } | ||
| // render will modify the text according to the different parameter set in the conf. | ||
| // If nothing is set, then it will return the text not modified. | ||
| render(text, intervals, conf) { | ||
| if (intervals.length == 0) { | ||
| return text; | ||
| } | ||
| let rendered = ''; | ||
| const pre = (conf === null || conf === void 0 ? void 0 : conf.pre) ? conf.pre : this.conf.pre; | ||
| const post = (conf === null || conf === void 0 ? void 0 : conf.post) ? conf.post : this.conf.post; | ||
| for (let i = 0; i < intervals.length; i++) { | ||
| const currentInterval = intervals[i]; | ||
| const previousNotMatchingInterval = calculatePreviousNotMatchingInterval(intervals, i); | ||
| let previousStr = ''; | ||
| if (previousNotMatchingInterval !== null) { | ||
| previousStr = this.extractSubString(text, previousNotMatchingInterval, conf); | ||
| } | ||
| const currentStr = this.extractSubString(text, currentInterval, conf); | ||
| rendered = `${rendered}${previousStr}${pre}${currentStr}${post}`; | ||
| } | ||
| // check if the last interval contains the end of the string. Otherwise, add it | ||
| const lastInterval = intervals[intervals.length - 1]; | ||
| if (lastInterval.to < text.length - 1) { | ||
| rendered = rendered + this.extractSubString(text, { from: lastInterval.to + 1, to: text.length }, conf); | ||
| } | ||
| return rendered; | ||
| } | ||
| extractSubString(text, interval, conf) { | ||
| const shouldEscape = (conf === null || conf === void 0 ? void 0 : conf.escapeHTML) !== undefined ? conf.escapeHTML : this.conf.escapeHTML; | ||
| let str = text.substr(interval.from, intervalSize(interval)); | ||
| if (shouldEscape) { | ||
| str = escapeHTML(str); | ||
| } | ||
| return str; | ||
| } | ||
| } | ||
| const fuz = new Fuzzy(); | ||
| export function filter(pattern, list, conf) { | ||
| return fuz.filter(pattern, list, conf); | ||
| } | ||
| export function match(pattern, text, conf) { | ||
| return fuz.match(pattern, text, conf); | ||
| } | ||
| export function render(text, intervals, conf) { | ||
| return fuz.render(text, intervals, conf); | ||
| } |
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
Long strings
Supply chain riskContains long string literals, which may be a sign of obfuscated or packed code.
Found 1 instance in 1 package
Long strings
Supply chain riskContains long string literals, which may be a sign of obfuscated or packed code.
Found 1 instance in 1 package
30614
9.87%7
16.67%526
8.01%146
13.18%1
Infinity%