Comparing version 1.0.4 to 2.0.0
@@ -7,61 +7,171 @@ 'use strict'; | ||
// Improved from Bulat Bochkariov's version (http://www.quora.com/Algorithms/How-is-the-fuzzy-search-algorithm-in-Sublime-Text-designed) | ||
var fuzzy = function fuzzy(searchSet, query, opts_) { | ||
var opts = _extends({ | ||
caseSensitive: false, | ||
before: '', | ||
after: '' | ||
}, opts_); | ||
var isArray = function isArray(arr) { | ||
return Object.prototype.toString.call(arr) === '[object Array]'; | ||
}; | ||
if (!query) { | ||
return searchSet; | ||
} | ||
var fuzzy = { | ||
/** | ||
* Tests if a string matches a pattern | ||
* @param {String} q The pattern | ||
* @param {String} str The string | ||
* @param {Boolean} caseSensitive True if case sensitive should count | ||
* @return {Boolean} True if the string matches, false otherwise | ||
*/ | ||
var tokens = opts.caseSensitive ? query.split('') : query.toLowerCase().split(''); | ||
var matches = []; | ||
var l = searchSet.length; | ||
var i = 0; | ||
test: function test(q, str) { | ||
var caseSensitive = arguments.length <= 2 || arguments[2] === undefined ? false : arguments[2]; | ||
for (i; i < l; ++i) { | ||
// tokenIndex : query letter index | ||
// stringIndex : current possible result letter index | ||
// matchWithHighlights : string containing result | ||
// stringLC : lower-case search, or not | ||
if (typeof q !== 'string' || typeof str !== 'string') { | ||
return -1; | ||
} | ||
var tokenIndex = 0; | ||
var stringIndex = 0; | ||
var matchWithHighlights = ''; | ||
var matchedPositions = []; | ||
var stringLC = opts.caseSensitive ? searchSet[i] : searchSet[i].toLowerCase(); | ||
if (!str) { | ||
return -1; | ||
} | ||
while (stringIndex < searchSet[i].length) { | ||
// [Still] same letter | ||
if (stringLC[stringIndex] === tokens[tokenIndex]) { | ||
matchWithHighlights += opts.before + searchSet[i][stringIndex] + opts.after; | ||
matchedPositions.push(stringIndex); | ||
++tokenIndex; | ||
if (!q) { | ||
return true; | ||
} | ||
if (tokenIndex >= tokens.length) { | ||
matches.push(matchWithHighlights + searchSet[i].slice(stringIndex + 1)); | ||
break; | ||
} | ||
if (!caseSensitive) { | ||
q = q.toLowerCase(); | ||
str = str.toLowerCase(); | ||
} | ||
var pos = 0; | ||
var i = 0; | ||
while (i < str.length) { | ||
if (str[i] === q[pos]) { | ||
pos += 1; | ||
} | ||
++i; | ||
} | ||
return pos === q.length; | ||
}, | ||
/** | ||
* Tests if a string matches a pattern and return a score | ||
* @param {String} q The pattern | ||
* @param {String} str The string | ||
* @param {Object} opts Options containing `caseSensitive` `before` and `after` | ||
* @return {Object} Object containing score and surrounded (or intact) result | ||
*/ | ||
match: function match(q, str, opts) { | ||
if (typeof q !== 'string' || typeof str !== 'string') { | ||
return { score: 0, result: str }; | ||
} | ||
if (!str) { | ||
return { score: 0, result: str }; | ||
} | ||
if (!q) { | ||
return { score: 1, result: str }; | ||
} | ||
opts = _extends({ | ||
caseSensitive: false, | ||
before: '', | ||
after: '' | ||
}, opts); | ||
if (!opts.caseSensitive) { | ||
q = q.toLowerCase(); | ||
str = str.toLowerCase(); | ||
} | ||
// String with surrounded results | ||
var result = ''; | ||
// Number of spaces between matches | ||
var steps = 0; | ||
// Actual pattern position | ||
var pos = 0; | ||
// Last match position | ||
var lastI = 0; | ||
var i = 0; | ||
while (i < str.length) { | ||
var c = str[i]; | ||
if (c === q[pos]) { | ||
result += opts.before + c + opts.after; | ||
// Move to the next pattern character | ||
pos += 1; | ||
// Add spaces between the last match to steps | ||
steps += i - lastI; | ||
// Reset counter to the actual position in string | ||
lastI = i; | ||
} else { | ||
matchWithHighlights += searchSet[i][stringIndex]; | ||
result += c; | ||
} | ||
++stringIndex; | ||
++i; | ||
} | ||
} | ||
// Order by number of occurrences (requires surrounding) | ||
if (opts.before.length > 0) { | ||
matches = matches.sort(function (a, b) { | ||
var scoreA = a.split(opts.before).length - 1; | ||
var scoreB = b.split(opts.before).length - 1; | ||
if (pos === q.length) { | ||
// Score between 0 and 1 calculated by the number of spaces | ||
// between letters and the string length. | ||
// The biggest the score is the better | ||
var score = q.length / (steps + 1); | ||
return scoreA - scoreB; | ||
return { score: score, result: result }; | ||
} | ||
return { score: 0, result: str }; | ||
}, | ||
/** | ||
* Filters an array based on the pattern | ||
* @param {String} q The pattern | ||
* @param {Array<String>} set An array of queries | ||
* @param {Object} opts Options containing `caseSensitive` `before` and `after` | ||
* @return {Array<String>} A sorted array of results | ||
*/ | ||
filter: function filter(q, set, opts) { | ||
if (!isArray(set)) { | ||
return []; | ||
} | ||
if (typeof q !== 'string' || !q) { | ||
return set; | ||
} | ||
opts = _extends({ | ||
caseSensitive: false, | ||
before: '', | ||
after: '' | ||
}, opts); | ||
var results = []; | ||
var i = 0; | ||
while (i < set.length) { | ||
var str = set[i]; | ||
var result = fuzzy.match(q, str, opts); | ||
if (result.score > 0) { | ||
results.push(result); | ||
} | ||
++i; | ||
} | ||
return results.sort(function (a, b) { | ||
return b.score - a.score; | ||
}).map(function (elem) { | ||
return elem.result; | ||
}); | ||
} | ||
return matches; | ||
}; | ||
@@ -68,0 +178,0 @@ |
@@ -1,2 +0,2 @@ | ||
"use strict";var _typeof=typeof Symbol==="function"&&typeof Symbol.iterator==="symbol"?function(obj){return typeof obj}:function(obj){return obj&&typeof Symbol==="function"&&obj.constructor===Symbol?"symbol":typeof obj};var _extends=Object.assign||function(target){for(var i=1;i<arguments.length;i++){var source=arguments[i];for(var key in source){if(Object.prototype.hasOwnProperty.call(source,key)){target[key]=source[key]}}}return target};var fuzzy=function fuzzy(searchSet,query,opts_){var opts=_extends({caseSensitive:false,before:"",after:""},opts_);if(!query){return searchSet}var tokens=opts.caseSensitive?query.split(""):query.toLowerCase().split("");var matches=[];var l=searchSet.length;var i=0;for(i;i<l;++i){var tokenIndex=0;var stringIndex=0;var matchWithHighlights="";var matchedPositions=[];var stringLC=opts.caseSensitive?searchSet[i]:searchSet[i].toLowerCase();while(stringIndex<searchSet[i].length){if(stringLC[stringIndex]===tokens[tokenIndex]){matchWithHighlights+=opts.before+searchSet[i][stringIndex]+opts.after;matchedPositions.push(stringIndex);++tokenIndex;if(tokenIndex>=tokens.length){matches.push(matchWithHighlights+searchSet[i].slice(stringIndex+1));break}}else{matchWithHighlights+=searchSet[i][stringIndex]}++stringIndex}}if(opts.before.length>0){matches=matches.sort(function(a,b){var scoreA=a.split(opts.before).length-1;var scoreB=b.split(opts.before).length-1;return scoreA-scoreB})}return matches};(function(root,factory){if(typeof define==="function"&&define.amd){define([],factory)}else if((typeof module==="undefined"?"undefined":_typeof(module))==="object"&&module.exports){module.exports=factory()}else{root.returnExports=factory()}})(undefined,function(){return fuzzy}); | ||
"use strict";var _typeof=typeof Symbol==="function"&&typeof Symbol.iterator==="symbol"?function(obj){return typeof obj}:function(obj){return obj&&typeof Symbol==="function"&&obj.constructor===Symbol?"symbol":typeof obj};var _extends=Object.assign||function(target){for(var i=1;i<arguments.length;i++){var source=arguments[i];for(var key in source){if(Object.prototype.hasOwnProperty.call(source,key)){target[key]=source[key]}}}return target};var isArray=function isArray(arr){return Object.prototype.toString.call(arr)==="[object Array]"};var fuzzy={test:function test(q,str){var caseSensitive=arguments.length<=2||arguments[2]===undefined?false:arguments[2];if(typeof q!=="string"||typeof str!=="string"){return-1}if(!str){return-1}if(!q){return true}if(!caseSensitive){q=q.toLowerCase();str=str.toLowerCase()}var pos=0;var i=0;while(i<str.length){if(str[i]===q[pos]){pos+=1}++i}return pos===q.length},match:function match(q,str,opts){if(typeof q!=="string"||typeof str!=="string"){return{score:0,result:str}}if(!str){return{score:0,result:str}}if(!q){return{score:1,result:str}}opts=_extends({caseSensitive:false,before:"",after:""},opts);if(!opts.caseSensitive){q=q.toLowerCase();str=str.toLowerCase()}var result="";var steps=0;var pos=0;var lastI=0;var i=0;while(i<str.length){var c=str[i];if(c===q[pos]){result+=opts.before+c+opts.after;pos+=1;steps+=i-lastI;lastI=i}else{result+=c}++i}if(pos===q.length){var score=q.length/(steps+1);return{score:score,result:result}}return{score:0,result:str}},filter:function filter(q,set,opts){if(!isArray(set)){return[]}if(typeof q!=="string"||!q){return set}opts=_extends({caseSensitive:false,before:"",after:""},opts);var results=[];var i=0;while(i<set.length){var str=set[i];var result=fuzzy.match(q,str,opts);if(result.score>0){results.push(result)}++i}return results.sort(function(a,b){return b.score-a.score}).map(function(elem){return elem.result})}};(function(root,factory){if(typeof define==="function"&&define.amd){define([],factory)}else if((typeof module==="undefined"?"undefined":_typeof(module))==="object"&&module.exports){module.exports=factory()}else{root.returnExports=factory()}})(undefined,function(){return fuzzy}); | ||
//# sourceMappingURL=build/fuzzy.min.js.map |
{ | ||
"name": "fuzzyjs", | ||
"version": "1.0.4", | ||
"version": "2.0.0", | ||
"description": "Fuzzy.js is a fuzzy search algorithm in javascript", | ||
@@ -5,0 +5,0 @@ "main": "build/fuzzy.js", |
@@ -14,8 +14,11 @@ # fuzzyjs # | ||
```js | ||
const arr = ['A sentence composed of multiple words', 'Set syntax Javscript']; | ||
fuzzy(arr, 'scmw'); | ||
// Basic true/false test | ||
fuzzy.test('li', 'lorem ipsum'); // true | ||
fuzzy.test('li', 'Lorem ipsum', true); // false | ||
['A sentence composed of multiple words'] | ||
// Advanced match with surrounding and score | ||
fuzzy.match('li', 'lorem ipsum'); | ||
// { score: 0.2857, result: 'lorem ipsum' } | ||
fuzzy(arr, 'w', { | ||
fuzzy.match('Li', 'Lorem ipsum', { | ||
caseSensitive: true, | ||
@@ -25,4 +28,13 @@ before: '<span>', | ||
}); | ||
// { score: 0.2857, result: '<span>L</span>orem <span>i</span>psum' } | ||
['A sentence composed of multiple <span>w</span>ords'] | ||
// Filter an array using match (array is sorted based on score) | ||
const arr = ['lorem ipsum', 'foo', 'the li element']; | ||
fuzzy.filter('li', arr, { caseSensitive: true, before: '<span>', after: '</span>' }); | ||
/* | ||
[ | ||
'the <span>l</span><span>i</span> element', | ||
'<span>l</span>orem <span>i</span>psum' | ||
] | ||
*/ | ||
``` |
import assert from 'assert'; | ||
import fuzzy from '../src/fuzzy'; | ||
const testSet = [ | ||
'A sentence composed of multiple Words', | ||
'a sentence composed of multiple words', | ||
'Set syntax Javascript' | ||
]; | ||
describe('fuzzyjs', () => { | ||
describe('default search', () => { | ||
it('finds the right string and surrounds it with a span tag', () => { | ||
const expected = [ | ||
'A sentence composed of multiple Words', | ||
'a sentence composed of multiple words' | ||
]; | ||
describe('test', () => { | ||
it('should return -1 if no query', () => { | ||
const str = 'foo'; | ||
const q = null; | ||
const expected = -1; | ||
const result = fuzzy(testSet, 'scmw'); | ||
const result = fuzzy.test(q, str); | ||
assert.deepEqual(result, expected); | ||
assert.equal(expected, result); | ||
}); | ||
it('should return the full set without query', () => { | ||
const expected = testSet; | ||
it('should return -1 if no string to test', () => { | ||
const str = null; | ||
const q = 'foo'; | ||
const expected = -1; | ||
const result = fuzzy(testSet); | ||
const result = fuzzy.test(q, str); | ||
assert.deepEqual(result, expected); | ||
assert.equal(expected, result); | ||
}); | ||
it('should return -1 if string is empty', () => { | ||
const str = ''; | ||
const q = 'foo'; | ||
const expected = -1; | ||
const result = fuzzy.test(q, str); | ||
assert.equal(expected, result); | ||
}); | ||
it('should return true if query is empty', () => { | ||
const str = 'foo'; | ||
const q = ''; | ||
const expected = true; | ||
const result = fuzzy.test(q, str); | ||
assert.equal(expected, result); | ||
}); | ||
it('should match when the pattern matches', () => { | ||
const str = 'lorem ipsum'; | ||
const q = 'li'; | ||
const expected = true; | ||
const result = fuzzy.test(q, str); | ||
assert.equal(expected, result); | ||
}); | ||
it('should not match when the pattern does not match', () => { | ||
const str = 'lorem ipsum'; | ||
const q = 'foo'; | ||
const expected = false; | ||
const result = fuzzy.test(q, str); | ||
assert.equal(expected, result); | ||
}); | ||
it('should match with caseSensitive set to true and pattern matching', () => { | ||
const str = 'Lorem ipsum'; | ||
const q = 'Li'; | ||
const expected = true; | ||
const result = fuzzy.test(q, str, true); | ||
assert.equal(expected, result); | ||
}); | ||
it('should not match with caseSensitive set to true and pattern not matching', () => { | ||
const str = 'Lorem ipsum'; | ||
const q = 'li'; | ||
const expected = false; | ||
const result = fuzzy.test(q, str, true); | ||
assert.equal(expected, result); | ||
}); | ||
}); | ||
describe('custom surrounding tag', () => { | ||
it('finds the right string and surrounds it with a span tag', () => { | ||
const expected = [ | ||
'A <strong>s</strong>enten<strong>c</strong>e co<strong>m</strong>posed of multiple <strong>W</strong>ords', | ||
'a <strong>s</strong>enten<strong>c</strong>e co<strong>m</strong>posed of multiple <strong>w</strong>ords' | ||
]; | ||
describe('match', () => { | ||
it('should return { score: 0, result: str } if no query', () => { | ||
const str = 'foo'; | ||
const q = null; | ||
const result = fuzzy(testSet, 'scmw', { | ||
caseSensitive: false, | ||
before : '<strong>', | ||
after : '</strong>' | ||
}); | ||
const expectedScore = 0; | ||
const expectedResult = 'foo'; | ||
assert.deepEqual(result, expected); | ||
const result = fuzzy.match(q, str); | ||
assert.equal(expectedScore, result.score); | ||
assert.equal(expectedResult, result.result); | ||
}); | ||
it('should return { score: 0, result: str } if no string to match', () => { | ||
const str = null; | ||
const q = 'foo'; | ||
const expectedScore = 0; | ||
const expectedResult = null; | ||
const result = fuzzy.match(q, str); | ||
assert.equal(expectedScore, result.score); | ||
assert.equal(expectedResult, result.result); | ||
}); | ||
it('should return { score: 0, result: str } if string is empty', () => { | ||
const str = ''; | ||
const q = 'foo'; | ||
const expectedScore = 0; | ||
const expectedResult = ''; | ||
const result = fuzzy.match(q, str); | ||
assert.equal(expectedScore, result.score); | ||
assert.equal(expectedResult, result.result); | ||
}); | ||
it('should return { score: 1, result: str } if query is empty', () => { | ||
const str = 'foo'; | ||
const q = ''; | ||
const expectedScore = 1; | ||
const expectedResult = 'foo'; | ||
const result = fuzzy.match(q, str); | ||
assert.equal(expectedScore, result.score); | ||
assert.equal(expectedResult, result.result); | ||
}); | ||
it('should match when the pattern matches', () => { | ||
const str = 'lorem ipsum'; | ||
const q = 'li'; | ||
const expectedScore = 'li'.length / (6 + 1); | ||
const expectedResult = 'lorem ipsum'; | ||
const result = fuzzy.match(q, str); | ||
assert.equal(expectedScore, result.score); | ||
assert.equal(expectedResult, result.result); | ||
}); | ||
it('should scores 1 when the pattern is equal to the string', () => { | ||
const str = 'lorem ipsum'; | ||
const q = 'lorem ipsum'; | ||
const expectedScore = 1; | ||
const expectedResult = 'lorem ipsum'; | ||
const result = fuzzy.match(q, str); | ||
assert.equal(expectedScore, result.score); | ||
assert.equal(expectedResult, result.result); | ||
}); | ||
it('should not match when the pattern does not match', () => { | ||
const str = 'lorem ipsum'; | ||
const q = 'foo'; | ||
const expectedScore = 0; | ||
const expectedResult = 'lorem ipsum'; | ||
const result = fuzzy.match(q, str); | ||
assert.equal(expectedScore, result.score); | ||
assert.equal(expectedResult, result.result); | ||
}); | ||
it('should match with caseSensitive set to true and pattern matching', () => { | ||
const str = 'Lorem ipsum'; | ||
const q = 'Li'; | ||
const expectedScore = 'li'.length / (6 + 1); | ||
const expectedResult = 'Lorem ipsum'; | ||
const result = fuzzy.match(q, str, { caseSensitive: true }); | ||
assert.equal(expectedScore, result.score); | ||
assert.equal(expectedResult, result.result); | ||
}); | ||
it('should not match with caseSensitive set to true and pattern not matching', () => { | ||
const str = 'Lorem ipsum'; | ||
const q = 'li'; | ||
const expectedScore = 0; | ||
const expectedResult = 'Lorem ipsum'; | ||
const result = fuzzy.match(q, str, { caseSensitive: true }); | ||
assert.equal(expectedScore, result.score); | ||
assert.equal(expectedResult, result.result); | ||
}); | ||
it('should surround with options', () => { | ||
const str = 'lorem ipsum'; | ||
const q = 'li'; | ||
const expectedScore = 'li'.length / (6 + 1); | ||
const expectedResult = '<span>l</span>orem <span>i</span>psum'; | ||
const result = fuzzy.match(q, str, { before: '<span>', after: '</span>' }); | ||
assert.equal(expectedScore, result.score); | ||
assert.equal(expectedResult, result.result); | ||
}); | ||
}); | ||
describe('case sensitive search', () => { | ||
it('finds only the lower case letter', () => { | ||
const expected = [ | ||
'a sentence composed of multiple words' | ||
]; | ||
describe('filter', () => { | ||
it('should return the original set if no query', () => { | ||
const arr = ['lorem ipsum', 'foo', 'the li element']; | ||
const q = ''; | ||
const result = fuzzy(testSet, 'w', { | ||
caseSensitive: true | ||
}); | ||
const expected = ['lorem ipsum', 'foo', 'the li element']; | ||
assert.deepEqual(result, expected); | ||
const result = fuzzy.filter(q, arr); | ||
assert.deepEqual(expected, result); | ||
}); | ||
it('finds only the upper case letter', () => { | ||
const expected = [ | ||
'A sentence composed of multiple Words' | ||
]; | ||
it('should return the original set if invalid query', () => { | ||
const arr = ['lorem ipsum', 'foo', 'the li element']; | ||
const q = null; | ||
const result = fuzzy(testSet, 'W', { | ||
caseSensitive: true | ||
}); | ||
const expected = ['lorem ipsum', 'foo', 'the li element']; | ||
assert.deepEqual(result, expected); | ||
const result = fuzzy.filter(q, arr); | ||
assert.deepEqual(expected, result); | ||
}); | ||
it('should return an empty array if invalid set', () => { | ||
const arr = null; | ||
const q = ''; | ||
const expected = []; | ||
const result = fuzzy.filter(q, arr); | ||
assert.deepEqual(expected, result); | ||
}); | ||
it('should filter an array based on pattern', () => { | ||
const arr = ['lorem ipsum', 'foo', 'the li element']; | ||
const q = 'li'; | ||
const expected = ['the li element', 'lorem ipsum']; | ||
const result = fuzzy.filter(q, arr); | ||
assert.deepEqual(expected, result); | ||
}); | ||
}); | ||
}); |
Sorry, the diff of this file is not supported yet
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
22124
338
39
1