├── .circleci └── config.yml ├── .gitignore ├── LICENSE ├── README.md ├── app ├── .gitignore ├── build.gradle ├── package-lock.json ├── proguard-rules.pro └── src │ ├── commonMain │ └── kotlin │ │ └── com │ │ └── willowtreeapps │ │ └── fuzzywuzzy │ │ ├── Ration.kt │ │ ├── ToStringFunction.kt │ │ └── diffutils │ │ ├── Applicable.kt │ │ ├── DiffUtils.kt │ │ ├── Extractor.kt │ │ ├── FuzzySearch.kt │ │ ├── PriorityQueue.kt │ │ ├── algorithms │ │ ├── BasicAlgorithm.kt │ │ ├── DefaultStringFunction.kt │ │ ├── PrimitiveUtils.kt │ │ ├── RatioAlgorithm.kt │ │ ├── SetUtils.kt │ │ ├── TokenSet.kt │ │ ├── TokenSort.kt │ │ ├── Utils.kt │ │ └── WeightedRatio.kt │ │ ├── model │ │ ├── BoundExtractedResult.kt │ │ └── ExtractedResult.kt │ │ ├── ratio │ │ ├── PartialRatio.kt │ │ └── SimpleRatio.kt │ │ └── structs │ │ └── EditOps.kt │ ├── jsMain │ └── kotlin │ │ └── com │ │ └── willowtreeapps │ │ └── fuzzywuzzy │ │ ├── algorithms │ │ └── PlatformImpls.kt │ │ └── diffutils │ │ └── algorithms │ │ └── pattern.kt │ ├── jvmMain │ └── kotlin │ │ └── com │ │ └── willowtreeapps │ │ └── fuzzywuzzy │ │ └── diffutils │ │ └── algorithms │ │ └── pattern.kt │ ├── nativeMain │ └── kotlin │ │ └── com │ │ └── willowtreeapps │ │ └── fuzzywuzzy │ │ └── diffutils │ │ └── algorithms │ │ └── pattern.kt │ ├── test │ └── kotlin │ │ └── com │ │ └── willowtree │ │ └── fuzzywuzzy │ │ ├── ExtractorTest.kt │ │ ├── FuzzyWuzzyTest.kt │ │ └── algorithms │ │ └── DefaultStringProcessorTest.kt │ └── wasmMain │ └── kotlin │ └── com │ └── willowtreeapps │ └── fuzzywuzzy │ └── diffutils │ └── algorithms │ └── pattern.kt ├── build.gradle ├── gradle.properties ├── gradle ├── dependencies.gradle ├── pom.gradle ├── publish.gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat └── settings.gradle /.circleci/config.yml: -------------------------------------------------------------------------------- 1 | # Java Gradle CircleCI 2.0 configuration file 2 | # 3 | # Check https://circleci.com/docs/2.0/language-java/ for more details 4 | # 5 | version: 2 6 | jobs: 7 | build: 8 | docker: 9 | # specify the version you desire here 10 | - image: circleci/openjdk:8-jdk 11 | 12 | # Specify service dependencies here if necessary 13 | # CircleCI maintains a library of pre-built images 14 | # documented at https://circleci.com/docs/2.0/circleci-images/ 15 | # - image: circleci/postgres:9.4 16 | 17 | working_directory: ~/repo 18 | 19 | environment: 20 | # Customize the JVM maximum heap limit 21 | JVM_OPTS: -Xmx3200m 22 | TERM: dumb 23 | 24 | steps: 25 | - checkout 26 | 27 | # Download and cache dependencies 28 | - restore_cache: 29 | keys: 30 | - v1-dependencies-{{ checksum "build.gradle" }} 31 | # fallback to using the latest cache if no exact match is found 32 | - v1-dependencies- 33 | 34 | - run: gradle dependencies 35 | 36 | - save_cache: 37 | paths: 38 | - ~/.gradle 39 | key: v1-dependencies-{{ checksum "build.gradle" }} 40 | 41 | # run tests! 42 | - run: gradle app:jvmTest 43 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .gradle 3 | /local.properties 4 | /.idea/libraries 5 | /.idea/modules.xml 6 | /.idea/workspace.xml 7 | /.idea 8 | /iOS/NameGame/.idea/ 9 | .DS_Store 10 | /build 11 | /common/build 12 | /captures 13 | .externalNativeBuild 14 | node_modules/ 15 | 16 | # Created by https://www.gitignore.io/api/swift,xcode 17 | 18 | ### Swift ### 19 | # Xcode 20 | # 21 | # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore 22 | 23 | ## Build generated 24 | build/ 25 | DerivedData/ 26 | 27 | 28 | ## Various settings 29 | *.pbxuser 30 | !default.pbxuser 31 | *.mode1v3 32 | !default.mode1v3 33 | *.mode2v3 34 | !default.mode2v3 35 | *.perspectivev3 36 | !default.perspectivev3 37 | xcuserdata/ 38 | 39 | ## Other 40 | *.moved-aside 41 | *.xccheckout 42 | *.xcscmblueprint 43 | 44 | ## Obj-C/Swift specific 45 | *.hmap 46 | *.ipa 47 | *.dSYM.zip 48 | *.dSYM 49 | 50 | ## Playgrounds 51 | timeline.xctimeline 52 | playground.xcworkspace 53 | 54 | # Swift Package Manager 55 | # 56 | # Add this line if you want to avoid checking in source code from Swift Package Manager dependencies. 57 | # Packages/ 58 | # Package.pins 59 | .build/ 60 | 61 | # CocoaPods - Refactored to standalone file 62 | 63 | # Carthage - Refactored to standalone file 64 | 65 | # fastlane 66 | # 67 | # It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the 68 | # screenshots whenever they are needed. 69 | # For more information about the recommended setup visit: 70 | # https://docs.fastlane.tools/best-practices/source-control/#source-control 71 | 72 | fastlane/report.xml 73 | fastlane/Preview.html 74 | fastlane/screenshots 75 | fastlane/test_output 76 | 77 | ### Xcode ### 78 | # Xcode 79 | # 80 | # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore 81 | 82 | ## Build generated 83 | 84 | ## Various settings 85 | 86 | ## Other 87 | 88 | 89 | # End of https://www.gitignore.io/api/swift,xcode 90 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "{}" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright {yyyy} {name of copyright owner} 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # FuzzyWuzzy-Kotlin 2 | 3 | ![badge][badge-android] 4 | ![badge][badge-native] 5 | ![badge][badge-js] 6 | ![badge][badge-jvm] 7 | ![badge][badge-linux] 8 | ![badge][badge-windows] 9 | ![badge][badge-mac] 10 | ![badge][badge-wasm] 11 | 12 | [![CircleCI](https://circleci.com/gh/willowtreeapps/fuzzywuzzy-kotlin.svg?style=svg)](https://circleci.com/gh/willowtreeapps/fuzzywuzzy-kotlin) 13 | 14 | Fuzzy string matching for Kotlin (JVM, iOS) - fork of [the Java fork](https://github.com/xdrop/fuzzywuzzy) of of [Fuzzy Wuzzy Python lib](https://github.com/seatgeek/fuzzywuzzy). For use in on JVM, Android, or Kotlin Multiplatform projects (JVM/Android, iOS, mac, linux) 15 | 16 | Useful for selecting the closest matching string from a collection of strings. Various algorithms are available. 17 | 18 | See Java repo or Python repo for usage. 19 | 20 | To add to project in the common module add the dependency: 21 | 22 | ``` 23 | sourceSets { 24 | commonMain { 25 | dependencies { 26 | implementation "com.willowtreeapps:fuzzywuzzy-kotlin:0.1.1" 27 | } 28 | } 29 | } 30 | ``` 31 | [badge-android]: http://img.shields.io/badge/platform-android-brightgreen.svg?style=flat 32 | [badge-native]: http://img.shields.io/badge/platform-native-lightgrey.svg?style=flat 33 | [badge-native]: http://img.shields.io/badge/platform-native-lightgrey.svg?style=flat 34 | [badge-js]: http://img.shields.io/badge/platform-js-yellow.svg?style=flat 35 | [badge-js]: http://img.shields.io/badge/platform-js-yellow.svg?style=flat 36 | [badge-jvm]: http://img.shields.io/badge/platform-jvm-orange.svg?style=flat 37 | [badge-jvm]: http://img.shields.io/badge/platform-jvm-orange.svg?style=flat 38 | [badge-linux]: http://img.shields.io/badge/platform-linux-important.svg?style=flat 39 | [badge-linux]: http://img.shields.io/badge/platform-linux-important.svg?style=flat 40 | [badge-windows]: http://img.shields.io/badge/platform-windows-informational.svg?style=flat 41 | [badge-windows]: http://img.shields.io/badge/platform-windows-informational.svg?style=flat 42 | [badge-mac]: http://img.shields.io/badge/platform-macos-lightgrey.svg?style=flat 43 | [badge-mac]: http://img.shields.io/badge/platform-macos-lightgrey.svg?style=flat 44 | [badge-wasm]: https://img.shields.io/badge/platform-wasm-darkblue.svg?style=flat 45 | -------------------------------------------------------------------------------- /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /app/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'java' 2 | apply plugin: 'kotlin-multiplatform' 3 | apply plugin: 'com.moowork.node' 4 | 5 | archivesBaseName = 'fuzzywuzzy-kotlin' 6 | 7 | group 'com.willowtreeapps' 8 | version '0.1.1' 9 | final nodeVersion = '11.2.0' 10 | final nodeWorkingDir = project.buildDir 11 | final nodeModules = "$nodeWorkingDir/node_modules" 12 | final mochaVersion = '5.2.0' 13 | final pathSeparator = System.properties["path.separator"] 14 | 15 | kotlin { 16 | jvm() 17 | js() { 18 | [compileKotlinJs, compileTestKotlinJs].each { configuration -> 19 | configuration.kotlinOptions { 20 | moduleKind = 'umd' 21 | sourceMap = true 22 | metaInfo = true 23 | } 24 | } 25 | } 26 | 27 | iosArm64("ios") 28 | iosX64("iosSim") 29 | macosX64("macos") 30 | mingwX64("win") 31 | wasm32("wasm") 32 | linuxArm32Hfp("linArm32") 33 | linuxMips32("linMips32") 34 | linuxMipsel32("linMipsel32") 35 | linuxX64("lin64") 36 | 37 | sourceSets { 38 | commonMain { 39 | dependencies { 40 | implementation kotlin("stdlib-common") 41 | } 42 | } 43 | commonTest { 44 | kotlin.srcDir('src/test') 45 | dependencies { 46 | implementation kotlin("test-common") 47 | implementation kotlin("test-annotations-common") 48 | } 49 | } 50 | 51 | jvmMain { 52 | kotlin.srcDir('src/jvmMain/kotlin') 53 | dependencies { 54 | implementation kotlin("stdlib") 55 | } 56 | } 57 | jvmTest { 58 | dependencies { 59 | implementation kotlin("test") 60 | implementation kotlin("test-junit") 61 | implementation 'junit:junit:4.12' 62 | } 63 | } 64 | jsMain { 65 | kotlin.srcDir('src/jsMain/kotlin') 66 | dependencies { 67 | implementation kotlin("stdlib-js") 68 | } 69 | compileKotlinJs { 70 | kotlinOptions.metaInfo = true 71 | kotlinOptions.sourceMap = true 72 | kotlinOptions.suppressWarnings = true 73 | kotlinOptions.verbose = true 74 | kotlinOptions.main = "call" 75 | kotlinOptions.moduleKind = "umd" 76 | } 77 | } 78 | jsTest { 79 | dependencies { 80 | implementation kotlin("test-js") 81 | implementation kotlin("stdlib-js") 82 | } 83 | } 84 | nativeMain { 85 | kotlin.srcDir('src/nativeMain/kotlin') 86 | } 87 | 88 | iosSimMain.dependsOn iosMain 89 | iosSimTest.dependsOn iosTest 90 | 91 | configure([targets.ios, targets.iosSim, targets.macos, targets.win, targets.linArm32, targets.linMips32, targets.linMipsel32, targets.lin64]) { 92 | compilations.main.source(sourceSets.nativeMain) 93 | } 94 | } 95 | } 96 | 97 | //Workaround to copy kotlin libraries so they are visible during testing 98 | def jsLibDir = "$compileKotlinJs.destinationDir/lib" 99 | def jsTestLibDir = "$compileTestKotlinJs.destinationDir/lib" 100 | 101 | //uncomment below to test JS. This conflicts with iOS config 102 | /* 103 | configurations { 104 | jsLibs 105 | jsTestLibs 106 | } 107 | dependencies { 108 | jsLibs "org.jetbrains.kotlin:kotlin-stdlib-js:$kotlinRuntimeVersion" 109 | jsTestLibs "org.jetbrains.kotlin:kotlin-test-js:$kotlinRuntimeVersion" 110 | } 111 | 112 | task copyJsDependencies(type: Copy, dependsOn: compileKotlinJs) { 113 | configurations.jsLibs.each { 114 | from zipTree(it.absolutePath).matching { include '*.js'} 115 | } 116 | into jsLibDir 117 | } 118 | jsMainClasses.dependsOn copyJsDependencies 119 | task copyJsTestDependencies(type: Copy) { 120 | configurations.jsTestLibs.each { 121 | from zipTree(it.absolutePath).matching { include '*.js'} 122 | } 123 | into jsTestLibDir 124 | } 125 | jsTestClasses.dependsOn copyJsTestDependencies 126 | 127 | */ 128 | 129 | 130 | 131 | node { 132 | version = nodeVersion 133 | download = true 134 | workDir = file("$project.buildDir/nodejs") 135 | nodeModulesDir = file(nodeWorkingDir) 136 | } 137 | task installMocha(type: NpmTask, group: 'npm') { 138 | outputs.dir "$nodeModules/mocha" 139 | args = ['install', "mocha@$mochaVersion"] 140 | } 141 | task runMocha(type: NodeTask, dependsOn: [installMocha, jsMainClasses, jsTestClasses], group: 'npm') { 142 | environment = ["NODE_PATH": "$jsLibDir$pathSeparator$jsTestLibDir$pathSeparator$compileKotlinJs.destinationDir"] 143 | script = file("$nodeWorkingDir/node_modules/mocha/bin/mocha") 144 | args = [compileTestKotlinJs.outputFile] 145 | } 146 | //Use mocha to run js tests 147 | jsTest.dependsOn runMocha 148 | 149 | task iosTest(dependsOn: 'linkTestDebugExecutableIosSim') { 150 | doLast { 151 | def binary = kotlin.targets.iosSim.compilations.test.getBinary('EXECUTABLE', 'DEBUG') 152 | exec { 153 | commandLine 'xcrun', 'simctl', 'spawn', "iPhone XR", binary.absolutePath 154 | } 155 | } 156 | } 157 | 158 | tasks.check.dependsOn iosTest 159 | 160 | // workaround for https://youtrack.jetbrains.com/issue/KT-27170 161 | //configurations { 162 | // compileClasspath 163 | //} 164 | 165 | afterEvaluate { 166 | // Alias the task names we use elsewhere to the new task names. 167 | tasks.create('installMP').dependsOn('publishKotlinMultiplatformPublicationToMavenLocal') 168 | tasks.create('installLocally') { 169 | dependsOn 'publishKotlinMultiplatformPublicationToTestRepository' 170 | dependsOn 'publishJvmPublicationToTestRepository' 171 | dependsOn 'publishJsPublicationToTestRepository' 172 | dependsOn 'publishMetadataPublicationToTestRepository' 173 | } 174 | tasks.create('installIosLocally') { 175 | dependsOn 'publishKotlinMultiplatformPublicationToTestRepository' 176 | dependsOn 'publishIosArm32PublicationToTestRepository' 177 | dependsOn 'publishIosArm64PublicationToTestRepository' 178 | dependsOn 'publishIosX64PublicationToTestRepository' 179 | dependsOn 'publishMetadataPublicationToTestRepository' 180 | } 181 | // NOTE: We do not alias uploadArchives because CI runs it on Linux and we only want to run it on Mac OS. 182 | //tasks.create('uploadArchives').dependsOn('publishKotlinMultiplatformPublicationToMavenRepository') 183 | } 184 | 185 | apply from: rootProject.file('gradle/publish.gradle') 186 | 187 | publishing { 188 | publications.all { 189 | // Rewrite all artifacts from using the project name to just 'runtime'. 190 | artifactId = artifactId.replace(project.name, 'fuzzywuzzy-kotlin') 191 | } 192 | } 193 | 194 | -------------------------------------------------------------------------------- /app/package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "requires": true, 3 | "lockfileVersion": 1, 4 | "dependencies": { 5 | "ansi-colors": { 6 | "version": "3.2.3", 7 | "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-3.2.3.tgz", 8 | "integrity": "sha512-LEHHyuhlPY3TmuUYMh2oz89lTShfvgbmzaBcxve9t/9Wuy7Dwf4yoAKcND7KFT1HAQfqZ12qtc+DUrBMeKF9nw==" 9 | }, 10 | "ansi-regex": { 11 | "version": "3.0.0", 12 | "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", 13 | "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=" 14 | }, 15 | "ansi-styles": { 16 | "version": "3.2.1", 17 | "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", 18 | "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", 19 | "requires": { 20 | "color-convert": "1.9.3" 21 | } 22 | }, 23 | "argparse": { 24 | "version": "1.0.10", 25 | "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", 26 | "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", 27 | "requires": { 28 | "sprintf-js": "1.0.3" 29 | } 30 | }, 31 | "balanced-match": { 32 | "version": "1.0.0", 33 | "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", 34 | "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=" 35 | }, 36 | "brace-expansion": { 37 | "version": "1.1.11", 38 | "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", 39 | "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", 40 | "requires": { 41 | "balanced-match": "1.0.0", 42 | "concat-map": "0.0.1" 43 | } 44 | }, 45 | "browser-stdout": { 46 | "version": "1.3.1", 47 | "resolved": "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.1.tgz", 48 | "integrity": "sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==" 49 | }, 50 | "camelcase": { 51 | "version": "5.3.1", 52 | "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", 53 | "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==" 54 | }, 55 | "chalk": { 56 | "version": "2.4.2", 57 | "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", 58 | "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", 59 | "requires": { 60 | "ansi-styles": "3.2.1", 61 | "escape-string-regexp": "1.0.5", 62 | "supports-color": "5.5.0" 63 | }, 64 | "dependencies": { 65 | "supports-color": { 66 | "version": "5.5.0", 67 | "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", 68 | "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", 69 | "requires": { 70 | "has-flag": "3.0.0" 71 | } 72 | } 73 | } 74 | }, 75 | "cliui": { 76 | "version": "4.1.0", 77 | "resolved": "https://registry.npmjs.org/cliui/-/cliui-4.1.0.tgz", 78 | "integrity": "sha512-4FG+RSG9DL7uEwRUZXZn3SS34DiDPfzP0VOiEwtUWlE+AR2EIg+hSyvrIgUUfhdgR/UkAeW2QHgeP+hWrXs7jQ==", 79 | "requires": { 80 | "string-width": "2.1.1", 81 | "strip-ansi": "4.0.0", 82 | "wrap-ansi": "2.1.0" 83 | } 84 | }, 85 | "code-point-at": { 86 | "version": "1.1.0", 87 | "resolved": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz", 88 | "integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=" 89 | }, 90 | "color-convert": { 91 | "version": "1.9.3", 92 | "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", 93 | "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", 94 | "requires": { 95 | "color-name": "1.1.3" 96 | } 97 | }, 98 | "color-name": { 99 | "version": "1.1.3", 100 | "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", 101 | "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=" 102 | }, 103 | "concat-map": { 104 | "version": "0.0.1", 105 | "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", 106 | "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=" 107 | }, 108 | "cross-spawn": { 109 | "version": "6.0.5", 110 | "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz", 111 | "integrity": "sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==", 112 | "requires": { 113 | "nice-try": "1.0.5", 114 | "path-key": "2.0.1", 115 | "semver": "5.7.0", 116 | "shebang-command": "1.2.0", 117 | "which": "1.3.1" 118 | } 119 | }, 120 | "debug": { 121 | "version": "3.2.6", 122 | "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz", 123 | "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==", 124 | "requires": { 125 | "ms": "2.1.1" 126 | } 127 | }, 128 | "decamelize": { 129 | "version": "1.2.0", 130 | "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", 131 | "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=" 132 | }, 133 | "define-properties": { 134 | "version": "1.1.3", 135 | "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.3.tgz", 136 | "integrity": "sha512-3MqfYKj2lLzdMSf8ZIZE/V+Zuy+BgD6f164e8K2w7dgnpKArBDerGYpM46IYYcjnkdPNMjPk9A6VFB8+3SKlXQ==", 137 | "requires": { 138 | "object-keys": "1.1.1" 139 | } 140 | }, 141 | "diff": { 142 | "version": "3.5.0", 143 | "resolved": "https://registry.npmjs.org/diff/-/diff-3.5.0.tgz", 144 | "integrity": "sha512-A46qtFgd+g7pDZinpnwiRJtxbC1hpgf0uzP3iG89scHk0AUC7A1TGxf5OiiOUv/JMZR8GOt8hL900hV0bOy5xA==" 145 | }, 146 | "emoji-regex": { 147 | "version": "7.0.3", 148 | "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-7.0.3.tgz", 149 | "integrity": "sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==" 150 | }, 151 | "end-of-stream": { 152 | "version": "1.4.1", 153 | "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.1.tgz", 154 | "integrity": "sha512-1MkrZNvWTKCaigbn+W15elq2BB/L22nqrSY5DKlo3X6+vclJm8Bb5djXJBmEX6fS3+zCh/F4VBK5Z2KxJt4s2Q==", 155 | "requires": { 156 | "once": "1.4.0" 157 | } 158 | }, 159 | "es-abstract": { 160 | "version": "1.13.0", 161 | "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.13.0.tgz", 162 | "integrity": "sha512-vDZfg/ykNxQVwup/8E1BZhVzFfBxs9NqMzGcvIJrqg5k2/5Za2bWo40dK2J1pgLngZ7c+Shh8lwYtLGyrwPutg==", 163 | "requires": { 164 | "es-to-primitive": "1.2.0", 165 | "function-bind": "1.1.1", 166 | "has": "1.0.3", 167 | "is-callable": "1.1.4", 168 | "is-regex": "1.0.4", 169 | "object-keys": "1.1.1" 170 | } 171 | }, 172 | "es-to-primitive": { 173 | "version": "1.2.0", 174 | "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.0.tgz", 175 | "integrity": "sha512-qZryBOJjV//LaxLTV6UC//WewneB3LcXOL9NP++ozKVXsIIIpm/2c13UDiD9Jp2eThsecw9m3jPqDwTyobcdbg==", 176 | "requires": { 177 | "is-callable": "1.1.4", 178 | "is-date-object": "1.0.1", 179 | "is-symbol": "1.0.2" 180 | } 181 | }, 182 | "escape-string-regexp": { 183 | "version": "1.0.5", 184 | "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", 185 | "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=" 186 | }, 187 | "esprima": { 188 | "version": "4.0.1", 189 | "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", 190 | "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==" 191 | }, 192 | "execa": { 193 | "version": "1.0.0", 194 | "resolved": "https://registry.npmjs.org/execa/-/execa-1.0.0.tgz", 195 | "integrity": "sha512-adbxcyWV46qiHyvSp50TKt05tB4tK3HcmF7/nxfAdhnox83seTDbwnaqKO4sXRy7roHAIFqJP/Rw/AuEbX61LA==", 196 | "requires": { 197 | "cross-spawn": "6.0.5", 198 | "get-stream": "4.1.0", 199 | "is-stream": "1.1.0", 200 | "npm-run-path": "2.0.2", 201 | "p-finally": "1.0.0", 202 | "signal-exit": "3.0.2", 203 | "strip-eof": "1.0.0" 204 | } 205 | }, 206 | "find-up": { 207 | "version": "3.0.0", 208 | "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", 209 | "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", 210 | "requires": { 211 | "locate-path": "3.0.0" 212 | } 213 | }, 214 | "flat": { 215 | "version": "4.1.0", 216 | "resolved": "https://registry.npmjs.org/flat/-/flat-4.1.0.tgz", 217 | "integrity": "sha512-Px/TiLIznH7gEDlPXcUD4KnBusa6kR6ayRUVcnEAbreRIuhkqow/mun59BuRXwoYk7ZQOLW1ZM05ilIvK38hFw==", 218 | "requires": { 219 | "is-buffer": "2.0.3" 220 | } 221 | }, 222 | "fs.realpath": { 223 | "version": "1.0.0", 224 | "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", 225 | "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=" 226 | }, 227 | "function-bind": { 228 | "version": "1.1.1", 229 | "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", 230 | "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==" 231 | }, 232 | "get-caller-file": { 233 | "version": "2.0.5", 234 | "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", 235 | "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==" 236 | }, 237 | "get-stream": { 238 | "version": "4.1.0", 239 | "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-4.1.0.tgz", 240 | "integrity": "sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w==", 241 | "requires": { 242 | "pump": "3.0.0" 243 | } 244 | }, 245 | "glob": { 246 | "version": "7.1.3", 247 | "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.3.tgz", 248 | "integrity": "sha512-vcfuiIxogLV4DlGBHIUOwI0IbrJ8HWPc4MU7HzviGeNho/UJDfi6B5p3sHeWIQ0KGIU0Jpxi5ZHxemQfLkkAwQ==", 249 | "requires": { 250 | "fs.realpath": "1.0.0", 251 | "inflight": "1.0.6", 252 | "inherits": "2.0.3", 253 | "minimatch": "3.0.4", 254 | "once": "1.4.0", 255 | "path-is-absolute": "1.0.1" 256 | } 257 | }, 258 | "growl": { 259 | "version": "1.10.5", 260 | "resolved": "https://registry.npmjs.org/growl/-/growl-1.10.5.tgz", 261 | "integrity": "sha512-qBr4OuELkhPenW6goKVXiv47US3clb3/IbuWF9KNKEijAy9oeHxU9IgzjvJhHkUzhaj7rOUD7+YGWqUjLp5oSA==" 262 | }, 263 | "has": { 264 | "version": "1.0.3", 265 | "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", 266 | "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", 267 | "requires": { 268 | "function-bind": "1.1.1" 269 | } 270 | }, 271 | "has-flag": { 272 | "version": "3.0.0", 273 | "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", 274 | "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=" 275 | }, 276 | "has-symbols": { 277 | "version": "1.0.0", 278 | "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.0.tgz", 279 | "integrity": "sha1-uhqPGvKg/DllD1yFA2dwQSIGO0Q=" 280 | }, 281 | "he": { 282 | "version": "1.2.0", 283 | "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", 284 | "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==" 285 | }, 286 | "inflight": { 287 | "version": "1.0.6", 288 | "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", 289 | "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", 290 | "requires": { 291 | "once": "1.4.0", 292 | "wrappy": "1.0.2" 293 | } 294 | }, 295 | "inherits": { 296 | "version": "2.0.3", 297 | "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", 298 | "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=" 299 | }, 300 | "invert-kv": { 301 | "version": "2.0.0", 302 | "resolved": "https://registry.npmjs.org/invert-kv/-/invert-kv-2.0.0.tgz", 303 | "integrity": "sha512-wPVv/y/QQ/Uiirj/vh3oP+1Ww+AWehmi1g5fFWGPF6IpCBCDVrhgHRMvrLfdYcwDh3QJbGXDW4JAuzxElLSqKA==" 304 | }, 305 | "is-buffer": { 306 | "version": "2.0.3", 307 | "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-2.0.3.tgz", 308 | "integrity": "sha512-U15Q7MXTuZlrbymiz95PJpZxu8IlipAp4dtS3wOdgPXx3mqBnslrWU14kxfHB+Py/+2PVKSr37dMAgM2A4uArw==" 309 | }, 310 | "is-callable": { 311 | "version": "1.1.4", 312 | "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.1.4.tgz", 313 | "integrity": "sha512-r5p9sxJjYnArLjObpjA4xu5EKI3CuKHkJXMhT7kwbpUyIFD1n5PMAsoPvWnvtZiNz7LjkYDRZhd7FlI0eMijEA==" 314 | }, 315 | "is-date-object": { 316 | "version": "1.0.1", 317 | "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.1.tgz", 318 | "integrity": "sha1-mqIOtq7rv/d/vTPnTKAbM1gdOhY=" 319 | }, 320 | "is-fullwidth-code-point": { 321 | "version": "2.0.0", 322 | "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", 323 | "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=" 324 | }, 325 | "is-regex": { 326 | "version": "1.0.4", 327 | "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.0.4.tgz", 328 | "integrity": "sha1-VRdIm1RwkbCTDglWVM7SXul+lJE=", 329 | "requires": { 330 | "has": "1.0.3" 331 | } 332 | }, 333 | "is-stream": { 334 | "version": "1.1.0", 335 | "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz", 336 | "integrity": "sha1-EtSj3U5o4Lec6428hBc66A2RykQ=" 337 | }, 338 | "is-symbol": { 339 | "version": "1.0.2", 340 | "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.2.tgz", 341 | "integrity": "sha512-HS8bZ9ox60yCJLH9snBpIwv9pYUAkcuLhSA1oero1UB5y9aiQpRA8y2ex945AOtCZL1lJDeIk3G5LthswI46Lw==", 342 | "requires": { 343 | "has-symbols": "1.0.0" 344 | } 345 | }, 346 | "isexe": { 347 | "version": "2.0.0", 348 | "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", 349 | "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=" 350 | }, 351 | "js-yaml": { 352 | "version": "3.13.1", 353 | "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.13.1.tgz", 354 | "integrity": "sha512-YfbcO7jXDdyj0DGxYVSlSeQNHbD7XPWvrVWeVUujrQEoZzWJIRrCPoyk6kL6IAjAG2IolMK4T0hNUe0HOUs5Jw==", 355 | "requires": { 356 | "argparse": "1.0.10", 357 | "esprima": "4.0.1" 358 | } 359 | }, 360 | "lcid": { 361 | "version": "2.0.0", 362 | "resolved": "https://registry.npmjs.org/lcid/-/lcid-2.0.0.tgz", 363 | "integrity": "sha512-avPEb8P8EGnwXKClwsNUgryVjllcRqtMYa49NTsbQagYuT1DcXnl1915oxWjoyGrXR6zH/Y0Zc96xWsPcoDKeA==", 364 | "requires": { 365 | "invert-kv": "2.0.0" 366 | } 367 | }, 368 | "locate-path": { 369 | "version": "3.0.0", 370 | "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", 371 | "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", 372 | "requires": { 373 | "p-locate": "3.0.0", 374 | "path-exists": "3.0.0" 375 | } 376 | }, 377 | "lodash": { 378 | "version": "4.17.11", 379 | "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.11.tgz", 380 | "integrity": "sha512-cQKh8igo5QUhZ7lg38DYWAxMvjSAKG0A8wGSVimP07SIUEK2UO+arSRKbRZWtelMtN5V0Hkwh5ryOto/SshYIg==" 381 | }, 382 | "log-symbols": { 383 | "version": "2.2.0", 384 | "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-2.2.0.tgz", 385 | "integrity": "sha512-VeIAFslyIerEJLXHziedo2basKbMKtTw3vfn5IzG0XTjhAVEJyNHnL2p7vc+wBDSdQuUpNw3M2u6xb9QsAY5Eg==", 386 | "requires": { 387 | "chalk": "2.4.2" 388 | } 389 | }, 390 | "map-age-cleaner": { 391 | "version": "0.1.3", 392 | "resolved": "https://registry.npmjs.org/map-age-cleaner/-/map-age-cleaner-0.1.3.tgz", 393 | "integrity": "sha512-bJzx6nMoP6PDLPBFmg7+xRKeFZvFboMrGlxmNj9ClvX53KrmvM5bXFXEWjbz4cz1AFn+jWJ9z/DJSz7hrs0w3w==", 394 | "requires": { 395 | "p-defer": "1.0.0" 396 | } 397 | }, 398 | "mem": { 399 | "version": "4.3.0", 400 | "resolved": "https://registry.npmjs.org/mem/-/mem-4.3.0.tgz", 401 | "integrity": "sha512-qX2bG48pTqYRVmDB37rn/6PT7LcR8T7oAX3bf99u1Tt1nzxYfxkgqDwUwolPlXweM0XzBOBFzSx4kfp7KP1s/w==", 402 | "requires": { 403 | "map-age-cleaner": "0.1.3", 404 | "mimic-fn": "2.1.0", 405 | "p-is-promise": "2.1.0" 406 | } 407 | }, 408 | "mimic-fn": { 409 | "version": "2.1.0", 410 | "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", 411 | "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==" 412 | }, 413 | "minimatch": { 414 | "version": "3.0.4", 415 | "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", 416 | "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", 417 | "requires": { 418 | "brace-expansion": "1.1.11" 419 | } 420 | }, 421 | "minimist": { 422 | "version": "0.0.8", 423 | "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", 424 | "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=" 425 | }, 426 | "mkdirp": { 427 | "version": "0.5.1", 428 | "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", 429 | "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", 430 | "requires": { 431 | "minimist": "0.0.8" 432 | } 433 | }, 434 | "mocha": { 435 | "version": "6.1.4", 436 | "resolved": "https://registry.npmjs.org/mocha/-/mocha-6.1.4.tgz", 437 | "integrity": "sha512-PN8CIy4RXsIoxoFJzS4QNnCH4psUCPWc4/rPrst/ecSJJbLBkubMiyGCP2Kj/9YnWbotFqAoeXyXMucj7gwCFg==", 438 | "requires": { 439 | "ansi-colors": "3.2.3", 440 | "browser-stdout": "1.3.1", 441 | "debug": "3.2.6", 442 | "diff": "3.5.0", 443 | "escape-string-regexp": "1.0.5", 444 | "find-up": "3.0.0", 445 | "glob": "7.1.3", 446 | "growl": "1.10.5", 447 | "he": "1.2.0", 448 | "js-yaml": "3.13.1", 449 | "log-symbols": "2.2.0", 450 | "minimatch": "3.0.4", 451 | "mkdirp": "0.5.1", 452 | "ms": "2.1.1", 453 | "node-environment-flags": "1.0.5", 454 | "object.assign": "4.1.0", 455 | "strip-json-comments": "2.0.1", 456 | "supports-color": "6.0.0", 457 | "which": "1.3.1", 458 | "wide-align": "1.1.3", 459 | "yargs": "13.2.2", 460 | "yargs-parser": "13.0.0", 461 | "yargs-unparser": "1.5.0" 462 | } 463 | }, 464 | "ms": { 465 | "version": "2.1.1", 466 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", 467 | "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==" 468 | }, 469 | "nice-try": { 470 | "version": "1.0.5", 471 | "resolved": "https://registry.npmjs.org/nice-try/-/nice-try-1.0.5.tgz", 472 | "integrity": "sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==" 473 | }, 474 | "node-environment-flags": { 475 | "version": "1.0.5", 476 | "resolved": "https://registry.npmjs.org/node-environment-flags/-/node-environment-flags-1.0.5.tgz", 477 | "integrity": "sha512-VNYPRfGfmZLx0Ye20jWzHUjyTW/c+6Wq+iLhDzUI4XmhrDd9l/FozXV3F2xOaXjvp0co0+v1YSR3CMP6g+VvLQ==", 478 | "requires": { 479 | "object.getownpropertydescriptors": "2.0.3", 480 | "semver": "5.7.0" 481 | } 482 | }, 483 | "npm-run-path": { 484 | "version": "2.0.2", 485 | "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-2.0.2.tgz", 486 | "integrity": "sha1-NakjLfo11wZ7TLLd8jV7GHFTbF8=", 487 | "requires": { 488 | "path-key": "2.0.1" 489 | } 490 | }, 491 | "number-is-nan": { 492 | "version": "1.0.1", 493 | "resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz", 494 | "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=" 495 | }, 496 | "object-keys": { 497 | "version": "1.1.1", 498 | "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", 499 | "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==" 500 | }, 501 | "object.assign": { 502 | "version": "4.1.0", 503 | "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.0.tgz", 504 | "integrity": "sha512-exHJeq6kBKj58mqGyTQ9DFvrZC/eR6OwxzoM9YRoGBqrXYonaFyGiFMuc9VZrXf7DarreEwMpurG3dd+CNyW5w==", 505 | "requires": { 506 | "define-properties": "1.1.3", 507 | "function-bind": "1.1.1", 508 | "has-symbols": "1.0.0", 509 | "object-keys": "1.1.1" 510 | } 511 | }, 512 | "object.getownpropertydescriptors": { 513 | "version": "2.0.3", 514 | "resolved": "https://registry.npmjs.org/object.getownpropertydescriptors/-/object.getownpropertydescriptors-2.0.3.tgz", 515 | "integrity": "sha1-h1jIRvW0B62rDyNuCYbxSwUcqhY=", 516 | "requires": { 517 | "define-properties": "1.1.3", 518 | "es-abstract": "1.13.0" 519 | } 520 | }, 521 | "once": { 522 | "version": "1.4.0", 523 | "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", 524 | "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", 525 | "requires": { 526 | "wrappy": "1.0.2" 527 | } 528 | }, 529 | "os-locale": { 530 | "version": "3.1.0", 531 | "resolved": "https://registry.npmjs.org/os-locale/-/os-locale-3.1.0.tgz", 532 | "integrity": "sha512-Z8l3R4wYWM40/52Z+S265okfFj8Kt2cC2MKY+xNi3kFs+XGI7WXu/I309QQQYbRW4ijiZ+yxs9pqEhJh0DqW3Q==", 533 | "requires": { 534 | "execa": "1.0.0", 535 | "lcid": "2.0.0", 536 | "mem": "4.3.0" 537 | } 538 | }, 539 | "p-defer": { 540 | "version": "1.0.0", 541 | "resolved": "https://registry.npmjs.org/p-defer/-/p-defer-1.0.0.tgz", 542 | "integrity": "sha1-n26xgvbJqozXQwBKfU+WsZaw+ww=" 543 | }, 544 | "p-finally": { 545 | "version": "1.0.0", 546 | "resolved": "https://registry.npmjs.org/p-finally/-/p-finally-1.0.0.tgz", 547 | "integrity": "sha1-P7z7FbiZpEEjs0ttzBi3JDNqLK4=" 548 | }, 549 | "p-is-promise": { 550 | "version": "2.1.0", 551 | "resolved": "https://registry.npmjs.org/p-is-promise/-/p-is-promise-2.1.0.tgz", 552 | "integrity": "sha512-Y3W0wlRPK8ZMRbNq97l4M5otioeA5lm1z7bkNkxCka8HSPjR0xRWmpCmc9utiaLP9Jb1eD8BgeIxTW4AIF45Pg==" 553 | }, 554 | "p-limit": { 555 | "version": "2.2.0", 556 | "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.2.0.tgz", 557 | "integrity": "sha512-pZbTJpoUsCzV48Mc9Nh51VbwO0X9cuPFE8gYwx9BTCt9SF8/b7Zljd2fVgOxhIF/HDTKgpVzs+GPhyKfjLLFRQ==", 558 | "requires": { 559 | "p-try": "2.2.0" 560 | } 561 | }, 562 | "p-locate": { 563 | "version": "3.0.0", 564 | "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", 565 | "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", 566 | "requires": { 567 | "p-limit": "2.2.0" 568 | } 569 | }, 570 | "p-try": { 571 | "version": "2.2.0", 572 | "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", 573 | "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==" 574 | }, 575 | "path-exists": { 576 | "version": "3.0.0", 577 | "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", 578 | "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=" 579 | }, 580 | "path-is-absolute": { 581 | "version": "1.0.1", 582 | "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", 583 | "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=" 584 | }, 585 | "path-key": { 586 | "version": "2.0.1", 587 | "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz", 588 | "integrity": "sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A=" 589 | }, 590 | "pump": { 591 | "version": "3.0.0", 592 | "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", 593 | "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", 594 | "requires": { 595 | "end-of-stream": "1.4.1", 596 | "once": "1.4.0" 597 | } 598 | }, 599 | "require-directory": { 600 | "version": "2.1.1", 601 | "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", 602 | "integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I=" 603 | }, 604 | "require-main-filename": { 605 | "version": "2.0.0", 606 | "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz", 607 | "integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==" 608 | }, 609 | "semver": { 610 | "version": "5.7.0", 611 | "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.0.tgz", 612 | "integrity": "sha512-Ya52jSX2u7QKghxeoFGpLwCtGlt7j0oY9DYb5apt9nPlJ42ID+ulTXESnt/qAQcoSERyZ5sl3LDIOw0nAn/5DA==" 613 | }, 614 | "set-blocking": { 615 | "version": "2.0.0", 616 | "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", 617 | "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=" 618 | }, 619 | "shebang-command": { 620 | "version": "1.2.0", 621 | "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz", 622 | "integrity": "sha1-RKrGW2lbAzmJaMOfNj/uXer98eo=", 623 | "requires": { 624 | "shebang-regex": "1.0.0" 625 | } 626 | }, 627 | "shebang-regex": { 628 | "version": "1.0.0", 629 | "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz", 630 | "integrity": "sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM=" 631 | }, 632 | "signal-exit": { 633 | "version": "3.0.2", 634 | "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.2.tgz", 635 | "integrity": "sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0=" 636 | }, 637 | "sprintf-js": { 638 | "version": "1.0.3", 639 | "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", 640 | "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=" 641 | }, 642 | "string-width": { 643 | "version": "2.1.1", 644 | "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", 645 | "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", 646 | "requires": { 647 | "is-fullwidth-code-point": "2.0.0", 648 | "strip-ansi": "4.0.0" 649 | } 650 | }, 651 | "strip-ansi": { 652 | "version": "4.0.0", 653 | "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", 654 | "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", 655 | "requires": { 656 | "ansi-regex": "3.0.0" 657 | } 658 | }, 659 | "strip-eof": { 660 | "version": "1.0.0", 661 | "resolved": "https://registry.npmjs.org/strip-eof/-/strip-eof-1.0.0.tgz", 662 | "integrity": "sha1-u0P/VZim6wXYm1n80SnJgzE2Br8=" 663 | }, 664 | "strip-json-comments": { 665 | "version": "2.0.1", 666 | "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", 667 | "integrity": "sha1-PFMZQukIwml8DsNEhYwobHygpgo=" 668 | }, 669 | "supports-color": { 670 | "version": "6.0.0", 671 | "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-6.0.0.tgz", 672 | "integrity": "sha512-on9Kwidc1IUQo+bQdhi8+Tijpo0e1SS6RoGo2guUwn5vdaxw8RXOF9Vb2ws+ihWOmh4JnCJOvaziZWP1VABaLg==", 673 | "requires": { 674 | "has-flag": "3.0.0" 675 | } 676 | }, 677 | "which": { 678 | "version": "1.3.1", 679 | "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", 680 | "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", 681 | "requires": { 682 | "isexe": "2.0.0" 683 | } 684 | }, 685 | "which-module": { 686 | "version": "2.0.0", 687 | "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.0.tgz", 688 | "integrity": "sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho=" 689 | }, 690 | "wide-align": { 691 | "version": "1.1.3", 692 | "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.3.tgz", 693 | "integrity": "sha512-QGkOQc8XL6Bt5PwnsExKBPuMKBxnGxWWW3fU55Xt4feHozMUhdUMaBCk290qpm/wG5u/RSKzwdAC4i51YigihA==", 694 | "requires": { 695 | "string-width": "2.1.1" 696 | } 697 | }, 698 | "wrap-ansi": { 699 | "version": "2.1.0", 700 | "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-2.1.0.tgz", 701 | "integrity": "sha1-2Pw9KE3QV5T+hJc8rs3Rz4JP3YU=", 702 | "requires": { 703 | "string-width": "1.0.2", 704 | "strip-ansi": "3.0.1" 705 | }, 706 | "dependencies": { 707 | "ansi-regex": { 708 | "version": "2.1.1", 709 | "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", 710 | "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=" 711 | }, 712 | "is-fullwidth-code-point": { 713 | "version": "1.0.0", 714 | "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", 715 | "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", 716 | "requires": { 717 | "number-is-nan": "1.0.1" 718 | } 719 | }, 720 | "string-width": { 721 | "version": "1.0.2", 722 | "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", 723 | "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", 724 | "requires": { 725 | "code-point-at": "1.1.0", 726 | "is-fullwidth-code-point": "1.0.0", 727 | "strip-ansi": "3.0.1" 728 | } 729 | }, 730 | "strip-ansi": { 731 | "version": "3.0.1", 732 | "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", 733 | "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", 734 | "requires": { 735 | "ansi-regex": "2.1.1" 736 | } 737 | } 738 | } 739 | }, 740 | "wrappy": { 741 | "version": "1.0.2", 742 | "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", 743 | "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=" 744 | }, 745 | "y18n": { 746 | "version": "4.0.0", 747 | "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.0.tgz", 748 | "integrity": "sha512-r9S/ZyXu/Xu9q1tYlpsLIsa3EeLXXk0VwlxqTcFRfg9EhMW+17kbt9G0NrgCmhGb5vT2hyhJZLfDGx+7+5Uj/w==" 749 | }, 750 | "yargs": { 751 | "version": "13.2.2", 752 | "resolved": "https://registry.npmjs.org/yargs/-/yargs-13.2.2.tgz", 753 | "integrity": "sha512-WyEoxgyTD3w5XRpAQNYUB9ycVH/PQrToaTXdYXRdOXvEy1l19br+VJsc0vcO8PTGg5ro/l/GY7F/JMEBmI0BxA==", 754 | "requires": { 755 | "cliui": "4.1.0", 756 | "find-up": "3.0.0", 757 | "get-caller-file": "2.0.5", 758 | "os-locale": "3.1.0", 759 | "require-directory": "2.1.1", 760 | "require-main-filename": "2.0.0", 761 | "set-blocking": "2.0.0", 762 | "string-width": "3.1.0", 763 | "which-module": "2.0.0", 764 | "y18n": "4.0.0", 765 | "yargs-parser": "13.0.0" 766 | }, 767 | "dependencies": { 768 | "ansi-regex": { 769 | "version": "4.1.0", 770 | "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", 771 | "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==" 772 | }, 773 | "string-width": { 774 | "version": "3.1.0", 775 | "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz", 776 | "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==", 777 | "requires": { 778 | "emoji-regex": "7.0.3", 779 | "is-fullwidth-code-point": "2.0.0", 780 | "strip-ansi": "5.2.0" 781 | } 782 | }, 783 | "strip-ansi": { 784 | "version": "5.2.0", 785 | "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", 786 | "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", 787 | "requires": { 788 | "ansi-regex": "4.1.0" 789 | } 790 | } 791 | } 792 | }, 793 | "yargs-parser": { 794 | "version": "13.0.0", 795 | "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-13.0.0.tgz", 796 | "integrity": "sha512-w2LXjoL8oRdRQN+hOyppuXs+V/fVAYtpcrRxZuF7Kt/Oc+Jr2uAcVntaUTNT6w5ihoWfFDpNY8CPx1QskxZ/pw==", 797 | "requires": { 798 | "camelcase": "5.3.1", 799 | "decamelize": "1.2.0" 800 | } 801 | }, 802 | "yargs-unparser": { 803 | "version": "1.5.0", 804 | "resolved": "https://registry.npmjs.org/yargs-unparser/-/yargs-unparser-1.5.0.tgz", 805 | "integrity": "sha512-HK25qidFTCVuj/D1VfNiEndpLIeJN78aqgR23nL3y4N0U/91cOAzqfHlF8n2BvoNDcZmJKin3ddNSvOxSr8flw==", 806 | "requires": { 807 | "flat": "4.1.0", 808 | "lodash": "4.17.11", 809 | "yargs": "12.0.5" 810 | }, 811 | "dependencies": { 812 | "get-caller-file": { 813 | "version": "1.0.3", 814 | "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-1.0.3.tgz", 815 | "integrity": "sha512-3t6rVToeoZfYSGd8YoLFR2DJkiQrIiUrGcjvFX2mDw3bn6k2OtwHN0TNCLbBO+w8qTvimhDkv+LSscbJY1vE6w==" 816 | }, 817 | "require-main-filename": { 818 | "version": "1.0.1", 819 | "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-1.0.1.tgz", 820 | "integrity": "sha1-l/cXtp1IeE9fUmpsWqj/3aBVpNE=" 821 | }, 822 | "yargs": { 823 | "version": "12.0.5", 824 | "resolved": "https://registry.npmjs.org/yargs/-/yargs-12.0.5.tgz", 825 | "integrity": "sha512-Lhz8TLaYnxq/2ObqHDql8dX8CJi97oHxrjUcYtzKbbykPtVW9WB+poxI+NM2UIzsMgNCZTIf0AQwsjK5yMAqZw==", 826 | "requires": { 827 | "cliui": "4.1.0", 828 | "decamelize": "1.2.0", 829 | "find-up": "3.0.0", 830 | "get-caller-file": "1.0.3", 831 | "os-locale": "3.1.0", 832 | "require-directory": "2.1.1", 833 | "require-main-filename": "1.0.1", 834 | "set-blocking": "2.0.0", 835 | "string-width": "2.1.1", 836 | "which-module": "2.0.0", 837 | "y18n": "4.0.0", 838 | "yargs-parser": "11.1.1" 839 | } 840 | }, 841 | "yargs-parser": { 842 | "version": "11.1.1", 843 | "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-11.1.1.tgz", 844 | "integrity": "sha512-C6kB/WJDiaxONLJQnF8ccx9SEeoTTLek8RVbaOIsrAUS8VrBEXfmeSnCZxygc+XC2sNMBIwOOnfcxiynjHsVSQ==", 845 | "requires": { 846 | "camelcase": "5.3.1", 847 | "decamelize": "1.2.0" 848 | } 849 | } 850 | } 851 | } 852 | } 853 | } 854 | -------------------------------------------------------------------------------- /app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile 22 | -------------------------------------------------------------------------------- /app/src/commonMain/kotlin/com/willowtreeapps/fuzzywuzzy/Ration.kt: -------------------------------------------------------------------------------- 1 | package com.willowtreeapps.fuzzywuzzy 2 | 3 | import com.willowtreeapps.fuzzywuzzy.diffutils.Applicable 4 | 5 | /** 6 | * Interface for the different ratios 7 | */ 8 | interface Ratio : Applicable { 9 | 10 | /** 11 | * Applies the ratio between the two strings 12 | * 13 | * @param s1 Input string 14 | * @param s2 Input string 15 | * @return Integer representing ratio of similarity 16 | */ 17 | override fun apply(s1: String, s2: String): Int 18 | 19 | /** 20 | * Applies the ratio between the two strings 21 | * 22 | * @param s1 Input string 23 | * @param s2 Input string 24 | * @param sp String processor to pre-process strings before calculating the ratio 25 | * @return Integer representing ratio of similarity 26 | */ 27 | fun apply(s1: String, s2: String, sp: ToStringFunction): Int 28 | 29 | } -------------------------------------------------------------------------------- /app/src/commonMain/kotlin/com/willowtreeapps/fuzzywuzzy/ToStringFunction.kt: -------------------------------------------------------------------------------- 1 | package com.willowtreeapps.fuzzywuzzy 2 | 3 | 4 | /** 5 | * Transforms an item of type T to a String. 6 | * 7 | * @param The type of the item to transform. 8 | */ 9 | interface ToStringFunction { 10 | /** 11 | * Transforms the input item to a string. 12 | * 13 | * @param item The item to transform. 14 | * @return A string to use for comparing the item. 15 | */ 16 | fun apply(item: T): String 17 | 18 | companion object { 19 | 20 | /** 21 | * A default ToStringFunction that returns the input string; 22 | * used by methods that use plain strings in [FuzzySearch]. 23 | */ 24 | val NO_PROCESS: ToStringFunction = object : ToStringFunction { 25 | override fun apply(item: String): String { 26 | return item 27 | } 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /app/src/commonMain/kotlin/com/willowtreeapps/fuzzywuzzy/diffutils/Applicable.kt: -------------------------------------------------------------------------------- 1 | package com.willowtreeapps.fuzzywuzzy.diffutils 2 | 3 | /** 4 | * A ratio/algorithm that can be applied 5 | */ 6 | 7 | interface Applicable { 8 | 9 | /** 10 | * Apply the ratio/algorithm to the input strings 11 | * 12 | * @param s1 Input string 13 | * @param s2 Input string 14 | * @return The score of similarity 15 | */ 16 | fun apply(s1: String, s2: String): Int 17 | 18 | } -------------------------------------------------------------------------------- /app/src/commonMain/kotlin/com/willowtreeapps/fuzzywuzzy/diffutils/DiffUtils.kt: -------------------------------------------------------------------------------- 1 | package com.willowtreeapps.fuzzywuzzy.diffutils 2 | 3 | import com.willowtreeapps.fuzzywuzzy.diffutils.structs.EditOp 4 | import com.willowtreeapps.fuzzywuzzy.diffutils.structs.EditType 5 | import com.willowtreeapps.fuzzywuzzy.diffutils.structs.EditType.* 6 | import com.willowtreeapps.fuzzywuzzy.diffutils.structs.MatchingBlock 7 | import com.willowtreeapps.fuzzywuzzy.diffutils.structs.OpCode 8 | 9 | /** 10 | * This is a port of all the functions needed from python-levenshtein C implementation. 11 | * The code was ported line by line but unfortunately it was mostly undocumented, 12 | * so it is mostly non readable (eg. var names) 13 | */ 14 | object DiffUtils { 15 | 16 | private fun getEditOps(s1: String, s2: String): Array { 17 | return getEditOps(s1.length, s1, s2.length, s2) 18 | } 19 | 20 | 21 | private fun getEditOps(len1: Int, s1: String, len2: Int, s2: String): Array { 22 | var len1Copy = len1 23 | var len2Copy = len2 24 | 25 | var len1o = 0 26 | val len2o: Int 27 | var i = 0 28 | 29 | val matrix: IntArray 30 | 31 | val c1 = s1 32 | val c2 = s2 33 | 34 | var p1 = 0 35 | var p2 = 0 36 | 37 | while (len1Copy > 0 && len2Copy > 0 && c1[p1] == c2[p2]) { 38 | len1Copy-- 39 | len2Copy-- 40 | 41 | p1++ 42 | p2++ 43 | 44 | len1o++ 45 | } 46 | 47 | len2o = len1o 48 | 49 | /* strip common suffix */ 50 | while (len1Copy > 0 && len2Copy > 0 && c1[p1 + len1Copy - 1] == c2[p2 + len2Copy - 1]) { 51 | len1Copy-- 52 | len2Copy-- 53 | } 54 | 55 | len1Copy++ 56 | len2Copy++ 57 | 58 | matrix = IntArray(len2Copy * len1Copy) 59 | 60 | while (i < len2Copy) { 61 | matrix[i] = i 62 | i++ 63 | } 64 | i = 1 65 | while (i < len1Copy) { 66 | matrix[len2Copy * i] = i 67 | i++ 68 | } 69 | 70 | i = 1 71 | while (i < len1Copy) { 72 | 73 | var ptrPrev = (i - 1) * len2Copy 74 | var ptrC = i * len2Copy 75 | val ptrEnd = ptrC + len2Copy - 1 76 | 77 | val char1 = c1[p1 + i - 1] 78 | var ptrChar2 = p2 79 | 80 | var x = i 81 | 82 | ptrC++ 83 | 84 | while (ptrC <= ptrEnd) { 85 | 86 | var c3 = matrix[ptrPrev++] + if (char1 != c2[ptrChar2++]) 1 else 0 87 | x++ 88 | 89 | if (x > c3) { 90 | x = c3 91 | } 92 | 93 | c3 = matrix[ptrPrev] + 1 94 | 95 | if (x > c3) { 96 | x = c3 97 | } 98 | 99 | matrix[ptrC++] = x 100 | 101 | } 102 | i++ 103 | 104 | } 105 | 106 | 107 | return editOpsFromCostMatrix(len1Copy, c1, p1, len1o, len2Copy, c2, p2, len2o, matrix) 108 | } 109 | 110 | 111 | private fun editOpsFromCostMatrix(len1: Int, c1: String, p1: Int, o1: Int, 112 | len2: Int, c2: String, p2: Int, o2: Int, 113 | matrix: IntArray): Array { 114 | 115 | var i: Int = len1 - 1 116 | var j: Int = len2 - 1 117 | var pos: Int = matrix[len1 * len2 - 1] 118 | 119 | var ptr: Int = len1 * len2 - 1 120 | 121 | val ops: Array 122 | 123 | var dir = 0 124 | 125 | ops = arrayOfNulls(pos) 126 | 127 | while (i > 0 || j > 0) { 128 | 129 | if (dir < 0 && j != 0 && matrix[ptr] == matrix[ptr - 1] + 1) { 130 | 131 | val eop = EditOp() 132 | 133 | pos-- 134 | ops[pos] = eop 135 | eop.type = INSERT 136 | eop.spos = i + o1 137 | eop.dpos = --j + o2 138 | ptr-- 139 | 140 | continue 141 | } 142 | 143 | if (dir > 0 && i != 0 && matrix[ptr] == matrix[ptr - len2] + 1) { 144 | 145 | val eop = EditOp() 146 | 147 | pos-- 148 | ops[pos] = eop 149 | eop.type = DELETE 150 | eop.spos = --i + o1 151 | eop.dpos = j + o2 152 | ptr -= len2 153 | 154 | continue 155 | 156 | } 157 | 158 | if (i != 0 && j != 0 && matrix[ptr] == matrix[ptr - len2 - 1] 159 | && c1[p1 + i - 1] == c2[p2 + j - 1]) { 160 | 161 | i-- 162 | j-- 163 | ptr -= len2 + 1 164 | dir = 0 165 | 166 | continue 167 | 168 | } 169 | 170 | if (i != 0 && j != 0 && matrix[ptr] == matrix[ptr - len2 - 1] + 1) { 171 | 172 | pos-- 173 | 174 | val eop = EditOp() 175 | ops[pos] = eop 176 | 177 | eop.type = REPLACE 178 | eop.spos = --i + o1 179 | eop.dpos = --j + o2 180 | 181 | ptr -= len2 + 1 182 | dir = 0 183 | continue 184 | 185 | } 186 | 187 | if (dir == 0 && j != 0 && matrix[ptr] == matrix[ptr - 1] + 1) { 188 | 189 | pos-- 190 | val eop = EditOp() 191 | ops[pos] = eop 192 | eop.type = INSERT 193 | eop.spos = i + o1 194 | eop.dpos = --j + o2 195 | ptr-- 196 | dir = -1 197 | 198 | continue 199 | } 200 | 201 | if (dir == 0 && i != 0 && matrix[ptr] == matrix[ptr - len2] + 1) { 202 | pos-- 203 | val eop = EditOp() 204 | ops[pos] = eop 205 | 206 | eop.type = DELETE 207 | eop.spos = --i + o1 208 | eop.dpos = j + o2 209 | ptr -= len2 210 | dir = 1 211 | continue 212 | } 213 | 214 | assert(false) 215 | 216 | } 217 | 218 | return ops.requireNoNulls() 219 | 220 | } 221 | 222 | fun getMatchingBlocks(s1: String, s2: String): Array { 223 | 224 | return getMatchingBlocks(s1.length, s2.length, getEditOps(s1, s2)) 225 | 226 | } 227 | 228 | fun getMatchingBlocks(len1: Int, len2: Int, ops: Array): Array { 229 | 230 | val n = ops.size 231 | 232 | var noOfMB = 0 233 | var i: Int 234 | var o = 0 235 | 236 | i = n 237 | while (i-- != 0) { 238 | 239 | if (ops[o].type === KEEP) { 240 | 241 | noOfMB++ 242 | 243 | while (i != 0 && ops[o].type === KEEP) { 244 | i-- 245 | o++ 246 | } 247 | 248 | if (i == 0) 249 | break 250 | 251 | } 252 | o++ 253 | 254 | } 255 | 256 | val matchingBlocks = arrayOfNulls(noOfMB + 1) 257 | var mb = 0 258 | o = 0 259 | matchingBlocks[mb] = MatchingBlock() 260 | 261 | i = n 262 | while (i != 0) { 263 | 264 | if (ops[o].type === KEEP) { 265 | 266 | 267 | matchingBlocks[mb]!!.spos = ops[o].sbeg 268 | matchingBlocks[mb]!!.dpos = ops[o].dbeg 269 | 270 | while (i != 0 && ops[o].type === KEEP) { 271 | i-- 272 | o++ 273 | } 274 | 275 | if (i == 0) { 276 | matchingBlocks[mb]!!.length = len1 - matchingBlocks[mb]!!.spos 277 | mb++ 278 | break 279 | } 280 | 281 | matchingBlocks[mb]!!.length = ops[o].sbeg - matchingBlocks[mb]!!.spos 282 | mb++ 283 | matchingBlocks[mb] = MatchingBlock() 284 | } 285 | i-- 286 | o++ 287 | 288 | 289 | } 290 | 291 | assert(mb == noOfMB) 292 | 293 | val finalBlock = MatchingBlock() 294 | finalBlock.spos = len1 295 | finalBlock.dpos = len2 296 | finalBlock.length = 0 297 | 298 | matchingBlocks[mb] = finalBlock 299 | 300 | return matchingBlocks 301 | 302 | 303 | } 304 | 305 | 306 | private fun getMatchingBlocks(len1: Int, len2: Int, ops: Array): Array { 307 | 308 | val n = ops.size 309 | 310 | var numberOfMatchingBlocks = 0 311 | var i: Int 312 | var spos: Int 313 | var dpos: Int 314 | 315 | var o = 0 316 | 317 | dpos = 0 318 | spos = dpos 319 | 320 | var type: EditType 321 | 322 | i = n 323 | while (i != 0) { 324 | 325 | 326 | while (ops[o].type === KEEP && --i != 0) { 327 | o++ 328 | } 329 | 330 | if (i == 0) 331 | break 332 | 333 | if (spos < ops[o].spos || dpos < ops[o].dpos) { 334 | 335 | numberOfMatchingBlocks++ 336 | spos = ops[o].spos 337 | dpos = ops[o].dpos 338 | 339 | } 340 | 341 | type = ops[o].type!! 342 | 343 | when (type) { 344 | REPLACE -> do { 345 | spos++ 346 | dpos++ 347 | i-- 348 | o++ 349 | } while (i != 0 && ops[o].type === type && 350 | spos == ops[o].spos && dpos == ops[o].dpos) 351 | 352 | DELETE -> do { 353 | spos++ 354 | i-- 355 | o++ 356 | } while (i != 0 && ops[o].type === type && 357 | spos == ops[o].spos && dpos == ops[o].dpos) 358 | 359 | INSERT -> do { 360 | dpos++ 361 | i-- 362 | o++ 363 | } while (i != 0 && ops[o].type === type && 364 | spos == ops[o].spos && dpos == ops[o].dpos) 365 | 366 | else -> { 367 | } 368 | } 369 | } 370 | 371 | if (spos < len1 || dpos < len2) { 372 | numberOfMatchingBlocks++ 373 | } 374 | 375 | val matchingBlocks = arrayOfNulls(numberOfMatchingBlocks + 1) 376 | 377 | o = 0 378 | dpos = 0 379 | spos = dpos 380 | var mbIndex = 0 381 | 382 | 383 | i = n 384 | while (i != 0) { 385 | 386 | while (ops[o].type === KEEP && --i != 0) 387 | o++ 388 | 389 | if (i == 0) 390 | break 391 | 392 | if (spos < ops[o].spos || dpos < ops[o].dpos) { 393 | val mb = MatchingBlock() 394 | 395 | mb.spos = spos 396 | mb.dpos = dpos 397 | mb.length = ops[o].spos - spos 398 | spos = ops[o].spos 399 | dpos = ops[o].dpos 400 | 401 | matchingBlocks[mbIndex++] = mb 402 | 403 | } 404 | 405 | type = ops[o].type!! 406 | 407 | when (type) { 408 | REPLACE -> do { 409 | spos++ 410 | dpos++ 411 | i-- 412 | o++ 413 | } while (i != 0 && ops[o].type === type && 414 | spos == ops[o].spos && dpos == ops[o].dpos) 415 | 416 | DELETE -> do { 417 | spos++ 418 | i-- 419 | o++ 420 | } while (i != 0 && ops[o].type === type && 421 | spos == ops[o].spos && dpos == ops[o].dpos) 422 | 423 | INSERT -> do { 424 | dpos++ 425 | i-- 426 | o++ 427 | } while (i != 0 && ops[o].type === type && 428 | spos == ops[o].spos && dpos == ops[o].dpos) 429 | 430 | else -> { 431 | } 432 | } 433 | } 434 | 435 | if (spos < len1 || dpos < len2) { 436 | assert(len1 - spos == len2 - dpos) 437 | 438 | val mb = MatchingBlock() 439 | mb.spos = spos 440 | mb.dpos = dpos 441 | mb.length = len1 - spos 442 | 443 | matchingBlocks[mbIndex++] = mb 444 | } 445 | 446 | assert(numberOfMatchingBlocks == mbIndex) 447 | 448 | val finalBlock = MatchingBlock() 449 | finalBlock.spos = len1 450 | finalBlock.dpos = len2 451 | finalBlock.length = 0 452 | 453 | matchingBlocks[mbIndex] = finalBlock 454 | 455 | 456 | return matchingBlocks.filterNotNull().toTypedArray() 457 | } 458 | 459 | 460 | private fun editOpsToOpCodes(ops: Array, len1: Int, len2: Int): Array { 461 | 462 | val n = ops.size 463 | var noOfBlocks = 0 464 | var i: Int 465 | var spos: Int 466 | var dpos: Int 467 | var o = 0 468 | var type: EditType 469 | 470 | dpos = 0 471 | spos = dpos 472 | 473 | i = n 474 | while (i != 0) { 475 | 476 | while (ops[o].type === KEEP && --i != 0) { 477 | o++ 478 | } 479 | 480 | if (i == 0) 481 | break 482 | 483 | if (spos < ops[o].spos || dpos < ops[o].dpos) { 484 | 485 | noOfBlocks++ 486 | spos = ops[o].spos 487 | dpos = ops[o].dpos 488 | 489 | } 490 | 491 | // TODO: Is this right? 492 | noOfBlocks++ 493 | type = ops[o].type!! 494 | 495 | when (type) { 496 | REPLACE -> do { 497 | spos++ 498 | dpos++ 499 | i-- 500 | o++ 501 | } while (i != 0 && ops[o].type === type && 502 | spos == ops[o].spos && dpos == ops[o].dpos) 503 | 504 | DELETE -> do { 505 | spos++ 506 | i-- 507 | o++ 508 | } while (i != 0 && ops[o].type === type && 509 | spos == ops[o].spos && dpos == ops[o].dpos) 510 | 511 | INSERT -> do { 512 | dpos++ 513 | i-- 514 | o++ 515 | } while (i != 0 && ops[o].type === type && 516 | spos == ops[o].spos && dpos == ops[o].dpos) 517 | 518 | else -> { 519 | } 520 | } 521 | } 522 | 523 | if (spos < len1 || dpos < len2) 524 | noOfBlocks++ 525 | 526 | val opCodes = arrayOfNulls(noOfBlocks) 527 | 528 | o = 0 529 | dpos = 0 530 | spos = dpos 531 | var oIndex = 0 532 | 533 | i = n 534 | while (i != 0) { 535 | 536 | while (ops[o].type === KEEP && --i != 0) 537 | o++ 538 | 539 | if (i == 0) 540 | break 541 | 542 | val oc = OpCode() 543 | opCodes[oIndex] = oc 544 | oc.sbeg = spos 545 | oc.dbeg = dpos 546 | 547 | if (spos < ops[o].spos || dpos < ops[o].dpos) { 548 | 549 | oc.type = KEEP 550 | oc.send = ops[o].spos 551 | spos = oc.send 552 | oc.dend = ops[o].dpos 553 | dpos = oc.dend 554 | 555 | oIndex++ 556 | val oc2 = OpCode() 557 | opCodes[oIndex] = oc2 558 | oc2.sbeg = spos 559 | oc2.dbeg = dpos 560 | 561 | } 562 | 563 | type = ops[o].type!! 564 | 565 | when (type) { 566 | REPLACE -> do { 567 | spos++ 568 | dpos++ 569 | i-- 570 | o++ 571 | } while (i != 0 && ops[o].type === type && 572 | spos == ops[o].spos && dpos == ops[o].dpos) 573 | 574 | DELETE -> do { 575 | spos++ 576 | i-- 577 | o++ 578 | } while (i != 0 && ops[o].type === type && 579 | spos == ops[o].spos && dpos == ops[o].dpos) 580 | 581 | INSERT -> do { 582 | dpos++ 583 | i-- 584 | o++ 585 | } while (i != 0 && ops[o].type === type && 586 | spos == ops[o].spos && dpos == ops[o].dpos) 587 | 588 | else -> { 589 | } 590 | } 591 | 592 | opCodes[oIndex]!!.type = type 593 | opCodes[oIndex]!!.send = spos 594 | opCodes[oIndex]!!.dend = dpos 595 | oIndex++ 596 | } 597 | 598 | if (spos < len1 || dpos < len2) { 599 | 600 | assert(len1 - spos == len2 - dpos) 601 | if (opCodes[oIndex] == null) 602 | opCodes[oIndex] = OpCode() 603 | opCodes[oIndex]!!.type = KEEP 604 | opCodes[oIndex]!!.sbeg = spos 605 | opCodes[oIndex]!!.dbeg = dpos 606 | opCodes[oIndex]!!.send = len1 607 | opCodes[oIndex]!!.dend = len2 608 | 609 | oIndex++ 610 | 611 | } 612 | 613 | assert(oIndex == noOfBlocks) 614 | 615 | return opCodes 616 | 617 | } 618 | 619 | fun levEditDistance(s1: String, s2: String, xcost: Int): Int { 620 | 621 | var i: Int 622 | val half: Int 623 | 624 | var c1 = s1 625 | var c2 = s2 626 | 627 | var str1 = 0 628 | var str2 = 0 629 | 630 | var len1 = s1.length 631 | var len2 = s2.length 632 | 633 | /* strip common prefix */ 634 | while (len1 > 0 && len2 > 0 && c1[str1] == c2[str2]) { 635 | 636 | len1-- 637 | len2-- 638 | str1++ 639 | str2++ 640 | 641 | } 642 | 643 | /* strip common suffix */ 644 | while (len1 > 0 && len2 > 0 && c1[str1 + len1 - 1] == c2[str2 + len2 - 1]) { 645 | len1-- 646 | len2-- 647 | } 648 | 649 | /* catch trivial cases */ 650 | if (len1 == 0) 651 | return len2 652 | if (len2 == 0) 653 | return len1 654 | 655 | /* make the inner cycle (i.e. str2) the longer one */ 656 | if (len1 > len2) { 657 | 658 | val nx = len1 659 | val temp = str1 660 | 661 | len1 = len2 662 | len2 = nx 663 | 664 | str1 = str2 665 | str2 = temp 666 | 667 | val t = c2 668 | c2 = c1 669 | c1 = t 670 | 671 | } 672 | 673 | /* check len1 == 1 separately */ 674 | if (len1 == 1) { 675 | return if (xcost != 0) { 676 | len2 + 1 - 2 * memchr(c2, str2, c1[str1], len2) 677 | } else { 678 | len2 - memchr(c2, str2, c1[str1], len2) 679 | } 680 | } 681 | 682 | len1++ 683 | len2++ 684 | half = len1 shr 1 685 | 686 | val row = IntArray(len2) 687 | var end = len2 - 1 688 | 689 | i = 0 690 | while (i < len2 - if (xcost != 0) 0 else half) { 691 | row[i] = i 692 | i++ 693 | } 694 | 695 | 696 | /* go through the matrix and compute the costs. yes, this is an extremely 697 | * obfuscated version, but also extremely memory-conservative and relatively 698 | * fast. */ 699 | 700 | if (xcost != 0) { 701 | 702 | i = 1 703 | while (i < len1) { 704 | 705 | var p = 1 706 | 707 | val ch1 = c1[str1 + i - 1] 708 | var c2p = str2 709 | 710 | var D = i 711 | var x = i 712 | 713 | while (p <= end) { 714 | 715 | if (ch1 == c2[c2p++]) { 716 | x = --D 717 | } else { 718 | x++ 719 | } 720 | D = row[p] 721 | D++ 722 | 723 | if (x > D) 724 | x = D 725 | row[p++] = x 726 | 727 | } 728 | i++ 729 | 730 | } 731 | 732 | } else { 733 | 734 | /* in this case we don't have to scan two corner triangles (of size len1/2) 735 | * in the matrix because no best path can go throught them. note this 736 | * breaks when len1 == len2 == 2 so the memchr() special case above is 737 | * necessary */ 738 | 739 | row[0] = len1 - half - 1 740 | i = 1 741 | while (i < len1) { 742 | var p: Int 743 | 744 | val ch1 = c1[str1 + i - 1] 745 | var c2p: Int 746 | 747 | var D: Int 748 | var x: Int 749 | 750 | /* skip the upper triangle */ 751 | if (i >= len1 - half) { 752 | val offset = i - (len1 - half) 753 | val c3: Int 754 | 755 | c2p = str2 + offset 756 | p = offset 757 | c3 = row[p++] + if (ch1 != c2[c2p++]) 1 else 0 758 | x = row[p] 759 | x++ 760 | D = x 761 | if (x > c3) { 762 | x = c3 763 | } 764 | row[p++] = x 765 | } else { 766 | p = 1 767 | c2p = str2 768 | x = i 769 | D = x 770 | } 771 | /* skip the lower triangle */ 772 | if (i <= half + 1) 773 | end = len2 + i - half - 2 774 | /* main */ 775 | while (p <= end) { 776 | val c3 = --D + if (ch1 != c2[c2p++]) 1 else 0 777 | x++ 778 | if (x > c3) { 779 | x = c3 780 | } 781 | D = row[p] 782 | D++ 783 | if (x > D) 784 | x = D 785 | row[p++] = x 786 | 787 | } 788 | 789 | /* lower triangle sentinel */ 790 | if (i <= half) { 791 | val c3 = --D + if (ch1 != c2[c2p]) 1 else 0 792 | x++ 793 | if (x > c3) { 794 | x = c3 795 | } 796 | row[p] = x 797 | } 798 | i++ 799 | } 800 | } 801 | 802 | i = row[end] 803 | 804 | return i 805 | 806 | } 807 | 808 | private fun memchr(haystack: String, offset: Int, needle: Char, num: Int): Int { 809 | var numCopy = num 810 | 811 | if (numCopy != 0) { 812 | var p = 0 813 | 814 | do { 815 | 816 | if (haystack[offset + p] == needle) 817 | return 1 818 | 819 | p++ 820 | 821 | } while (--numCopy != 0) 822 | 823 | } 824 | return 0 825 | 826 | } 827 | 828 | 829 | fun getRatio(s1: String, s2: String): Double { 830 | 831 | val len1 = s1.length 832 | val len2 = s2.length 833 | val lensum = len1 + len2 834 | 835 | val editDistance = levEditDistance(s1, s2, 1) 836 | 837 | return (lensum - editDistance) / lensum.toDouble() 838 | 839 | } 840 | 841 | 842 | } 843 | 844 | fun assert(assertion: Boolean) { 845 | if (!assertion) 846 | throw AssertionError() 847 | } 848 | -------------------------------------------------------------------------------- /app/src/commonMain/kotlin/com/willowtreeapps/fuzzywuzzy/diffutils/Extractor.kt: -------------------------------------------------------------------------------- 1 | package com.willowtreeapps.fuzzywuzzy.diffutils 2 | 3 | import com.willowtreeapps.fuzzywuzzy.ToStringFunction 4 | import com.willowtreeapps.fuzzywuzzy.diffutils.algorithms.Utils 5 | import com.willowtreeapps.fuzzywuzzy.diffutils.model.BoundExtractedResult 6 | import com.willowtreeapps.fuzzywuzzy.diffutils.model.ExtractedResult 7 | 8 | 9 | class Extractor(private var cutoff: Int = 0) { 10 | 11 | fun with(cutoff: Int): Extractor { 12 | this.cutoff = cutoff 13 | return this 14 | } 15 | 16 | /** 17 | * Returns the list of choices with their associated scores of similarity in a list 18 | * of [ExtractedResult] 19 | * 20 | * @param query The query string 21 | * @param choices The list of choices 22 | * @param func The function to apply 23 | * @return The list of results 24 | */ 25 | fun extractWithoutOrder(query: String, choices: Collection, 26 | func: Applicable): List { 27 | val yields = ArrayList() 28 | 29 | for ((index, s) in choices.withIndex()) { 30 | 31 | val score = func.apply(query, s) 32 | 33 | if (score >= cutoff) { 34 | yields.add(ExtractedResult(s, score, index)) 35 | } 36 | } 37 | 38 | return yields 39 | } 40 | 41 | /** 42 | * Returns the list of choices with their associated scores of similarity in a list 43 | * of [ExtractedResult] 44 | * 45 | * @param query The query string 46 | * @param choices The list of choices 47 | * @param toStringFunction The ToStringFunction to be applied to all choices. 48 | * @param func The function to apply 49 | * @return The list of results 50 | */ 51 | fun extractWithoutOrder(query: String, choices: Collection, 52 | toStringFunction: ToStringFunction, func: Applicable): List> { 53 | 54 | val yields = ArrayList>() 55 | 56 | for ((index, t) in choices.withIndex()) { 57 | 58 | val s = toStringFunction.apply(t) 59 | val score = func.apply(query, s) 60 | 61 | if (score >= cutoff) { 62 | yields.add(BoundExtractedResult(t, s, score, index)) 63 | } 64 | } 65 | 66 | return yields 67 | 68 | } 69 | 70 | /** 71 | * Find the single best match above a score in a list of choices. 72 | * 73 | * @param query A string to match against 74 | * @param choices A list of choices 75 | * @param func Scoring function 76 | * @return An object containing the best match and it's score 77 | */ 78 | fun extractOne(query: String, choices: Collection, func: Applicable): ExtractedResult { 79 | val extracted = extractWithoutOrder(query, choices, func) 80 | 81 | return extracted.max()!! 82 | } 83 | 84 | /** 85 | * Find the single best match above a score in a list of choices. 86 | * 87 | * @param query A string to match against 88 | * @param choices A list of choices 89 | * @param toStringFunction The ToStringFunction to be applied to all choices. 90 | * @param func Scoring function 91 | * @return An object containing the best match and it's score 92 | */ 93 | fun extractOne(query: String, choices: Collection, toStringFunction: ToStringFunction, 94 | func: Applicable): BoundExtractedResult { 95 | 96 | val extracted = extractWithoutOrder(query, choices, toStringFunction, func) 97 | 98 | return extracted.max()!! 99 | 100 | } 101 | 102 | /** 103 | * Creates a **sorted** list of [ExtractedResult] which contain the 104 | * top @param limit most similar choices 105 | * 106 | * @param query The query string 107 | * @param choices A list of choices 108 | * @param func The scoring function 109 | * @return A list of the results 110 | */ 111 | fun extractTop(query: String, choices: Collection, func: Applicable): List { 112 | val best = extractWithoutOrder(query, choices, func) 113 | 114 | return best.sortedDescending() 115 | } 116 | 117 | /** 118 | * Creates a **sorted** list of [ExtractedResult] which contain the 119 | * top @param limit most similar choices 120 | * 121 | * @param query The query string 122 | * @param choices A list of choices 123 | * @param toStringFunction The ToStringFunction to be applied to all choices. 124 | * @param func The scoring function 125 | * @return A list of the results 126 | */ 127 | fun extractTop(query: String, choices: Collection, 128 | toStringFunction: ToStringFunction, func: Applicable): List> { 129 | 130 | val best = extractWithoutOrder(query, choices, toStringFunction, func) 131 | return best.sortedDescending() 132 | } 133 | 134 | /** 135 | * Creates a **sorted** list of [ExtractedResult] which contain the 136 | * top @param limit most similar choices 137 | * 138 | * @param query The query string 139 | * @param choices A list of choices 140 | * @param limit Limits the number of results and speeds up 141 | * the search (k-top heap sort) is used 142 | * @return A list of the results 143 | */ 144 | fun extractTop(query: String, choices: Collection, func: Applicable, limit: Int): List { 145 | val best = extractWithoutOrder(query, choices, func) 146 | 147 | val results = Utils.findTopKHeap(best, limit) 148 | 149 | return results.sortedDescending() 150 | } 151 | 152 | /** 153 | * Creates a **sorted** list of [ExtractedResult] which contain the 154 | * top @param limit most similar choices 155 | * 156 | * @param query The query string 157 | * @param choices A list of choices 158 | * @param toStringFunction The ToStringFunction to be applied to all choices. 159 | * @param limit Limits the number of results and speeds up 160 | * the search (k-top heap sort) is used 161 | * @return A list of the results 162 | */ 163 | fun extractTop(query: String, choices: Collection, 164 | toStringFunction: ToStringFunction, func: Applicable, limit: Int): List> { 165 | 166 | val best = extractWithoutOrder(query, choices, toStringFunction, func) 167 | 168 | val results = Utils.findTopKHeap(best, limit) 169 | return results.sortedDescending() 170 | } 171 | } 172 | -------------------------------------------------------------------------------- /app/src/commonMain/kotlin/com/willowtreeapps/fuzzywuzzy/diffutils/FuzzySearch.kt: -------------------------------------------------------------------------------- 1 | package com.willowtreeapps.fuzzywuzzy.diffutils 2 | 3 | import com.willowtreeapps.fuzzywuzzy.ToStringFunction 4 | import com.willowtreeapps.fuzzywuzzy.diffutils.algorithms.TokenSet 5 | import com.willowtreeapps.fuzzywuzzy.diffutils.algorithms.TokenSort 6 | import com.willowtreeapps.fuzzywuzzy.diffutils.algorithms.WeightedRatio 7 | import com.willowtreeapps.fuzzywuzzy.diffutils.model.BoundExtractedResult 8 | import com.willowtreeapps.fuzzywuzzy.diffutils.model.ExtractedResult 9 | import com.willowtreeapps.fuzzywuzzy.diffutils.ratio.PartialRatio 10 | import com.willowtreeapps.fuzzywuzzy.diffutils.ratio.SimpleRatio 11 | 12 | /** 13 | * FuzzySearch facade class 14 | */ 15 | object FuzzySearch { 16 | 17 | /** 18 | * Calculates a Levenshtein simple ratio between the strings. 19 | * This is indicates a measure of similarity 20 | * 21 | * @param s1 Input string 22 | * @param s2 Input string 23 | * @return The simple ratio 24 | */ 25 | fun ratio(s1: String, s2: String): Int { 26 | 27 | return SimpleRatio().apply(s1, s2) 28 | 29 | } 30 | 31 | /** 32 | * Calculates a Levenshtein simple ratio between the strings. 33 | * This is indicates a measure of similarity 34 | * 35 | * @param s1 Input string 36 | * @param s2 Input string 37 | * @param stringFunction Functor which transforms strings before 38 | * calculating the ratio 39 | * @return The simple ratio 40 | */ 41 | fun ratio(s1: String, s2: String, stringFunction: ToStringFunction): Int { 42 | 43 | return SimpleRatio().apply(s1, s2, stringFunction) 44 | 45 | } 46 | 47 | /** 48 | * Inconsistent substrings lead to problems in matching. This ratio 49 | * uses a heuristic called "best partial" for when two strings 50 | * are of noticeably different lengths. 51 | * 52 | * @param s1 Input string 53 | * @param s2 Input string 54 | * @return The partial ratio 55 | */ 56 | fun partialRatio(s1: String, s2: String): Int { 57 | 58 | return PartialRatio().apply(s1, s2) 59 | 60 | } 61 | 62 | /** 63 | * Inconsistent substrings lead to problems in matching. This ratio 64 | * uses a heuristic called "best partial" for when two strings 65 | * are of noticeably different lengths. 66 | * 67 | * @param s1 Input string 68 | * @param s2 Input string 69 | * @param stringFunction Functor which transforms strings before 70 | * calculating the ratio 71 | * @return The partial ratio 72 | */ 73 | fun partialRatio(s1: String, s2: String, stringFunction: ToStringFunction): Int { 74 | 75 | return PartialRatio().apply(s1, s2, stringFunction) 76 | 77 | } 78 | 79 | /** 80 | * Find all alphanumeric tokens in the string and sort 81 | * those tokens and then take ratio of resulting 82 | * joined strings. 83 | * 84 | * @param s1 Input string 85 | * @param s2 Input string 86 | * @return The partial ratio of the strings 87 | */ 88 | fun tokenSortPartialRatio(s1: String, s2: String): Int { 89 | 90 | return TokenSort().apply(s1, s2, PartialRatio()) 91 | 92 | } 93 | 94 | /** 95 | * Find all alphanumeric tokens in the string and sort 96 | * those tokens and then take ratio of resulting 97 | * joined strings. 98 | * 99 | * @param s1 Input string 100 | * @param s2 Input string 101 | * @param stringFunction Functor which transforms strings before 102 | * calculating the ratio 103 | * @return The partial ratio of the strings 104 | */ 105 | fun tokenSortPartialRatio(s1: String, s2: String, stringFunction: ToStringFunction): Int { 106 | 107 | return TokenSort().apply(s1, s2, PartialRatio(), stringFunction) 108 | 109 | } 110 | 111 | /** 112 | * Find all alphanumeric tokens in the string and sort 113 | * those tokens and then take ratio of resulting 114 | * joined strings. 115 | * 116 | * @param s1 Input string 117 | * @param s2 Input string 118 | * @return The full ratio of the strings 119 | */ 120 | fun tokenSortRatio(s1: String, s2: String): Int { 121 | 122 | return TokenSort().apply(s1, s2, SimpleRatio()) 123 | 124 | } 125 | 126 | /** 127 | * Find all alphanumeric tokens in the string and sort 128 | * those tokens and then take ratio of resulting 129 | * joined strings. 130 | * 131 | * @param s1 Input string 132 | * @param s2 Input string 133 | * @param stringFunction Functor which transforms strings before 134 | * calculating the ratio 135 | * @return The full ratio of the strings 136 | */ 137 | fun tokenSortRatio(s1: String, s2: String, stringFunction: ToStringFunction): Int { 138 | 139 | return TokenSort().apply(s1, s2, SimpleRatio(), stringFunction) 140 | 141 | } 142 | 143 | 144 | /** 145 | * Splits the strings into tokens and computes intersections and remainders 146 | * between the tokens of the two strings. A comparison string is then 147 | * built up and is compared using the simple ratio algorithm. 148 | * Useful for strings where words appear redundantly. 149 | * 150 | * @param s1 Input string 151 | * @param s2 Input string 152 | * @return The ratio of similarity 153 | */ 154 | fun tokenSetRatio(s1: String, s2: String): Int { 155 | 156 | return TokenSet().apply(s1, s2, SimpleRatio()) 157 | 158 | } 159 | 160 | /** 161 | * Splits the strings into tokens and computes intersections and remainders 162 | * between the tokens of the two strings. A comparison string is then 163 | * built up and is compared using the simple ratio algorithm. 164 | * Useful for strings where words appear redundantly. 165 | * 166 | * @param s1 Input string 167 | * @param s2 Input string 168 | * @param stringFunction Functor which transforms strings before 169 | * calculating the ratio 170 | * @return The ratio of similarity 171 | */ 172 | fun tokenSetRatio(s1: String, s2: String, stringFunction: ToStringFunction): Int { 173 | 174 | return TokenSet().apply(s1, s2, SimpleRatio(), stringFunction) 175 | 176 | } 177 | 178 | /** 179 | * Splits the strings into tokens and computes intersections and remainders 180 | * between the tokens of the two strings. A comparison string is then 181 | * built up and is compared using the simple ratio algorithm. 182 | * Useful for strings where words appear redundantly. 183 | * 184 | * @param s1 Input string 185 | * @param s2 Input string 186 | * @return The ratio of similarity 187 | */ 188 | fun tokenSetPartialRatio(s1: String, s2: String): Int { 189 | 190 | return TokenSet().apply(s1, s2, PartialRatio()) 191 | 192 | } 193 | 194 | /** 195 | * Splits the strings into tokens and computes intersections and remainders 196 | * between the tokens of the two strings. A comparison string is then 197 | * built up and is compared using the simple ratio algorithm. 198 | * Useful for strings where words appear redundantly. 199 | * 200 | * @param s1 Input string 201 | * @param s2 Input string 202 | * @param stringFunction Functor which transforms strings before 203 | * calculating the ratio 204 | * @return The ratio of similarity 205 | */ 206 | fun tokenSetPartialRatio(s1: String, s2: String, stringFunction: ToStringFunction): Int { 207 | 208 | return TokenSet().apply(s1, s2, PartialRatio(), stringFunction) 209 | 210 | } 211 | 212 | /** 213 | * Calculates a weighted ratio between the different algorithms for best results 214 | * 215 | * @param s1 Input string 216 | * @param s2 Input string 217 | * @return The ratio of similarity 218 | */ 219 | fun weightedRatio(s1: String, s2: String): Int { 220 | 221 | return WeightedRatio().apply(s1, s2) 222 | 223 | } 224 | 225 | /** 226 | * Calculates a weighted ratio between the different algorithms for best results 227 | * 228 | * @param s1 Input string 229 | * @param s2 Input string 230 | * @param stringFunction Functor which transforms strings before 231 | * calculating the ratio 232 | * @return The ratio of similarity 233 | */ 234 | fun weightedRatio(s1: String, s2: String, stringFunction: ToStringFunction): Int { 235 | 236 | return WeightedRatio().apply(s1, s2, stringFunction) 237 | 238 | } 239 | 240 | /** 241 | * Creates a **sorted** list of [ExtractedResult] which contain the 242 | * top @param limit most similar choices 243 | * 244 | * @param query The query string 245 | * @param choices A list of choices 246 | * @param func The scoring function 247 | * @return A list of the results 248 | */ 249 | fun extractTop(query: String, choices: Collection, 250 | func: Applicable, limit: Int, cutoff: Int): List { 251 | 252 | val extractor = Extractor(cutoff) 253 | return extractor.extractTop(query, choices, func, limit) 254 | 255 | } 256 | 257 | /** 258 | * Creates a **sorted** list of [ExtractedResult] which contain the 259 | * top @param limit most similar choices 260 | * 261 | * @param query The query string 262 | * @param choices A list of choices 263 | * @param limit Limits the number of results and speeds up 264 | * the search (k-top heap sort) is used 265 | * @param cutoff Rejects any entries with score below this 266 | * @return A list of the results 267 | */ 268 | fun extractTop(query: String, choices: Collection, 269 | limit: Int, cutoff: Int): List { 270 | 271 | val extractor = Extractor(cutoff) 272 | return extractor.extractTop(query, choices, WeightedRatio(), limit) 273 | 274 | } 275 | 276 | /** 277 | * Creates a **sorted** list of [ExtractedResult] which contain the 278 | * top @param limit most similar choices 279 | * 280 | * @param query The query string 281 | * @param choices A list of choices 282 | * @param func The scoring function 283 | * @param limit The number of results to return 284 | * @return A list of the results 285 | */ 286 | fun extractTop(query: String, choices: Collection, 287 | func: Applicable, limit: Int): List { 288 | 289 | val extractor = Extractor() 290 | 291 | return extractor.extractTop(query, choices, func, limit) 292 | 293 | } 294 | 295 | /** 296 | * Creates a **sorted** list of [ExtractedResult] which contain the 297 | * top @param limit most similar choices 298 | * 299 | * @param query The query string 300 | * @param choices A list of choices 301 | * @param limit The number of results to return 302 | * @return A list of the results 303 | */ 304 | fun extractTop(query: String, choices: Collection, 305 | limit: Int): List { 306 | 307 | val extractor = Extractor() 308 | 309 | return extractor.extractTop(query, choices, WeightedRatio(), limit) 310 | 311 | } 312 | 313 | /** 314 | * Creates a **sorted** list of [ExtractedResult] which contain all the choices 315 | * with their corresponding score where higher is more similar 316 | * 317 | * @param query The query string 318 | * @param choices A list of choices 319 | * @param func The scoring function 320 | * @return A list of the results 321 | */ 322 | fun extractSorted(query: String, choices: Collection, func: Applicable): List { 323 | 324 | val extractor = Extractor() 325 | 326 | return extractor.extractTop(query, choices, func) 327 | 328 | } 329 | 330 | 331 | /** 332 | * Creates a **sorted** list of [ExtractedResult] which contain all the choices 333 | * with their corresponding score where higher is more similar 334 | * 335 | * @param query The query string 336 | * @param choices A list of choices 337 | * @param func The scoring function 338 | * @param cutoff Keep only scores above cutoff 339 | * @return A list of the results 340 | */ 341 | fun extractSorted(query: String, choices: Collection, func: Applicable, 342 | cutoff: Int): List { 343 | 344 | val extractor = Extractor(cutoff) 345 | 346 | return extractor.extractTop(query, choices, func) 347 | 348 | } 349 | 350 | /** 351 | * Creates a **sorted** list of [ExtractedResult] which contain all the choices 352 | * with their corresponding score where higher is more similar 353 | * 354 | * @param query The query string 355 | * @param choices A list of choices 356 | * @return A list of the results 357 | */ 358 | fun extractSorted(query: String, choices: Collection): List { 359 | 360 | val extractor = Extractor() 361 | 362 | return extractor.extractTop(query, choices, WeightedRatio()) 363 | 364 | } 365 | 366 | /** 367 | * Creates a **sorted** list of [ExtractedResult] which contain all the choices 368 | * with their corresponding score where higher is more similar 369 | * 370 | * @param query The query string 371 | * @param choices A list of choices 372 | * @param cutoff Keep only scores above cutoff 373 | * @return A list of the results 374 | */ 375 | fun extractSorted(query: String, choices: Collection, 376 | cutoff: Int): List { 377 | 378 | val extractor = Extractor(cutoff) 379 | 380 | return extractor.extractTop(query, choices, WeightedRatio()) 381 | 382 | } 383 | 384 | /** 385 | * Creates a list of [ExtractedResult] which contain all the choices with 386 | * their corresponding score where higher is more similar 387 | * 388 | * @param query The query string 389 | * @param choices A list of choices 390 | * @param func The scoring function 391 | * @return A list of the results 392 | */ 393 | fun extractAll(query: String, choices: Collection, func: Applicable): List { 394 | 395 | val extractor = Extractor() 396 | 397 | return extractor.extractWithoutOrder(query, choices, func) 398 | 399 | } 400 | 401 | /** 402 | * Creates a list of [ExtractedResult] which contain all the choices with 403 | * their corresponding score where higher is more similar 404 | * 405 | * @param query The query string 406 | * @param choices A list of choices 407 | * @param func The scoring function 408 | * @param cutoff Keep only scores above cutoff 409 | * @return A list of the results 410 | */ 411 | fun extractAll(query: String, choices: Collection, func: Applicable, 412 | cutoff: Int): List { 413 | 414 | val extractor = Extractor(cutoff) 415 | 416 | return extractor.extractWithoutOrder(query, choices, func) 417 | 418 | } 419 | 420 | /** 421 | * Creates a list of [ExtractedResult] which contain all the choices with 422 | * their corresponding score where higher is more similar 423 | * 424 | * @param query The query string 425 | * @param choices A list of choices 426 | * @return A list of the results 427 | */ 428 | fun extractAll(query: String, choices: Collection): List { 429 | 430 | val extractor = Extractor() 431 | 432 | return extractor.extractWithoutOrder(query, choices, WeightedRatio()) 433 | 434 | } 435 | 436 | /** 437 | * Creates a list of [ExtractedResult] which contain all the choices with 438 | * their corresponding score where higher is more similar 439 | * 440 | * @param query The query string 441 | * @param choices A list of choices 442 | * @param cutoff Keep only scores above cutoff 443 | * @return A list of the results 444 | */ 445 | fun extractAll(query: String, choices: Collection, cutoff: Int): List { 446 | 447 | val extractor = Extractor(cutoff) 448 | 449 | return extractor.extractWithoutOrder(query, choices, WeightedRatio()) 450 | 451 | } 452 | 453 | /** 454 | * Find the single best match above a score in a list of choices. 455 | * 456 | * @param query A string to match against 457 | * @param choices A list of choices 458 | * @param func Scoring function 459 | * @return An object containing the best match and it's score 460 | */ 461 | fun extractOne(query: String, choices: Collection, func: Applicable): ExtractedResult { 462 | 463 | val extractor = Extractor() 464 | 465 | return extractor.extractOne(query, choices, func) 466 | 467 | } 468 | 469 | /** 470 | * Find the single best match above a score in a list of choices. 471 | * 472 | * @param query A string to match against 473 | * @param choices A list of choices 474 | * @return An object containing the best match and it's score 475 | */ 476 | fun extractOne(query: String, choices: Collection): ExtractedResult { 477 | 478 | val extractor = Extractor() 479 | 480 | return extractor.extractOne(query, choices, WeightedRatio()) 481 | 482 | } 483 | 484 | /** 485 | * Creates a **sorted** list of [ExtractedResult] which contain the 486 | * top @param limit most similar choices 487 | * 488 | * @param query The query string 489 | * @param choices A list of choices 490 | * @param toStringFunction The ToStringFunction to be applied to all choices. 491 | * @param func The scoring function 492 | * @return A list of the results 493 | */ 494 | fun extractTop(query: String, choices: Collection, 495 | toStringFunction: ToStringFunction, func: Applicable, 496 | limit: Int, cutoff: Int): List> { 497 | 498 | val extractor = Extractor(cutoff) 499 | return extractor.extractTop(query, choices, toStringFunction, func, limit) 500 | 501 | } 502 | 503 | /** 504 | * Creates a **sorted** list of [ExtractedResult] which contain the 505 | * top @param limit most similar choices 506 | * 507 | * @param query The query string 508 | * @param choices A list of choices 509 | * @param toStringFunction The ToStringFunction to be applied to all choices. 510 | * @param limit Limits the number of results and speeds up 511 | * the search (k-top heap sort) is used 512 | * @param cutoff Rejects any entries with score below this 513 | * @return A list of the results 514 | */ 515 | fun extractTop(query: String, choices: Collection, 516 | toStringFunction: ToStringFunction, limit: Int, cutoff: Int): List> { 517 | 518 | val extractor = Extractor(cutoff) 519 | return extractor.extractTop(query, choices, toStringFunction, WeightedRatio(), limit) 520 | 521 | } 522 | 523 | /** 524 | * Creates a **sorted** list of [ExtractedResult] which contain the 525 | * top @param limit most similar choices 526 | * 527 | * @param query The query string 528 | * @param choices A list of choices 529 | * @param toStringFunction The ToStringFunction to be applied to all choices. 530 | * @param func The scoring function 531 | * @param limit The number of results to return 532 | * @return A list of the results 533 | */ 534 | fun extractTop(query: String, choices: Collection, 535 | toStringFunction: ToStringFunction, func: Applicable, 536 | limit: Int): List> { 537 | 538 | val extractor = Extractor() 539 | 540 | return extractor.extractTop(query, choices, toStringFunction, func, limit) 541 | 542 | } 543 | 544 | /** 545 | * Creates a **sorted** list of [ExtractedResult] which contain the 546 | * top @param limit most similar choices 547 | * 548 | * @param query The query string 549 | * @param choices A list of choices 550 | * @param toStringFunction The ToStringFunction to be applied to all choices. 551 | * @param limit The number of results to return 552 | * @return A list of the results 553 | */ 554 | fun extractTop(query: String, choices: Collection, 555 | toStringFunction: ToStringFunction, limit: Int): List> { 556 | 557 | val extractor = Extractor() 558 | 559 | return extractor.extractTop(query, choices, toStringFunction, WeightedRatio(), limit) 560 | 561 | } 562 | 563 | /** 564 | * Creates a **sorted** list of [ExtractedResult] which contain all the choices 565 | * with their corresponding score where higher is more similar 566 | * 567 | * @param query The query string 568 | * @param choices A list of choices 569 | * @param toStringFunction The ToStringFunction to be applied to all choices. 570 | * @param func The scoring function 571 | * @return A list of the results 572 | */ 573 | fun extractSorted(query: String, choices: Collection, 574 | toStringFunction: ToStringFunction, func: Applicable): List> { 575 | 576 | val extractor = Extractor() 577 | 578 | return extractor.extractTop(query, choices, toStringFunction, func) 579 | 580 | } 581 | 582 | 583 | /** 584 | * Creates a **sorted** list of [ExtractedResult] which contain all the choices 585 | * with their corresponding score where higher is more similar 586 | * 587 | * @param query The query string 588 | * @param choices A list of choices 589 | * @param toStringFunction The ToStringFunction to be applied to all choices. 590 | * @param func The scoring function 591 | * @param cutoff Keep only scores above cutoff 592 | * @return A list of the results 593 | */ 594 | fun extractSorted(query: String, choices: Collection, 595 | toStringFunction: ToStringFunction, func: Applicable, 596 | cutoff: Int): List> { 597 | 598 | val extractor = Extractor(cutoff) 599 | 600 | return extractor.extractTop(query, choices, toStringFunction, func) 601 | 602 | } 603 | 604 | /** 605 | * Creates a **sorted** list of [ExtractedResult] which contain all the choices 606 | * with their corresponding score where higher is more similar 607 | * 608 | * @param query The query string 609 | * @param choices A list of choices 610 | * @param toStringFunction The ToStringFunction to be applied to all choices. 611 | * @return A list of the results 612 | */ 613 | fun extractSorted(query: String, choices: Collection, 614 | toStringFunction: ToStringFunction): List> { 615 | 616 | val extractor = Extractor() 617 | 618 | return extractor.extractTop(query, choices, toStringFunction, WeightedRatio()) 619 | 620 | } 621 | 622 | /** 623 | * Creates a **sorted** list of [ExtractedResult] which contain all the choices 624 | * with their corresponding score where higher is more similar 625 | * 626 | * @param query The query string 627 | * @param choices A list of choices 628 | * @param toStringFunction The ToStringFunction to be applied to all choices. 629 | * @param cutoff Keep only scores above cutoff 630 | * @return A list of the results 631 | */ 632 | fun extractSorted(query: String, choices: Collection, 633 | toStringFunction: ToStringFunction, cutoff: Int): List> { 634 | 635 | val extractor = Extractor(cutoff) 636 | 637 | return extractor.extractTop(query, choices, toStringFunction, WeightedRatio()) 638 | 639 | } 640 | 641 | /** 642 | * Creates a list of [ExtractedResult] which contain all the choices with 643 | * their corresponding score where higher is more similar 644 | * 645 | * @param query The query string 646 | * @param choices A list of choices 647 | * @param toStringFunction The ToStringFunction to be applied to all choices. 648 | * @param func The scoring function 649 | * @return A list of the results 650 | */ 651 | fun extractAll(query: String, choices: Collection, 652 | toStringFunction: ToStringFunction, func: Applicable): List> { 653 | 654 | val extractor = Extractor() 655 | 656 | return extractor.extractWithoutOrder(query, choices, toStringFunction, func) 657 | 658 | } 659 | 660 | /** 661 | * Creates a list of [ExtractedResult] which contain all the choices with 662 | * their corresponding score where higher is more similar 663 | * 664 | * @param query The query string 665 | * @param choices A list of choices 666 | * @param toStringFunction The ToStringFunction to be applied to all choices. 667 | * @param func The scoring function 668 | * @param cutoff Keep only scores above cutoff 669 | * @return A list of the results 670 | */ 671 | fun extractAll(query: String, choices: Collection, 672 | toStringFunction: ToStringFunction, func: Applicable, 673 | cutoff: Int): List> { 674 | 675 | val extractor = Extractor(cutoff) 676 | 677 | return extractor.extractWithoutOrder(query, choices, toStringFunction, func) 678 | 679 | } 680 | 681 | /** 682 | * Creates a list of [ExtractedResult] which contain all the choices with 683 | * their corresponding score where higher is more similar 684 | * 685 | * @param query The query string 686 | * @param choices A list of choices 687 | * @param toStringFunction The ToStringFunction to be applied to all choices. 688 | * @return A list of the results 689 | */ 690 | fun extractAll(query: String, choices: Collection, 691 | toStringFunction: ToStringFunction): List> { 692 | 693 | val extractor = Extractor() 694 | 695 | return extractor.extractWithoutOrder(query, choices, toStringFunction, WeightedRatio()) 696 | 697 | } 698 | 699 | /** 700 | * Creates a list of [ExtractedResult] which contain all the choices with 701 | * their corresponding score where higher is more similar 702 | * 703 | * @param query The query string 704 | * @param choices A list of choices 705 | * @param toStringFunction The ToStringFunction to be applied to all choices. 706 | * @param cutoff Keep only scores above cutoff 707 | * @return A list of the results 708 | */ 709 | fun extractAll(query: String, choices: Collection, 710 | toStringFunction: ToStringFunction, cutoff: Int): List> { 711 | 712 | val extractor = Extractor(cutoff) 713 | 714 | return extractor.extractWithoutOrder(query, choices, toStringFunction, WeightedRatio()) 715 | 716 | } 717 | 718 | /** 719 | * Find the single best match above a score in a list of choices. 720 | * 721 | * @param query A string to match against 722 | * @param choices A list of choices 723 | * @param toStringFunction The ToStringFunction to be applied to all choices. 724 | * @param func Scoring function 725 | * @return An object containing the best match and it's score 726 | */ 727 | fun extractOne(query: String, choices: Collection, 728 | toStringFunction: ToStringFunction, func: Applicable): BoundExtractedResult { 729 | 730 | val extractor = Extractor() 731 | 732 | return extractor.extractOne(query, choices, toStringFunction, func) 733 | 734 | } 735 | 736 | /** 737 | * Find the single best match above a score in a list of choices. 738 | * 739 | * @param query A string to match against 740 | * @param choices A list of choices 741 | * @param toStringFunction The ToStringFunction to be applied to all choices. 742 | * @return An object containing the best match and it's score 743 | */ 744 | fun extractOne(query: String, choices: Collection, 745 | toStringFunction: ToStringFunction): BoundExtractedResult { 746 | 747 | val extractor = Extractor() 748 | 749 | return extractor.extractOne(query, choices, toStringFunction, WeightedRatio()) 750 | 751 | } 752 | 753 | 754 | } 755 | -------------------------------------------------------------------------------- /app/src/commonMain/kotlin/com/willowtreeapps/fuzzywuzzy/diffutils/PriorityQueue.kt: -------------------------------------------------------------------------------- 1 | package com.willowtreeapps.fuzzywuzzy.diffutils 2 | /* 3 | * Copyright (c) 2017 Kotlin Algorithm Club 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 all 13 | * 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 THE 21 | * SOFTWARE. 22 | */ 23 | 24 | 25 | import kotlin.Comparator 26 | 27 | class PriorityQueue(size: Int, private val comparator: Comparator? = null) : Collection { 28 | override var size: Int = 0 29 | private set 30 | private var arr: Array = Array?>(size) { null } as Array 31 | 32 | fun add(element: T) { 33 | if (size + 1 == arr.size) { 34 | resize() 35 | } 36 | arr[++size] = element 37 | swim(size) 38 | } 39 | 40 | fun peek(): T { 41 | if (size == 0) throw NoSuchElementException() 42 | return arr[1]!! 43 | } 44 | 45 | fun poll(): T { 46 | if (size == 0) throw NoSuchElementException() 47 | val res = peek() 48 | arr.swap(1, size--) 49 | sink(1) 50 | arr[size + 1] = null 51 | if ((size > 0) && (size == (arr.size - 1) / 4)) { 52 | resize() 53 | } 54 | return res 55 | } 56 | 57 | private fun swim(n: Int) { 58 | Companion.swim(arr, n, comparator) 59 | } 60 | 61 | private fun sink(n: Int) { 62 | Companion.sink(arr, n, size, comparator) 63 | } 64 | 65 | private fun resize() { 66 | val old = arr 67 | // arr = Array?>(size * 2, { null }) as Array 68 | arr = old.copyOf(old.size + 1) 69 | // System.arraycopy(old, 0, arr, 0, size + 1) 70 | } 71 | 72 | override fun isEmpty(): Boolean { 73 | return size == 0 74 | } 75 | 76 | override fun contains(element: T): Boolean { 77 | for (obj in this) { 78 | if (obj == element) return true 79 | } 80 | return false 81 | } 82 | 83 | override fun containsAll(elements: Collection): Boolean { 84 | for (element in elements) { 85 | if (!contains(element)) return false 86 | } 87 | return true 88 | } 89 | 90 | override fun iterator(): Iterator { 91 | return arr.copyOfRange(1, size + 1).map { it!! }.iterator() 92 | } 93 | 94 | companion object { 95 | private fun greater(arr: Array, i: Int, j: Int, comparator: Comparator? = null): Boolean { 96 | return if (comparator != null) { 97 | comparator.compare(arr[i], arr[j]) > 0 98 | } else { 99 | val left = arr[i]!! as Comparable 100 | left > arr[j]!! 101 | } 102 | } 103 | 104 | fun sink(arr: Array, a: Int, size: Int, comparator: Comparator? = null ) { 105 | var k = a 106 | while (2 * k <= size) { 107 | var j = 2 * k 108 | if (j < size && greater(arr, j, j + 1, comparator)) j++ 109 | if (!greater(arr, k, j, comparator)) break 110 | arr.swap(k, j) 111 | k = j 112 | } 113 | } 114 | 115 | fun swim(arr: Array, size: Int, comparator: Comparator? = null) { 116 | var n = size 117 | while (n > 1 && greater(arr, n / 2, n, comparator)) { 118 | arr.swap(n, n / 2) 119 | n /= 2 120 | } 121 | } 122 | } 123 | } 124 | 125 | fun Array.swap(i: Int, j: Int) { 126 | val tmp = this[i] 127 | this[i] = this[j] 128 | this[j] = tmp 129 | } -------------------------------------------------------------------------------- /app/src/commonMain/kotlin/com/willowtreeapps/fuzzywuzzy/diffutils/algorithms/BasicAlgorithm.kt: -------------------------------------------------------------------------------- 1 | package com.willowtreeapps.fuzzywuzzy.diffutils.algorithms 2 | 3 | import com.willowtreeapps.fuzzywuzzy.ToStringFunction 4 | import com.willowtreeapps.fuzzywuzzy.diffutils.Applicable 5 | 6 | 7 | abstract class BasicAlgorithm : Applicable { 8 | 9 | var stringFunction: ToStringFunction? = null 10 | internal set 11 | 12 | constructor() { 13 | this.stringFunction = DefaultStringFunction() 14 | } 15 | 16 | constructor(stringFunction: ToStringFunction) { 17 | this.stringFunction = stringFunction 18 | } 19 | 20 | abstract fun apply(s1: String, s2: String, stringProcessor: ToStringFunction): Int 21 | 22 | override fun apply(s1: String, s2: String): Int { 23 | 24 | return apply(s1, s2, this.stringFunction!!) 25 | 26 | } 27 | 28 | fun with(stringFunction: ToStringFunction): BasicAlgorithm { 29 | this.stringFunction = stringFunction 30 | return this 31 | } 32 | 33 | fun noProcessor(): BasicAlgorithm { 34 | this.stringFunction = ToStringFunction.NO_PROCESS 35 | return this 36 | } 37 | } -------------------------------------------------------------------------------- /app/src/commonMain/kotlin/com/willowtreeapps/fuzzywuzzy/diffutils/algorithms/DefaultStringFunction.kt: -------------------------------------------------------------------------------- 1 | package com.willowtreeapps.fuzzywuzzy.diffutils.algorithms 2 | 3 | import com.willowtreeapps.fuzzywuzzy.ToStringFunction 4 | 5 | expect val pattern: String 6 | 7 | class DefaultStringFunction : ToStringFunction { 8 | 9 | /** 10 | * Performs the default string processing on the item string 11 | * 12 | * @param `item` Input string 13 | * @return The processed string 14 | */ 15 | override fun apply(item: String) = subNonAlphaNumeric(item, " ").toLowerCase().trim { it <= ' ' } 16 | 17 | companion object { 18 | 19 | private const val nonUnicodePattern = "[^\\p{Alnum}]" 20 | private val r by lazy { 21 | try { 22 | Regex(pattern) 23 | } catch (e: IllegalArgumentException) { 24 | // Even though Android supports the unicode pattern class 25 | // for some reason it throws an IllegalArgumentException 26 | // if we pass the flag like on standard Java runtime 27 | // 28 | // We catch this and recompile without the flag (unicode should still work) 29 | Regex(nonUnicodePattern) 30 | } 31 | } 32 | 33 | 34 | /** 35 | * Substitute non alphanumeric characters. 36 | * 37 | * @param in The input string 38 | * @param sub The string to substitute with 39 | * @return The replaced string 40 | */ 41 | fun subNonAlphaNumeric(`in`: String, sub: String): String { 42 | val m = r.find(`in`) 43 | return if (m != null) { 44 | r.replace(`in`, sub) 45 | } else { 46 | `in` 47 | } 48 | 49 | } 50 | } 51 | 52 | } -------------------------------------------------------------------------------- /app/src/commonMain/kotlin/com/willowtreeapps/fuzzywuzzy/diffutils/algorithms/PrimitiveUtils.kt: -------------------------------------------------------------------------------- 1 | package com.willowtreeapps.fuzzywuzzy.diffutils.algorithms 2 | 3 | internal object PrimitiveUtils { 4 | 5 | fun max(vararg elems: Double): Double { 6 | 7 | if (elems.isEmpty()) return 0.0 8 | 9 | var best = elems[0] 10 | 11 | for (t in elems) { 12 | if (t > best) { 13 | best = t 14 | } 15 | } 16 | 17 | return best 18 | 19 | } 20 | 21 | fun max(vararg elems: Int): Int { 22 | 23 | if (elems.size == 0) return 0 24 | 25 | var best = elems[0] 26 | 27 | for (t in elems) { 28 | if (t > best) { 29 | best = t 30 | } 31 | } 32 | 33 | return best 34 | 35 | } 36 | 37 | 38 | } -------------------------------------------------------------------------------- /app/src/commonMain/kotlin/com/willowtreeapps/fuzzywuzzy/diffutils/algorithms/RatioAlgorithm.kt: -------------------------------------------------------------------------------- 1 | package com.willowtreeapps.fuzzywuzzy.diffutils.algorithms 2 | 3 | import com.willowtreeapps.fuzzywuzzy.Ratio 4 | import com.willowtreeapps.fuzzywuzzy.ToStringFunction 5 | import com.willowtreeapps.fuzzywuzzy.diffutils.ratio.SimpleRatio 6 | 7 | abstract class RatioAlgorithm : BasicAlgorithm { 8 | 9 | var ratio: Ratio? = null 10 | 11 | constructor() : super() { 12 | this.ratio = SimpleRatio() 13 | } 14 | 15 | constructor(stringFunction: ToStringFunction) : super(stringFunction) {} 16 | 17 | constructor(ratio: Ratio) : super() { 18 | this.ratio = ratio 19 | } 20 | 21 | 22 | constructor(stringFunction: ToStringFunction, ratio: Ratio) : super(stringFunction) { 23 | this.ratio = ratio 24 | } 25 | 26 | abstract fun apply(s1: String, s2: String, ratio: Ratio, stringFunction: ToStringFunction): Int 27 | 28 | fun with(ratio: Ratio): RatioAlgorithm { 29 | this.ratio = ratio 30 | return this 31 | } 32 | 33 | fun apply(s1: String, s2: String, ratio: Ratio): Int { 34 | return apply(s1, s2, ratio, stringFunction!!) 35 | } 36 | 37 | override fun apply(s1: String, s2: String, stringProcessor: ToStringFunction): Int { 38 | return apply(s1, s2, ratio!!, stringProcessor) 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /app/src/commonMain/kotlin/com/willowtreeapps/fuzzywuzzy/diffutils/algorithms/SetUtils.kt: -------------------------------------------------------------------------------- 1 | package com.willowtreeapps.fuzzywuzzy.diffutils.algorithms 2 | 3 | internal object SetUtils { 4 | 5 | fun intersection(s1: Set, s2: Set): Set { 6 | 7 | val intersection = HashSet(s1) 8 | intersection.retainAll(s2) 9 | 10 | return intersection 11 | 12 | } 13 | 14 | fun difference(s1: Set, s2: Set): Set { 15 | 16 | val difference = HashSet(s1) 17 | difference.removeAll(s2) 18 | 19 | return difference 20 | 21 | } 22 | 23 | } -------------------------------------------------------------------------------- /app/src/commonMain/kotlin/com/willowtreeapps/fuzzywuzzy/diffutils/algorithms/TokenSet.kt: -------------------------------------------------------------------------------- 1 | package com.willowtreeapps.fuzzywuzzy.diffutils.algorithms 2 | 3 | import com.willowtreeapps.fuzzywuzzy.Ratio 4 | import com.willowtreeapps.fuzzywuzzy.ToStringFunction 5 | 6 | class TokenSet : RatioAlgorithm() { 7 | 8 | override fun apply(s1: String, s2: String, ratio: Ratio, stringFunction: ToStringFunction): Int { 9 | var s1Copy = s1 10 | var s2Copy = s2 11 | 12 | s1Copy = stringFunction.apply(s1Copy) 13 | s2Copy = stringFunction.apply(s2Copy) 14 | 15 | val tokens1 = Utils.tokenizeSet(s1Copy) 16 | val tokens2 = Utils.tokenizeSet(s2Copy) 17 | 18 | val intersection = SetUtils.intersection(tokens1, tokens2) 19 | val diff1to2 = SetUtils.difference(tokens1, tokens2) 20 | val diff2to1 = SetUtils.difference(tokens2, tokens1) 21 | 22 | val sortedInter = Utils.sortAndJoin(intersection, " ").trim() 23 | val sorted1to2 = (sortedInter + " " + Utils.sortAndJoin(diff1to2, " ")).trim { it <= ' ' } 24 | val sorted2to1 = (sortedInter + " " + Utils.sortAndJoin(diff2to1, " ")).trim { it <= ' ' } 25 | 26 | val results = ArrayList() 27 | 28 | results.add(ratio.apply(sortedInter, sorted1to2)) 29 | results.add(ratio.apply(sortedInter, sorted2to1)) 30 | results.add(ratio.apply(sorted1to2, sorted2to1)) 31 | 32 | return results.max()!! 33 | 34 | } 35 | 36 | } -------------------------------------------------------------------------------- /app/src/commonMain/kotlin/com/willowtreeapps/fuzzywuzzy/diffutils/algorithms/TokenSort.kt: -------------------------------------------------------------------------------- 1 | package com.willowtreeapps.fuzzywuzzy.diffutils.algorithms 2 | 3 | import com.willowtreeapps.fuzzywuzzy.Ratio 4 | import com.willowtreeapps.fuzzywuzzy.ToStringFunction 5 | 6 | 7 | class TokenSort : RatioAlgorithm() { 8 | 9 | override fun apply(s1: String, s2: String, ratio: Ratio, stringFunction: ToStringFunction): Int { 10 | 11 | val sorted1 = processAndSort(s1, stringFunction) 12 | val sorted2 = processAndSort(s2, stringFunction) 13 | 14 | return ratio.apply(sorted1, sorted2) 15 | 16 | } 17 | 18 | private fun processAndSort(input: String, stringProcessor: ToStringFunction): String { 19 | var inputCopy = input 20 | 21 | inputCopy = stringProcessor.apply(inputCopy) 22 | val wordsArray = inputCopy.split("\\s+".toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray() 23 | 24 | val words = listOf(*wordsArray) 25 | val joined = Utils.sortAndJoin(words, " ") 26 | 27 | return joined.trim { it <= ' ' } 28 | 29 | } 30 | 31 | } -------------------------------------------------------------------------------- /app/src/commonMain/kotlin/com/willowtreeapps/fuzzywuzzy/diffutils/algorithms/Utils.kt: -------------------------------------------------------------------------------- 1 | package com.willowtreeapps.fuzzywuzzy.diffutils.algorithms 2 | 3 | import com.willowtreeapps.fuzzywuzzy.diffutils.PriorityQueue 4 | 5 | 6 | object Utils { 7 | 8 | 9 | internal fun tokenize(`in`: String): List { 10 | 11 | return listOf(*`in`.split("\\s+".toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray()) 12 | 13 | } 14 | 15 | internal fun tokenizeSet(`in`: String): Set { 16 | 17 | return HashSet(tokenize(`in`)) 18 | 19 | } 20 | 21 | internal fun sortAndJoin(col: List, sep: String): String { 22 | 23 | // Collections.sort(col) 24 | 25 | return join(col.sorted(), sep) 26 | 27 | } 28 | 29 | internal fun join(strings: List, sep: String): String { 30 | val buf = StringBuilder(strings.size * 16) 31 | 32 | for (i in strings.indices) { 33 | 34 | if (i < strings.size) { 35 | buf.append(sep) 36 | } 37 | 38 | buf.append(strings[i]) 39 | 40 | } 41 | 42 | return buf.toString().trim { it <= ' ' } 43 | } 44 | 45 | internal fun sortAndJoin(col: Set, sep: String): String { 46 | 47 | return sortAndJoin(ArrayList(col), sep) 48 | 49 | } 50 | 51 | fun > findTopKHeap(arr: List, k: Int): List { 52 | val pq = PriorityQueue(arr.size) 53 | 54 | for (x in arr) { 55 | if (pq.size < k) 56 | pq.add(x) 57 | else if (x > pq.peek()) { 58 | pq.poll() 59 | pq.add(x) 60 | } 61 | } 62 | val res = ArrayList() 63 | try { 64 | for (i in k downTo 1) { 65 | res.add(pq.poll()) 66 | } 67 | } catch (e: NoSuchElementException) { 68 | 69 | } 70 | return res 71 | 72 | } 73 | 74 | internal fun > max(vararg elems: T): T? { 75 | 76 | if (elems.isEmpty()) return null 77 | 78 | var best = elems[0] 79 | 80 | for (t in elems) { 81 | if (t.compareTo(best) > 0) { 82 | best = t 83 | } 84 | } 85 | 86 | return best 87 | 88 | } 89 | 90 | 91 | } -------------------------------------------------------------------------------- /app/src/commonMain/kotlin/com/willowtreeapps/fuzzywuzzy/diffutils/algorithms/WeightedRatio.kt: -------------------------------------------------------------------------------- 1 | package com.willowtreeapps.fuzzywuzzy.diffutils.algorithms 2 | 3 | import com.willowtreeapps.fuzzywuzzy.ToStringFunction 4 | import com.willowtreeapps.fuzzywuzzy.diffutils.FuzzySearch.partialRatio 5 | import com.willowtreeapps.fuzzywuzzy.diffutils.FuzzySearch.ratio 6 | import com.willowtreeapps.fuzzywuzzy.diffutils.FuzzySearch.tokenSetPartialRatio 7 | import com.willowtreeapps.fuzzywuzzy.diffutils.FuzzySearch.tokenSetRatio 8 | import com.willowtreeapps.fuzzywuzzy.diffutils.FuzzySearch.tokenSortPartialRatio 9 | import com.willowtreeapps.fuzzywuzzy.diffutils.FuzzySearch.tokenSortRatio 10 | import kotlin.math.max 11 | import kotlin.math.min 12 | import kotlin.math.round 13 | 14 | 15 | class WeightedRatio : BasicAlgorithm() { 16 | 17 | 18 | override fun apply(s1: String, s2: String, stringProcessor: ToStringFunction): Int { 19 | var s1Copy = s1 20 | var s2Copy = s2 21 | 22 | s1Copy = stringProcessor.apply(s1Copy) 23 | s2Copy = stringProcessor.apply(s2Copy) 24 | 25 | val len1 = s1Copy.length 26 | val len2 = s2Copy.length 27 | 28 | if (len1 == 0 || len2 == 0) { 29 | return 0 30 | } 31 | 32 | var tryPartials = TRY_PARTIALS 33 | val unbaseScale = UNBASE_SCALE 34 | var partialScale = PARTIAL_SCALE 35 | 36 | val base = ratio(s1Copy, s2Copy) 37 | val lenRatio = max(len1, len2).toDouble() / min(len1, len2) 38 | 39 | // if strings are similar length don't use partials 40 | if (lenRatio < 1.5) tryPartials = false 41 | 42 | // if one string is much shorter than the other 43 | if (lenRatio > 8) partialScale = .6 44 | 45 | if (tryPartials) { 46 | 47 | val partial = partialRatio(s1Copy, s2Copy) * partialScale 48 | val partialSor = tokenSortPartialRatio(s1Copy, s2Copy) * unbaseScale * partialScale 49 | val partialSet = tokenSetPartialRatio(s1Copy, s2Copy) * unbaseScale * partialScale 50 | 51 | return round(max(max(max(base.toDouble(), partial), partialSor), partialSet)).toInt() 52 | 53 | } else { 54 | 55 | val tokenSort = tokenSortRatio(s1Copy, s2Copy) * unbaseScale 56 | val tokenSet = tokenSetRatio(s1Copy, s2Copy) * unbaseScale 57 | 58 | return round(max(max(base.toDouble(), tokenSort), tokenSet)).toInt() 59 | 60 | } 61 | 62 | } 63 | 64 | companion object { 65 | 66 | const val UNBASE_SCALE = .95 67 | const val PARTIAL_SCALE = .90 68 | const val TRY_PARTIALS = true 69 | } 70 | 71 | } -------------------------------------------------------------------------------- /app/src/commonMain/kotlin/com/willowtreeapps/fuzzywuzzy/diffutils/model/BoundExtractedResult.kt: -------------------------------------------------------------------------------- 1 | package com.willowtreeapps.fuzzywuzzy.diffutils.model 2 | 3 | class BoundExtractedResult(val referent: T, var string: String?, val score: Int, val index: Int) : Comparable> { 4 | 5 | override fun toString(): String { 6 | return "(string: $string, score: $score, index: $index)" 7 | } 8 | 9 | override fun compareTo(other: BoundExtractedResult): Int { 10 | return this.score.compareTo(other.score) 11 | } 12 | } -------------------------------------------------------------------------------- /app/src/commonMain/kotlin/com/willowtreeapps/fuzzywuzzy/diffutils/model/ExtractedResult.kt: -------------------------------------------------------------------------------- 1 | package com.willowtreeapps.fuzzywuzzy.diffutils.model 2 | 3 | data class ExtractedResult(var string: String?, val score: Int, val index: Int) : Comparable { 4 | 5 | override fun compareTo(other: ExtractedResult): Int { 6 | return this.score.compareTo(other.score) 7 | } 8 | 9 | override fun toString(): String { 10 | return "(string: $string, score: $score, index: $index)" 11 | } 12 | } -------------------------------------------------------------------------------- /app/src/commonMain/kotlin/com/willowtreeapps/fuzzywuzzy/diffutils/ratio/PartialRatio.kt: -------------------------------------------------------------------------------- 1 | package com.willowtreeapps.fuzzywuzzy.diffutils.ratio 2 | 3 | import com.willowtreeapps.fuzzywuzzy.Ratio 4 | import com.willowtreeapps.fuzzywuzzy.ToStringFunction 5 | import com.willowtreeapps.fuzzywuzzy.diffutils.DiffUtils 6 | import kotlin.math.round 7 | 8 | 9 | /** 10 | * Partial ratio of similarity 11 | */ 12 | class PartialRatio : Ratio { 13 | 14 | /** 15 | * Computes a partial ratio between the strings 16 | * 17 | * @param s1 Input string 18 | * @param s2 Input string 19 | * @return The partial ratio 20 | */ 21 | override fun apply(s1: String, s2: String): Int { 22 | 23 | val shorter: String 24 | val longer: String 25 | 26 | if (s1.length < s2.length) { 27 | 28 | shorter = s1 29 | longer = s2 30 | 31 | } else { 32 | 33 | shorter = s2 34 | longer = s1 35 | 36 | } 37 | 38 | val matchingBlocks = DiffUtils.getMatchingBlocks(shorter, longer) 39 | 40 | val scores = ArrayList() 41 | 42 | for (mb in matchingBlocks) { 43 | 44 | val dist = mb.dpos - mb.spos 45 | 46 | val longStart = if (dist > 0) dist else 0 47 | var longEnd = longStart + shorter.length 48 | 49 | if (longEnd > longer.length) longEnd = longer.length 50 | 51 | val longSubstr = longer.substring(longStart, longEnd) 52 | 53 | val ratio = DiffUtils.getRatio(shorter, longSubstr) 54 | 55 | if (ratio > .995) { 56 | return 100 57 | } else { 58 | scores.add(ratio) 59 | } 60 | 61 | } 62 | 63 | return round(100 * scores.max()!!).toInt() 64 | 65 | } 66 | 67 | override fun apply(s1: String, s2: String, sp: ToStringFunction): Int { 68 | return apply(sp.apply(s1), sp.apply(s2)) 69 | } 70 | 71 | 72 | } 73 | -------------------------------------------------------------------------------- /app/src/commonMain/kotlin/com/willowtreeapps/fuzzywuzzy/diffutils/ratio/SimpleRatio.kt: -------------------------------------------------------------------------------- 1 | package com.willowtreeapps.fuzzywuzzy.diffutils.ratio 2 | 3 | import com.willowtreeapps.fuzzywuzzy.Ratio 4 | import com.willowtreeapps.fuzzywuzzy.ToStringFunction 5 | import com.willowtreeapps.fuzzywuzzy.diffutils.DiffUtils 6 | import kotlin.math.round 7 | 8 | class SimpleRatio : Ratio { 9 | 10 | /** 11 | * Computes a simple Levenshtein distance ratio between the strings 12 | * 13 | * @param s1 Input string 14 | * @param s2 Input string 15 | * @return The resulting ratio of similarity 16 | */ 17 | override fun apply(s1: String, s2: String): Int { 18 | 19 | return round(100 * DiffUtils.getRatio(s1, s2)).toInt() 20 | 21 | } 22 | 23 | override fun apply(s1: String, s2: String, sp: ToStringFunction): Int { 24 | return apply(sp.apply(s1), sp.apply(s2)) 25 | } 26 | } -------------------------------------------------------------------------------- /app/src/commonMain/kotlin/com/willowtreeapps/fuzzywuzzy/diffutils/structs/EditOps.kt: -------------------------------------------------------------------------------- 1 | package com.willowtreeapps.fuzzywuzzy.diffutils.structs 2 | 3 | data class EditOp( 4 | 5 | var type: EditType? = null, 6 | var spos: Int = 0, // source block pos 7 | var dpos: Int = 0 // destination block pos 8 | ) { 9 | 10 | override fun toString(): String { 11 | return type!!.name + "(" + spos + "," + dpos + ")" 12 | } 13 | } 14 | 15 | class MatchingBlock { 16 | var spos: Int = 0 17 | var dpos: Int = 0 18 | var length: Int = 0 19 | 20 | override fun toString(): String { 21 | return "($spos,$dpos,$length)" 22 | } 23 | } 24 | 25 | class OpCode { 26 | 27 | var type: EditType? = null 28 | var sbeg: Int = 0 29 | var send: Int = 0 30 | var dbeg: Int = 0 31 | var dend: Int = 0 32 | 33 | override fun toString(): String { 34 | return (type!!.name + "(" + sbeg + "," + send + "," 35 | + dbeg + "," + dend + ")") 36 | } 37 | } 38 | 39 | enum class EditType { 40 | DELETE, 41 | EQUAL, 42 | INSERT, 43 | REPLACE, 44 | KEEP 45 | } -------------------------------------------------------------------------------- /app/src/jsMain/kotlin/com/willowtreeapps/fuzzywuzzy/algorithms/PlatformImpls.kt: -------------------------------------------------------------------------------- 1 | package com.willowtreeapps.fuzzywuzzy.algorithms 2 | 3 | -------------------------------------------------------------------------------- /app/src/jsMain/kotlin/com/willowtreeapps/fuzzywuzzy/diffutils/algorithms/pattern.kt: -------------------------------------------------------------------------------- 1 | package com.willowtreeapps.fuzzywuzzy.diffutils.algorithms 2 | 3 | //actual val pattern: String ="[\\W_]+/g" 4 | actual val pattern = """[^\u0041-\u005A\u0061-\u007A\u00AA\u00B5\u00BA\u00C0-\u00D6\u00D8-\u00F6\u00F8-\u02C1\u02C6-\u02D1\u02E0-\u02E4\u02EC\u02EE\u0370-\u0374\u0376\u0377\u037A-\u037D\u0386\u0388-\u038A\u038C\u038E-\u03A1\u03A3-\u03F5\u03F7-\u0481\u048A-\u0527\u0531-\u0556\u0559\u0561-\u0587\u05D0-\u05EA\u05F0-\u05F2\u0620-\u064A\u066E\u066F\u0671-\u06D3\u06D5\u06E5\u06E6\u06EE\u06EF\u06FA-\u06FC\u06FF\u0710\u0712-\u072F\u074D-\u07A5\u07B1\u07CA-\u07EA\u07F4\u07F5\u07FA\u0800-\u0815\u081A\u0824\u0828\u0840-\u0858\u08A0\u08A2-\u08AC\u0904-\u0939\u093D\u0950\u0958-\u0961\u0971-\u0977\u0979-\u097F\u0985-\u098C\u098F\u0990\u0993-\u09A8\u09AA-\u09B0\u09B2\u09B6-\u09B9\u09BD\u09CE\u09DC\u09DD\u09DF-\u09E1\u09F0\u09F1\u0A05-\u0A0A\u0A0F\u0A10\u0A13-\u0A28\u0A2A-\u0A30\u0A32\u0A33\u0A35\u0A36\u0A38\u0A39\u0A59-\u0A5C\u0A5E\u0A72-\u0A74\u0A85-\u0A8D\u0A8F-\u0A91\u0A93-\u0AA8\u0AAA-\u0AB0\u0AB2\u0AB3\u0AB5-\u0AB9\u0ABD\u0AD0\u0AE0\u0AE1\u0B05-\u0B0C\u0B0F\u0B10\u0B13-\u0B28\u0B2A-\u0B30\u0B32\u0B33\u0B35-\u0B39\u0B3D\u0B5C\u0B5D\u0B5F-\u0B61\u0B71\u0B83\u0B85-\u0B8A\u0B8E-\u0B90\u0B92-\u0B95\u0B99\u0B9A\u0B9C\u0B9E\u0B9F\u0BA3\u0BA4\u0BA8-\u0BAA\u0BAE-\u0BB9\u0BD0\u0C05-\u0C0C\u0C0E-\u0C10\u0C12-\u0C28\u0C2A-\u0C33\u0C35-\u0C39\u0C3D\u0C58\u0C59\u0C60\u0C61\u0C85-\u0C8C\u0C8E-\u0C90\u0C92-\u0CA8\u0CAA-\u0CB3\u0CB5-\u0CB9\u0CBD\u0CDE\u0CE0\u0CE1\u0CF1\u0CF2\u0D05-\u0D0C\u0D0E-\u0D10\u0D12-\u0D3A\u0D3D\u0D4E\u0D60\u0D61\u0D7A-\u0D7F\u0D85-\u0D96\u0D9A-\u0DB1\u0DB3-\u0DBB\u0DBD\u0DC0-\u0DC6\u0E01-\u0E30\u0E32\u0E33\u0E40-\u0E46\u0E81\u0E82\u0E84\u0E87\u0E88\u0E8A\u0E8D\u0E94-\u0E97\u0E99-\u0E9F\u0EA1-\u0EA3\u0EA5\u0EA7\u0EAA\u0EAB\u0EAD-\u0EB0\u0EB2\u0EB3\u0EBD\u0EC0-\u0EC4\u0EC6\u0EDC-\u0EDF\u0F00\u0F40-\u0F47\u0F49-\u0F6C\u0F88-\u0F8C\u1000-\u102A\u103F\u1050-\u1055\u105A-\u105D\u1061\u1065\u1066\u106E-\u1070\u1075-\u1081\u108E\u10A0-\u10C5\u10C7\u10CD\u10D0-\u10FA\u10FC-\u1248\u124A-\u124D\u1250-\u1256\u1258\u125A-\u125D\u1260-\u1288\u128A-\u128D\u1290-\u12B0\u12B2-\u12B5\u12B8-\u12BE\u12C0\u12C2-\u12C5\u12C8-\u12D6\u12D8-\u1310\u1312-\u1315\u1318-\u135A\u1380-\u138F\u13A0-\u13F4\u1401-\u166C\u166F-\u167F\u1681-\u169A\u16A0-\u16EA\u1700-\u170C\u170E-\u1711\u1720-\u1731\u1740-\u1751\u1760-\u176C\u176E-\u1770\u1780-\u17B3\u17D7\u17DC\u1820-\u1877\u1880-\u18A8\u18AA\u18B0-\u18F5\u1900-\u191C\u1950-\u196D\u1970-\u1974\u1980-\u19AB\u19C1-\u19C7\u1A00-\u1A16\u1A20-\u1A54\u1AA7\u1B05-\u1B33\u1B45-\u1B4B\u1B83-\u1BA0\u1BAE\u1BAF\u1BBA-\u1BE5\u1C00-\u1C23\u1C4D-\u1C4F\u1C5A-\u1C7D\u1CE9-\u1CEC\u1CEE-\u1CF1\u1CF5\u1CF6\u1D00-\u1DBF\u1E00-\u1F15\u1F18-\u1F1D\u1F20-\u1F45\u1F48-\u1F4D\u1F50-\u1F57\u1F59\u1F5B\u1F5D\u1F5F-\u1F7D\u1F80-\u1FB4\u1FB6-\u1FBC\u1FBE\u1FC2-\u1FC4\u1FC6-\u1FCC\u1FD0-\u1FD3\u1FD6-\u1FDB\u1FE0-\u1FEC\u1FF2-\u1FF4\u1FF6-\u1FFC\u2071\u207F\u2090-\u209C\u2102\u2107\u210A-\u2113\u2115\u2119-\u211D\u2124\u2126\u2128\u212A-\u212D\u212F-\u2139\u213C-\u213F\u2145-\u2149\u214E\u2183\u2184\u2C00-\u2C2E\u2C30-\u2C5E\u2C60-\u2CE4\u2CEB-\u2CEE\u2CF2\u2CF3\u2D00-\u2D25\u2D27\u2D2D\u2D30-\u2D67\u2D6F\u2D80-\u2D96\u2DA0-\u2DA6\u2DA8-\u2DAE\u2DB0-\u2DB6\u2DB8-\u2DBE\u2DC0-\u2DC6\u2DC8-\u2DCE\u2DD0-\u2DD6\u2DD8-\u2DDE\u2E2F\u3005\u3006\u3031-\u3035\u303B\u303C\u3041-\u3096\u309D-\u309F\u30A1-\u30FA\u30FC-\u30FF\u3105-\u312D\u3131-\u318E\u31A0-\u31BA\u31F0-\u31FF\u3400-\u4DB5\u4E00-\u9FCC\uA000-\uA48C\uA4D0-\uA4FD\uA500-\uA60C\uA610-\uA61F\uA62A\uA62B\uA640-\uA66E\uA67F-\uA697\uA6A0-\uA6E5\uA717-\uA71F\uA722-\uA788\uA78B-\uA78E\uA790-\uA793\uA7A0-\uA7AA\uA7F8-\uA801\uA803-\uA805\uA807-\uA80A\uA80C-\uA822\uA840-\uA873\uA882-\uA8B3\uA8F2-\uA8F7\uA8FB\uA90A-\uA925\uA930-\uA946\uA960-\uA97C\uA984-\uA9B2\uA9CF\uAA00-\uAA28\uAA40-\uAA42\uAA44-\uAA4B\uAA60-\uAA76\uAA7A\uAA80-\uAAAF\uAAB1\uAAB5\uAAB6\uAAB9-\uAABD\uAAC0\uAAC2\uAADB-\uAADD\uAAE0-\uAAEA\uAAF2-\uAAF4\uAB01-\uAB06\uAB09-\uAB0E\uAB11-\uAB16\uAB20-\uAB26\uAB28-\uAB2E\uABC0-\uABE2\uAC00-\uD7A3\uD7B0-\uD7C6\uD7CB-\uD7FB\uF900-\uFA6D\uFA70-\uFAD9\uFB00-\uFB06\uFB13-\uFB17\uFB1D\uFB1F-\uFB28\uFB2A-\uFB36\uFB38-\uFB3C\uFB3E\uFB40\uFB41\uFB43\uFB44\uFB46-\uFBB1\uFBD3-\uFD3D\uFD50-\uFD8F\uFD92-\uFDC7\uFDF0-\uFDFB\uFE70-\uFE74\uFE76-\uFEFC\uFF21-\uFF3A\uFF41-\uFF5A\uFF66-\uFFBE\uFFC2-\uFFC7\uFFCA-\uFFCF\uFFD2-\uFFD7\uFFDA-\uFFDC]""" 5 | 6 | -------------------------------------------------------------------------------- /app/src/jvmMain/kotlin/com/willowtreeapps/fuzzywuzzy/diffutils/algorithms/pattern.kt: -------------------------------------------------------------------------------- 1 | package com.willowtreeapps.fuzzywuzzy.diffutils.algorithms 2 | 3 | actual val pattern: String = "(?U)[^\\p{Alnum}]" 4 | -------------------------------------------------------------------------------- /app/src/nativeMain/kotlin/com/willowtreeapps/fuzzywuzzy/diffutils/algorithms/pattern.kt: -------------------------------------------------------------------------------- 1 | package com.willowtreeapps.fuzzywuzzy.diffutils.algorithms 2 | 3 | //actual val pattern: String = "(?U)[^\\p{Alnum}]" 4 | actual val pattern: String = """/[\W]*/""" 5 | -------------------------------------------------------------------------------- /app/src/test/kotlin/com/willowtree/fuzzywuzzy/ExtractorTest.kt: -------------------------------------------------------------------------------- 1 | package com.willowtreeapps.fuzzywuzzy 2 | 3 | import com.willowtreeapps.fuzzywuzzy.diffutils.Extractor 4 | import com.willowtreeapps.fuzzywuzzy.diffutils.algorithms.WeightedRatio 5 | import kotlin.test.BeforeTest 6 | import kotlin.test.Test 7 | import kotlin.test.assertEquals 8 | import kotlin.test.assertTrue 9 | 10 | class ExtractorTest { 11 | 12 | lateinit var choices: List 13 | private lateinit var extractor: Extractor 14 | 15 | @BeforeTest 16 | fun setUp() { 17 | choices = listOf("google", "bing", "facebook", "linkedin", "twitter", "googleplus", "bingnews", "plexoogl") 18 | extractor = Extractor() 19 | } 20 | 21 | @Test 22 | fun testExtractWithoutOrder() { 23 | 24 | val res = extractor.extractWithoutOrder("goolge", choices, WeightedRatio()) 25 | 26 | assertEquals(res.size, choices.size) 27 | assertTrue(res[0].score > 0) 28 | } 29 | 30 | @Test 31 | fun testExtractOne() { 32 | 33 | val res = extractor.extractOne("goolge", choices, WeightedRatio()) 34 | 35 | assertEquals(res.string, "google") 36 | } 37 | 38 | @Test 39 | fun testExtractBests() { 40 | 41 | val res = extractor.extractTop("goolge", choices, WeightedRatio()) 42 | 43 | assertTrue(res[0].string == "google" && res[1].string == "googleplus") 44 | 45 | } 46 | 47 | @Test 48 | fun testExtractBests1() { 49 | 50 | val res = extractor.extractTop("goolge", choices, WeightedRatio(), 3) 51 | 52 | assertEquals(res.size, 3) 53 | assertTrue(res[0].string == "google" && res.get(1).string == "googleplus" && res.get(2).string == "plexoogl") 54 | 55 | } 56 | } -------------------------------------------------------------------------------- /app/src/test/kotlin/com/willowtree/fuzzywuzzy/FuzzyWuzzyTest.kt: -------------------------------------------------------------------------------- 1 | package com.willowtreeapps.fuzzywuzzy 2 | 3 | 4 | import com.willowtreeapps.fuzzywuzzy.diffutils.FuzzySearch 5 | import com.willowtreeapps.fuzzywuzzy.diffutils.ratio.SimpleRatio 6 | import kotlin.test.Test 7 | import kotlin.test.assertEquals 8 | import kotlin.test.assertTrue 9 | 10 | 11 | class FuzzyWuzzyTest { 12 | 13 | 14 | val choices = listOf("google", "bing", "facebook", "linkedin", "twitter", "googleplus", "bingnews", "plexoogl") 15 | val moreChoices = listOf("Atlanta Falcons", "New York Jets", "New York Giants", "Dallas Cowboys") 16 | 17 | 18 | @Test 19 | fun testRatio() { 20 | assertEquals(76, FuzzySearch.ratio("mysmilarstring", "mymostsimilarstsdsdring")) 21 | assertEquals(72, FuzzySearch.ratio("mysmilarstring", "myawfullysimilarstirng")) 22 | assertEquals(97, FuzzySearch.ratio("mysmilarstring", "mysimilarstring")) 23 | assertEquals(75, FuzzySearch.ratio("csr", "c s r")) 24 | 25 | } 26 | 27 | @Test 28 | fun testPartialRatio() { 29 | 30 | assertEquals(71, FuzzySearch.partialRatio("similar", "somewhresimlrbetweenthisstring")) 31 | assertEquals(43, FuzzySearch.partialRatio("similar", "notinheresim")) 32 | assertEquals(38, FuzzySearch.partialRatio("pros holdings, inc.", "settlement facility dow corning trust")) 33 | assertEquals(33, FuzzySearch.partialRatio("Should be the same", "Opposite ways go alike")) 34 | assertEquals(33, FuzzySearch.partialRatio("Opposite ways go alike", "Should be the same")) 35 | 36 | } 37 | 38 | @Test 39 | fun testTokenSortPartial() { 40 | 41 | assertEquals(67, FuzzySearch.tokenSortPartialRatio("mvn", "wwwwww.mavencentral.comm")) 42 | assertEquals(100, FuzzySearch.tokenSortPartialRatio(" order words out of ", " words out of order")) 43 | assertEquals(44, FuzzySearch.tokenSortPartialRatio("Testing token set ratio token", "Added another test")) 44 | 45 | } 46 | 47 | @Test 48 | fun testTokenSortRatio() { 49 | assertEquals(84, FuzzySearch.tokenSortRatio("fuzzy was a bear", "fuzzy fuzzy was a bear")) 50 | 51 | } 52 | 53 | @Test 54 | fun testTokenSetRatio() { 55 | 56 | assertEquals(100, FuzzySearch.tokenSetRatio("fuzzy fuzzy fuzzy bear", "fuzzy was a bear")) 57 | assertEquals(39, FuzzySearch.tokenSetRatio("Testing token set ratio token", "Added another test")) 58 | 59 | } 60 | 61 | @Test 62 | fun testTokenSetPartial() { 63 | 64 | assertEquals(11, FuzzySearch.tokenSetPartialRatio("fuzzy was a bear", "blind 100")) 65 | assertEquals(67, FuzzySearch.partialRatio("chicago transit authority", "cta")) 66 | 67 | } 68 | 69 | @Test 70 | fun testWeightedRatio() { 71 | 72 | 73 | assertEquals(60, FuzzySearch.weightedRatio("mvn", "wwwwww.mavencentral.comm")) 74 | assertEquals(40, FuzzySearch.weightedRatio("mvn", "www'l34.423.4/23.4/234//////www.mavencentral.comm")) 75 | assertEquals(97, FuzzySearch.weightedRatio("The quick brown fox jimps ofver the small lazy dog", 76 | "the quick brown fox jumps over the small lazy dog")) 77 | 78 | } 79 | 80 | @Test 81 | fun testExtractTop() { 82 | 83 | val res = FuzzySearch.extractTop("goolge", choices, 2) 84 | val res2 = FuzzySearch.extractTop("goolge", choices, SimpleRatio(), 2) 85 | 86 | assertEquals(res.size, 2) 87 | assertTrue(res.get(0).string == "google" && res.get(1).string == "googleplus") 88 | 89 | assertEquals(res2.size, 2) 90 | assertTrue(res2.get(0).string == "google" && res2.get(1).string == "googleplus") 91 | 92 | assertTrue(FuzzySearch.extractTop("goolge", choices, 2, 100).isEmpty()) 93 | 94 | } 95 | 96 | @Test 97 | fun testExtractAll() { 98 | 99 | val res = FuzzySearch.extractAll("goolge", choices) 100 | 101 | assertEquals(res.size, choices.size) 102 | assertEquals(res.get(0).string, "google") 103 | 104 | assertEquals(FuzzySearch.extractAll("goolge", choices, 40).size, 3) 105 | 106 | } 107 | 108 | @Test 109 | fun testExtractSorted() { 110 | 111 | val res = FuzzySearch.extractSorted("goolge", choices) 112 | 113 | assertEquals(res.size, choices.size) 114 | assertEquals(res.get(0).string, "google") 115 | assertEquals(res.get(1).string, "googleplus") 116 | 117 | assertEquals(FuzzySearch.extractSorted("goolge", choices, 40).size, 3) 118 | 119 | } 120 | 121 | 122 | @Test 123 | fun testExtractOne() { 124 | 125 | val res = FuzzySearch.extractOne("twiter", choices, SimpleRatio()) 126 | val res2 = FuzzySearch.extractOne("twiter", choices) 127 | val res3 = FuzzySearch.extractOne("cowboys", moreChoices) 128 | 129 | assertEquals(res.string, "twitter") 130 | assertEquals(res2.string, "twitter") 131 | assertTrue(res3.string == "Dallas Cowboys" && res3.score == 90) 132 | 133 | 134 | } 135 | 136 | 137 | } 138 | 139 | 140 | -------------------------------------------------------------------------------- /app/src/test/kotlin/com/willowtree/fuzzywuzzy/algorithms/DefaultStringProcessorTest.kt: -------------------------------------------------------------------------------- 1 | package com.willowtreeapps.fuzzywuzzy.algorithms 2 | 3 | import com.willowtreeapps.fuzzywuzzy.diffutils.algorithms.DefaultStringFunction 4 | import kotlin.test.Ignore 5 | import kotlin.test.Test 6 | import kotlin.test.assertEquals 7 | 8 | class DefaultStringProcessorTest { 9 | 10 | @Ignore 11 | @Test 12 | fun testProcess() { 13 | val inp = "s.trim μεγιουνικουντ n/o/n a.lph.a n.um" 14 | 15 | assertEquals("s trim μεγιουνικουντ n o n a lph a n um", DefaultStringFunction().apply(inp)) 16 | } 17 | } -------------------------------------------------------------------------------- /app/src/wasmMain/kotlin/com/willowtreeapps/fuzzywuzzy/diffutils/algorithms/pattern.kt: -------------------------------------------------------------------------------- 1 | package com.willowtreeapps.fuzzywuzzy.diffutils.algorithms 2 | 3 | actual val pattern: String = "(?U)[^\\p{Alnum}]" 4 | -------------------------------------------------------------------------------- /build.gradle: -------------------------------------------------------------------------------- 1 | buildscript { 2 | apply from: "$rootDir/gradle/dependencies.gradle" 3 | 4 | repositories { 5 | google() 6 | maven { url 'https://dl.bintray.com/jetbrains/kotlin-native-dependencies' } 7 | maven { url "https://plugins.gradle.org/m2/" } 8 | jcenter() 9 | } 10 | 11 | dependencies { 12 | classpath deps.plugins.kotlin 13 | classpath deps.plugins.dokka 14 | classpath deps.plugins.node 15 | } 16 | } 17 | apply from: "$rootDir/gradle/dependencies.gradle" 18 | 19 | allprojects { 20 | repositories { 21 | google() 22 | jcenter() 23 | 24 | } 25 | 26 | group = GROUP 27 | version = VERSION_NAME 28 | } 29 | 30 | task clean(type: Delete) { 31 | delete rootProject.buildDir 32 | } 33 | 34 | def addDependency(Upload task, String groupId, String artifactId, String version, String type) { 35 | task.repositories.withType(org.gradle.api.artifacts.maven.MavenDeployer) { mavenDeployer -> 36 | task.doFirst { 37 | mavenDeployer.pom.whenConfigured { 38 | // For whatever reason we can't 'new' these ourselves. Clone an existing instance instead. 39 | def dependency = it.dependencies.get(0).clone() 40 | dependency.groupId = groupId 41 | dependency.artifactId = artifactId 42 | dependency.version = version 43 | dependency.type = type 44 | it.dependencies.add(dependency) 45 | } 46 | } 47 | } 48 | } 49 | 50 | -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | # Project-wide Gradle settings. 2 | # IDE (e.g. Android Studio) users: 3 | # Gradle settings configured through the IDE *will override* 4 | # any settings specified in this file. 5 | # For more details on how to configure your build environment visit 6 | # http://www.gradle.org/docs/current/userguide/build_environment.html 7 | # Specifies the JVM arguments used for the daemon process. 8 | # The setting is particularly useful for tweaking memory settings. 9 | org.gradle.jvmargs=-Xmx1536m 10 | # When configured, Gradle will run in incubating parallel mode. 11 | # This option should only be used with decoupled projects. More details, visit 12 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects 13 | # org.gradle.parallel=true 14 | # AndroidX package structure to make it clearer which packages are bundled with the 15 | # Android operating system, and which are packaged with your app's APK 16 | # https://developer.android.com/topic/libraries/support-library/androidx-rn 17 | android.useAndroidX=true 18 | # Automatically convert third-party libraries to use AndroidX 19 | android.enableJetifier=true 20 | # Kotlin code style for this project: "official" or "obsolete": 21 | kotlin.code.style=official 22 | 23 | GROUP=com.willowtreeapps.fuzzywuzzy-kotlin 24 | VERSION_NAME=0.1.1 25 | 26 | POM_ARTIFACT_ID=fuzzywuzzy 27 | POM_DESCRIPTION=Fuzzy string matching for kotlin multiplatform 28 | POM_NAME=fuzzywuzzy-kotlin 29 | POM_URL=https://github.com/willowtreeapps/fuzzywuzzy-kotlin/ 30 | POM_SCM_URL=https://github.com/willowtreeapps/fuzzywuzzy-kotlin/ 31 | POM_SCM_CONNECTION=scm:git:git://github.com/willowtreeapps/fuzzywuzzy-kotlin.git 32 | POM_SCM_DEV_CONNECTION=scm:git:ssh://git@github.com/willowtreeapps/fuzzywuzzy-kotlin.git 33 | 34 | POM_LICENCE_NAME=The Apache Software License, Version 2.0 35 | POM_LICENCE_URL=http://www.apache.org/licenses/LICENSE-2.0.txt 36 | POM_LICENCE_DIST=repo 37 | 38 | POM_DEVELOPER_ID=willowtreeapps 39 | POM_DEVELOPER_NAME=WillowTree, LLC. -------------------------------------------------------------------------------- /gradle/dependencies.gradle: -------------------------------------------------------------------------------- 1 | ext.versions = [ 2 | kotlin : '1.3.30', 3 | kotlinCoroutines: '1.2.0', 4 | dokka : '0.9.17', 5 | nodePlugin: '1.2.0' 6 | ] 7 | ext.deps = [ 8 | plugins: [ 9 | android: 'com.android.tools.build:gradle:3.3.0', 10 | kotlin : "org.jetbrains.kotlin:kotlin-gradle-plugin:${versions.kotlin}", 11 | dokka : "org.jetbrains.dokka:dokka-gradle-plugin:${versions.dokka}", 12 | node : "com.moowork.gradle:gradle-node-plugin:${versions.nodePlugin}" 13 | ] 14 | ] 15 | -------------------------------------------------------------------------------- /gradle/pom.gradle: -------------------------------------------------------------------------------- 1 | def pomConfig = { 2 | licenses { 3 | license { 4 | name "The Apache Software License, Version 2.0" 5 | url "http://www.apache.org/licenses/LICENSE-2.0.txt" 6 | distribution "repo" 7 | } 8 | } 9 | developers { 10 | developer { 11 | id "WillowTree" 12 | name "WillowTree Team" 13 | organization "WillowTree" 14 | organizationUrl "http://www.willowtreeapps.com" 15 | } 16 | } 17 | 18 | scm { 19 | url "https://github.com/willowtreeapps/fuzzywuzzy-kotlin" 20 | } 21 | } 22 | 23 | project.ext.configureMavenCentralMetadata = { pom -> 24 | def root = asNode() 25 | root.appendNode('name', project.name) 26 | root.appendNode('description', 'FuzzyWuzzy-Kotlin is a port of the python fuzzy string matching library to Kotlin Multiplatform (JVM, Native, JS, Wasm)') 27 | root.appendNode('url', 'https://github.com/willowtreeapps/fuzzywuzzy-kotlin') 28 | root.children().last() + pomConfig 29 | } -------------------------------------------------------------------------------- /gradle/publish.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'maven' 2 | apply plugin: 'maven-publish' 3 | apply plugin: 'signing' 4 | apply plugin: 'org.jetbrains.dokka' 5 | 6 | 7 | dokka { 8 | impliedPlatforms = ["Common"] // This will force platform tags for all non-common sources e.g. "JVM" 9 | kotlinTasks { 10 | // dokka fails to retrieve sources from MPP-tasks so they must be set empty to avoid exception 11 | // use sourceRoot instead (see below) 12 | [] 13 | } 14 | packageOptions { 15 | prefix = "com.willowtreeapps.fuzzywuzzy" 16 | suppress = true 17 | } 18 | sourceRoot { 19 | // assuming there is only a single source dir... 20 | path = kotlin.sourceSets.commonMain.kotlin.srcDirs[0] 21 | platforms = ["Common"] 22 | } 23 | if (kotlin.sourceSets.getNames().contains("jvmMain")) { 24 | sourceRoot { 25 | // assuming there is only a single source dir... 26 | path = kotlin.sourceSets.jvmMain.kotlin.srcDirs[0] 27 | platforms = ["JVM"] 28 | } 29 | } 30 | if (kotlin.sourceSets.getNames().contains("jsMain")) { 31 | sourceRoot { 32 | // assuming there is only a single source dir... 33 | path = kotlin.sourceSets.jsMain.kotlin.srcDirs[0] 34 | platforms = ["js"] 35 | } 36 | } 37 | if (kotlin.sourceSets.getNames().contains("nativeMain")) { 38 | sourceRoot { 39 | // assuming there is only a single source dir... 40 | path = kotlin.sourceSets.nativeMain.kotlin.srcDirs[0] 41 | platforms = ["native"] 42 | } 43 | } 44 | } 45 | 46 | def isReleaseBuild() { 47 | return VERSION_NAME.contains("SNAPSHOT") == false 48 | } 49 | 50 | def getReleaseRepositoryUrl() { 51 | return hasProperty('RELEASE_REPOSITORY_URL') ? RELEASE_REPOSITORY_URL : 52 | "https://oss.sonatype.org/service/local/staging/deploy/maven2/" 53 | } 54 | 55 | def getSnapshotRepositoryUrl() { 56 | return hasProperty('SNAPSHOT_REPOSITORY_URL') ? SNAPSHOT_REPOSITORY_URL : 57 | "https://oss.sonatype.org/content/repositories/snapshots/" 58 | } 59 | 60 | def getRepositoryUsername() { 61 | return hasProperty('SONATYPE_NEXUS_USERNAME') ? SONATYPE_NEXUS_USERNAME : "" 62 | } 63 | 64 | def getRepositoryPassword() { 65 | return hasProperty('SONATYPE_NEXUS_PASSWORD') ? SONATYPE_NEXUS_PASSWORD : "" 66 | } 67 | 68 | task emptySourcesJar(type: Jar) { 69 | classifier = 'sources' 70 | } 71 | 72 | task javadocsJar(type: Jar, dependsOn: dokka) { 73 | classifier = 'javadoc' 74 | from dokka.outputDirectory 75 | } 76 | 77 | signing { 78 | required { isReleaseBuild() && gradle.taskGraph.hasTask("uploadArchives") } 79 | sign(publishing.publications) 80 | } 81 | 82 | publishing { 83 | publications.all { 84 | artifact javadocsJar 85 | 86 | pom.withXml { 87 | def root = asNode() 88 | 89 | root.children().last() + { 90 | resolveStrategy = Closure.DELEGATE_FIRST 91 | 92 | description POM_DESCRIPTION 93 | name POM_NAME 94 | url POM_URL 95 | licenses { 96 | license { 97 | name POM_LICENCE_NAME 98 | url POM_LICENCE_URL 99 | distribution POM_LICENCE_DIST 100 | } 101 | } 102 | scm { 103 | url POM_SCM_URL 104 | connection POM_SCM_CONNECTION 105 | developerConnection POM_SCM_DEV_CONNECTION 106 | } 107 | developers { 108 | developer { 109 | id POM_DEVELOPER_ID 110 | name POM_DEVELOPER_NAME 111 | } 112 | } 113 | } 114 | } 115 | } 116 | 117 | afterEvaluate { 118 | publications.getByName('kotlinMultiplatform') { 119 | // Source jars are only created for platforms, not the common artifact. 120 | artifact emptySourcesJar 121 | } 122 | } 123 | 124 | repositories { 125 | maven { 126 | url isReleaseBuild() ? getReleaseRepositoryUrl() : getSnapshotRepositoryUrl() 127 | credentials { 128 | username getRepositoryUsername() 129 | password getRepositoryPassword() 130 | } 131 | } 132 | maven { 133 | name 'test' 134 | url "file://${rootProject.buildDir}/localMaven" 135 | } 136 | } 137 | } 138 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/willowtreeapps/fuzzywuzzy-kotlin/64f9d3c409d6808024297dc7258edcc87793a7d8/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Wed Apr 17 16:40:04 EDT 2019 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | distributionUrl=https\://services.gradle.org/distributions/gradle-5.3.1-all.zip 7 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | ############################################################################## 4 | ## 5 | ## Gradle start up script for UN*X 6 | ## 7 | ############################################################################## 8 | 9 | # Attempt to set APP_HOME 10 | # Resolve links: $0 may be a link 11 | PRG="$0" 12 | # Need this for relative symlinks. 13 | while [ -h "$PRG" ] ; do 14 | ls=`ls -ld "$PRG"` 15 | link=`expr "$ls" : '.*-> \(.*\)$'` 16 | if expr "$link" : '/.*' > /dev/null; then 17 | PRG="$link" 18 | else 19 | PRG=`dirname "$PRG"`"/$link" 20 | fi 21 | done 22 | SAVED="`pwd`" 23 | cd "`dirname \"$PRG\"`/" >/dev/null 24 | APP_HOME="`pwd -P`" 25 | cd "$SAVED" >/dev/null 26 | 27 | APP_NAME="Gradle" 28 | APP_BASE_NAME=`basename "$0"` 29 | 30 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 31 | DEFAULT_JVM_OPTS="" 32 | 33 | # Use the maximum available, or set MAX_FD != -1 to use that value. 34 | MAX_FD="maximum" 35 | 36 | warn () { 37 | echo "$*" 38 | } 39 | 40 | die () { 41 | echo 42 | echo "$*" 43 | echo 44 | exit 1 45 | } 46 | 47 | # OS specific support (must be 'true' or 'false'). 48 | cygwin=false 49 | msys=false 50 | darwin=false 51 | nonstop=false 52 | case "`uname`" in 53 | CYGWIN* ) 54 | cygwin=true 55 | ;; 56 | Darwin* ) 57 | darwin=true 58 | ;; 59 | MINGW* ) 60 | msys=true 61 | ;; 62 | NONSTOP* ) 63 | nonstop=true 64 | ;; 65 | esac 66 | 67 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 68 | 69 | # Determine the Java command to use to start the JVM. 70 | if [ -n "$JAVA_HOME" ] ; then 71 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 72 | # IBM's JDK on AIX uses strange locations for the executables 73 | JAVACMD="$JAVA_HOME/jre/sh/java" 74 | else 75 | JAVACMD="$JAVA_HOME/bin/java" 76 | fi 77 | if [ ! -x "$JAVACMD" ] ; then 78 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 79 | 80 | Please set the JAVA_HOME variable in your environment to match the 81 | location of your Java installation." 82 | fi 83 | else 84 | JAVACMD="java" 85 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 86 | 87 | Please set the JAVA_HOME variable in your environment to match the 88 | location of your Java installation." 89 | fi 90 | 91 | # Increase the maximum file descriptors if we can. 92 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then 93 | MAX_FD_LIMIT=`ulimit -H -n` 94 | if [ $? -eq 0 ] ; then 95 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 96 | MAX_FD="$MAX_FD_LIMIT" 97 | fi 98 | ulimit -n $MAX_FD 99 | if [ $? -ne 0 ] ; then 100 | warn "Could not set maximum file descriptor limit: $MAX_FD" 101 | fi 102 | else 103 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 104 | fi 105 | fi 106 | 107 | # For Darwin, add options to specify how the application appears in the dock 108 | if $darwin; then 109 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 110 | fi 111 | 112 | # For Cygwin, switch paths to Windows format before running java 113 | if $cygwin ; then 114 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 115 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 116 | JAVACMD=`cygpath --unix "$JAVACMD"` 117 | 118 | # We build the pattern for arguments to be converted via cygpath 119 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 120 | SEP="" 121 | for dir in $ROOTDIRSRAW ; do 122 | ROOTDIRS="$ROOTDIRS$SEP$dir" 123 | SEP="|" 124 | done 125 | OURCYGPATTERN="(^($ROOTDIRS))" 126 | # Add a user-defined pattern to the cygpath arguments 127 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 128 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 129 | fi 130 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 131 | i=0 132 | for arg in "$@" ; do 133 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 134 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 135 | 136 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 137 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 138 | else 139 | eval `echo args$i`="\"$arg\"" 140 | fi 141 | i=$((i+1)) 142 | done 143 | case $i in 144 | (0) set -- ;; 145 | (1) set -- "$args0" ;; 146 | (2) set -- "$args0" "$args1" ;; 147 | (3) set -- "$args0" "$args1" "$args2" ;; 148 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;; 149 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 150 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 151 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 152 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 153 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 154 | esac 155 | fi 156 | 157 | # Escape application args 158 | save () { 159 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done 160 | echo " " 161 | } 162 | APP_ARGS=$(save "$@") 163 | 164 | # Collect all arguments for the java command, following the shell quoting and substitution rules 165 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" 166 | 167 | # by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong 168 | if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then 169 | cd "$(dirname "$0")" 170 | fi 171 | 172 | exec "$JAVACMD" "$@" 173 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @if "%DEBUG%" == "" @echo off 2 | @rem ########################################################################## 3 | @rem 4 | @rem Gradle startup script for Windows 5 | @rem 6 | @rem ########################################################################## 7 | 8 | @rem Set local scope for the variables with windows NT shell 9 | if "%OS%"=="Windows_NT" setlocal 10 | 11 | set DIRNAME=%~dp0 12 | if "%DIRNAME%" == "" set DIRNAME=. 13 | set APP_BASE_NAME=%~n0 14 | set APP_HOME=%DIRNAME% 15 | 16 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 17 | set DEFAULT_JVM_OPTS= 18 | 19 | @rem Find java.exe 20 | if defined JAVA_HOME goto findJavaFromJavaHome 21 | 22 | set JAVA_EXE=java.exe 23 | %JAVA_EXE% -version >NUL 2>&1 24 | if "%ERRORLEVEL%" == "0" goto init 25 | 26 | echo. 27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 28 | echo. 29 | echo Please set the JAVA_HOME variable in your environment to match the 30 | echo location of your Java installation. 31 | 32 | goto fail 33 | 34 | :findJavaFromJavaHome 35 | set JAVA_HOME=%JAVA_HOME:"=% 36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 37 | 38 | if exist "%JAVA_EXE%" goto init 39 | 40 | echo. 41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 42 | echo. 43 | echo Please set the JAVA_HOME variable in your environment to match the 44 | echo location of your Java installation. 45 | 46 | goto fail 47 | 48 | :init 49 | @rem Get command-line arguments, handling Windows variants 50 | 51 | if not "%OS%" == "Windows_NT" goto win9xME_args 52 | 53 | :win9xME_args 54 | @rem Slurp the command line arguments. 55 | set CMD_LINE_ARGS= 56 | set _SKIP=2 57 | 58 | :win9xME_args_slurp 59 | if "x%~1" == "x" goto execute 60 | 61 | set CMD_LINE_ARGS=%* 62 | 63 | :execute 64 | @rem Setup the command line 65 | 66 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 67 | 68 | @rem Execute Gradle 69 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 70 | 71 | :end 72 | @rem End local scope for the variables with windows NT shell 73 | if "%ERRORLEVEL%"=="0" goto mainEnd 74 | 75 | :fail 76 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 77 | rem the _cmd.exe /c_ return code! 78 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 79 | exit /b 1 80 | 81 | :mainEnd 82 | if "%OS%"=="Windows_NT" endlocal 83 | 84 | :omega 85 | -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | pluginManagement { 2 | resolutionStrategy { 3 | eachPlugin { 4 | if (requested.id.id == "kotlin-multiplatform") { 5 | useModule("org.jetbrains.kotlin:kotlin-gradle-plugin:${requested.version}") 6 | } 7 | } 8 | } 9 | } 10 | 11 | include ':app' 12 | rootProject.name='FuzzyWuzzy' 13 | 14 | 15 | rootProject.name = 'test' 16 | enableFeaturePreview('GRADLE_METADATA') 17 | enableFeaturePreview('STABLE_PUBLISHING') 18 | 19 | --------------------------------------------------------------------------------