├── .gitignore ├── .flowconfig ├── .babelrc ├── prepublish.sh ├── README.md ├── LICENSE ├── dist ├── fuzzy-match-utils.min.js └── fuzzy-match-utils.js ├── package.json ├── .eslintrc └── src └── main.js /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | *.log 3 | .idea 4 | -------------------------------------------------------------------------------- /.flowconfig: -------------------------------------------------------------------------------- 1 | [options] 2 | module.file_ext=.js 3 | module.file_ext=.jsx 4 | 5 | -------------------------------------------------------------------------------- /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["latest"], 3 | "plugins": [ 4 | "transform-flow-strip-types" 5 | ] 6 | } 7 | -------------------------------------------------------------------------------- /prepublish.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # Borrowed from React CDK. 3 | 4 | echo "=> Transpiling 'src' into ES5 ..." 5 | echo "" 6 | rm -rf ./dist && mkdir dist 7 | NODE_ENV=production ./node_modules/.bin/babel --ignore tests ./src --out-file ./dist/fuzzy-match-utils.js 8 | NODE_ENV=production ./node_modules/.bin/uglifyjs dist/fuzzy-match-utils.js -o dist/fuzzy-match-utils.min.js -m 9 | echo "" 10 | echo "=> Transpiling completed." 11 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # fuzzy-match-utils 2 | 3 | A collection of string matching algorithms built with 4 | [React Select](https://github.com/JedWatson/react-select) in mind. 5 | 6 | ## Installation 7 | 8 | ``` 9 | npm install --save fuzzy-match-utils 10 | ``` 11 | 12 | ## Usage 13 | 14 | This module exports four utility functions: 15 | 16 | - `filterOptions` 17 | - `typeaheadSimilarity` 18 | - `fullStringDistance` 19 | - `cleanUpText` 20 | 21 | They're well documented in [main.js](./src/main.js). 22 | 23 | It also exports a [Flow type](https://flowtype.org/) for `Option`, if you're 24 | into that sort of thing. 25 | 26 | **Usage examples coming… soon?** 27 | 28 | ## License 29 | 30 | [MIT](./LICENSE) 31 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2017 Khan Academy. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /dist/fuzzy-match-utils.min.js: -------------------------------------------------------------------------------- 1 | "use strict";Object.defineProperty(exports,"__esModule",{value:true});exports.filterOptions=filterOptions;exports.typeaheadSimilarity=typeaheadSimilarity;exports.fullStringDistance=fullStringDistance;exports.cleanUpText=cleanUpText;function filterOptions(r,e,t){if(!e){return r}var n=cleanUpText(e,t);return r.filter(function(r){var e=r.label,t=r.value;return e!=null&&t!=null}).map(function(r){return{option:r,score:typeaheadSimilarity(cleanUpText(r.label,t),n)}}).filter(function(r){return r.score>=n.length-2}).sort(function(r,e){return e.score-r.score}).map(function(r){return r.option})}function typeaheadSimilarity(r,e){var t=r.length;var n=e.length;var a=[];if(!t||!n){return 0}if(t (http://rileyjshaw.com)", 36 | "license": "MIT", 37 | "bugs": { 38 | "url": "https://github.com/Khan/fuzzy-match-utils/issues" 39 | }, 40 | "homepage": "https://github.com/Khan/fuzzy-match-utils#readme", 41 | "devDependencies": { 42 | "babel-cli": "^6.24.0", 43 | "babel-eslint": "^7.2.1", 44 | "babel-plugin-transform-flow-strip-types": "^6.22.0", 45 | "babel-preset-latest": "^6.24.0", 46 | "eslint": "^3.18.0", 47 | "eslint-plugin-flowtype": "^2.30.4", 48 | "eslint-plugin-react": "^6.10.3", 49 | "flow-bin": "^0.42.0", 50 | "uglify-js": "^2.8.16" 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "parser": "babel-eslint", 3 | "env": { 4 | "browser": true, 5 | "node": true, 6 | "es6": true 7 | }, 8 | "rules": { 9 | "array-bracket-spacing": [2, "never"], 10 | "brace-style": [2, "1tbs", {"allowSingleLine": false}], 11 | "camelcase": [2, {"properties": "never"}], 12 | "comma-dangle": [2, "always-multiline"], 13 | "comma-spacing": [2, {"before": false, "after": true}], 14 | "curly": 2, 15 | "eol-last": 2, 16 | "eqeqeq": [2, "allow-null"], 17 | "guard-for-in": 2, 18 | "indent": [2, 4, {"SwitchCase": 1}], 19 | "keyword-spacing": 2, 20 | "linebreak-style": [2, "unix"], 21 | "max-len": [2, 80, 4, {"ignoreUrls": true, "ignorePattern": "\\brequire(?:WithVars)?\\([\"']|eslint-disable"}], 22 | "max-lines": [2, 1000], 23 | "no-alert": 2, 24 | "no-array-constructor": 2, 25 | "no-console": 2, 26 | "no-debugger": 2, 27 | "no-dupe-class-members": 2, 28 | "no-dupe-keys": 2, 29 | "no-extra-bind": 2, 30 | "no-new": 2, 31 | "no-new-func": 2, 32 | "no-new-object": 2, 33 | "no-spaced-func": 2, 34 | "no-throw-literal": 2, 35 | "no-trailing-spaces": 2, 36 | "no-undef": 2, 37 | "no-unexpected-multiline": 2, 38 | "no-unreachable": 2, 39 | "no-unused-vars": [2, {"args": "none", "varsIgnorePattern": "^_*$"}], 40 | "no-useless-call": 2, 41 | "no-with": 2, 42 | "object-curly-spacing": 2, 43 | "one-var": [2, "never"], 44 | "semi": [2, "always"], 45 | "space-before-function-paren": [2, "never"], 46 | "space-in-parens": 2, 47 | "space-infix-ops": 2, 48 | "space-unary-ops": 2, 49 | "no-case-declarations": 0, 50 | "valid-jsdoc": 0, 51 | "require-jsdoc": 0, 52 | // --------------------------------------- 53 | // ES6 rules. 54 | "arrow-spacing": 2, 55 | "computed-property-spacing": 2, 56 | "constructor-super": 2, 57 | "no-const-assign": 2, 58 | "no-this-before-super": 2, 59 | "no-var": 2, 60 | "prefer-const": 2, 61 | "prefer-spread": 2, 62 | "prefer-template": 0, 63 | "arrow-parens": 0, 64 | "prefer-arrow-callback": 0, 65 | // --------------------------------------- 66 | // React rules. 67 | "react/forbid-prop-types": [2, { "forbid": [ "array", "object" ] }], 68 | "react/jsx-closing-bracket-location": [2, "line-aligned"], 69 | "react/jsx-curly-spacing": [2, "never"], 70 | "react/jsx-indent-props": 2, 71 | "react/jsx-no-duplicate-props": 2, 72 | "react/jsx-no-undef": 2, 73 | "react/jsx-uses-react": 2, 74 | "react/jsx-uses-vars": 2, 75 | "react/no-did-mount-set-state": [2], 76 | "react/no-did-update-set-state": 2, 77 | "react/no-direct-mutation-state": 2, 78 | "react/prop-types": 2, 79 | "react/self-closing-comp": 2, 80 | "react/sort-comp": 2, 81 | "react/jsx-sort-props": 0, 82 | "react/sort-prop-types": 0 83 | }, 84 | "ecmaFeatures": { 85 | "arrowFunctions": true, 86 | "blockBindings": true, 87 | "classes": true, 88 | "destructuring": true, 89 | "experimentalObjectRestSpread": true, 90 | "forOf": true, 91 | "jsx": true, 92 | "restParams": true, 93 | "spread": true, 94 | "templateStrings": true 95 | }, 96 | "plugins": [ 97 | "react" 98 | ] 99 | } 100 | -------------------------------------------------------------------------------- /src/main.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | /** 3 | * A collection of string matching algorithms built with React Select in mind. 4 | */ 5 | 6 | // Option type from React Select and similar libraries. 7 | export type Option = { 8 | label?: string, 9 | value?: any, 10 | }; 11 | 12 | type MapOfStrings = {[key: string]: string}; 13 | 14 | 15 | /** 16 | * Filters React Select options and sorts by similarity to a search filter. 17 | * Handles partial matches, eg. searching for "Waberg High" will find "Raoul 18 | * Wallenberg Traditional High School". Case insensitive. Ignores 19 | * non-alphanumeric characters. 20 | * 21 | * @param options An unfiltered list of Options. 22 | * @param? filter A string to compare against Option labels. 23 | * @param? substitutions Strings with multiple spellings or variations that we 24 | * expect to match, eg. accented characters or abbreviated words. 25 | * 26 | * @return A filtered and sorted array of Options. 27 | */ 28 | export function filterOptions( 29 | options: Array