From b6078694384664b50be378980069b9ab3b7ef125 Mon Sep 17 00:00:00 2001 From: retep007 Date: Wed, 1 May 2019 10:57:22 +0200 Subject: [PATCH 1/2] Add suggestion for misspelled component name using Levenshtein distance Closes #1816 --- Cargo.lock | 7 +++++++ Cargo.toml | 1 + src/dist/manifest.rs | 8 ++++++++ src/errors.rs | 4 ++-- src/toolchain.rs | 46 ++++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 64 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 20e2071303..ead20c6c51 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -638,6 +638,11 @@ name = "lazycell" version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" +[[package]] +name = "levenshtein" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" + [[package]] name = "libc" version = "0.2.51" @@ -1236,6 +1241,7 @@ dependencies = [ "flate2 1.0.7 (registry+https://github.com/rust-lang/crates.io-index)", "git-testament 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)", "lazy_static 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)", + "levenshtein 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)", "libc 0.2.51 (registry+https://github.com/rust-lang/crates.io-index)", "markdown 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", "openssl 0.10.20 (registry+https://github.com/rust-lang/crates.io-index)", @@ -1954,6 +1960,7 @@ dependencies = [ "checksum kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "7507624b29483431c0ba2d82aece8ca6cdba9382bff4ddd0f7490560c056098d" "checksum lazy_static 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "bc5729f27f159ddd61f4df6228e827e86643d4d3e7c32183cb30a1c08f604a14" "checksum lazycell 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "b294d6fa9ee409a054354afc4352b0b9ef7ca222c69b8812cbea9e7d2bf3783f" +"checksum levenshtein 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)" = "66189c12161c65c0023ceb53e2fccc0013311bcb36a7cbd0f9c5e938b408ac96" "checksum libc 0.2.51 (registry+https://github.com/rust-lang/crates.io-index)" = "bedcc7a809076656486ffe045abeeac163da1b558e963a31e29fbfbeba916917" "checksum libz-sys 1.0.25 (registry+https://github.com/rust-lang/crates.io-index)" = "2eb5e43362e38e2bca2fd5f5134c4d4564a23a5c28e9b95411652021a8675ebe" "checksum lock_api 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "62ebf1391f6acad60e5c8b43706dde4582df75c06698ab44511d15016bc2442c" diff --git a/Cargo.toml b/Cargo.toml index ffcb08f7a6..87b5ab649c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -29,6 +29,7 @@ error-chain = "0.12" flate2 = "1" git-testament = "0.1.4" lazy_static = "1" +levenshtein = "1.0.4" libc = "0.2" markdown = "0.2" # Used by `curl` or `reqwest` backend although it isn't imported diff --git a/src/dist/manifest.rs b/src/dist/manifest.rs index 9c861ea0e1..fdf887563f 100644 --- a/src/dist/manifest.rs +++ b/src/dist/manifest.rs @@ -448,4 +448,12 @@ impl Component { pkg.to_string() } } + pub fn description_in_manifest(&self) -> String { + let pkg = self.short_name_in_manifest(); + if let Some(ref t) = self.target { + format!("'{}' for target '{}'", pkg, t) + } else { + format!("'{}'", pkg) + } + } } diff --git a/src/errors.rs b/src/errors.rs index 1155b1fc97..69b2e49e66 100644 --- a/src/errors.rs +++ b/src/errors.rs @@ -302,9 +302,9 @@ error_chain! { description("toolchain does not support components") display("toolchain '{}' does not support components", t) } - UnknownComponent(t: String, c: String) { + UnknownComponent(t: String, c: String, s: String) { description("toolchain does not contain component") - display("toolchain '{}' does not contain component {}", t, c) + display("toolchain '{}' does not contain component {}{}", t, c, s) } AddingRequiredComponent(t: String, c: String) { description("required component cannot be added") diff --git a/src/toolchain.rs b/src/toolchain.rs index 237d50f1e6..60f1066193 100644 --- a/src/toolchain.rs +++ b/src/toolchain.rs @@ -604,6 +604,7 @@ impl<'a> Toolchain<'a> { return Err(ErrorKind::UnknownComponent( self.name.to_string(), component.description(&manifest), + self.get_component_suggestion(&component), ) .into()); } @@ -629,6 +630,50 @@ impl<'a> Toolchain<'a> { } } + fn get_component_suggestion(&self, component: &Component) -> String { + use levenshtein::levenshtein; + // Suggest only for very small differences + // High number can result in innacurate suggestions for short queries e.g. `rls` + const MAX_DISTANCE: usize = 3; + + let components = self.list_components(); + if let Ok(components) = components { + let min = components + .iter() + .filter(|c| c.installed) + .map(|c| { + ( + levenshtein( + &c.component.name_in_manifest()[..], + &component.name_in_manifest()[..], + ), + c, + ) + }) + .min_by_key(|t| t.0) + .expect("There should be always at least one component"); + + // If suggestion is to different don't suggest anything + if min.0 > MAX_DISTANCE { + return String::new(); + } + // If compnent differs suggest only component + if min.1.component.short_name_in_manifest() == component.short_name_in_manifest() { + return format!( + " - Did you mean {} (targets differ)?", + min.1.component.description_in_manifest() + ); + } + + format!( + " - Did you mean {}?", + min.1.component.short_name_in_manifest() + ) + } else { + String::new() + } + } + pub fn remove_component(&self, mut component: Component) -> Result<()> { if !self.exists() { return Err(ErrorKind::ToolchainNotInstalled(self.name.to_owned()).into()); @@ -674,6 +719,7 @@ impl<'a> Toolchain<'a> { return Err(ErrorKind::UnknownComponent( self.name.to_string(), component.description(&manifest), + self.get_component_suggestion(&component), ) .into()); } From 654576fecf836768456d5a9e3c551674c6e56e36 Mon Sep 17 00:00:00 2001 From: "Filip (Glamhoth) Demski" Date: Wed, 1 May 2019 16:08:38 +0200 Subject: [PATCH 2/2] Replace levenshtein distance with Damerau levenshtein. Add tests. --- Cargo.lock | 79 +++++++++++++++++++++++++++++++++++++++++++----- Cargo.toml | 2 +- src/toolchain.rs | 18 +++++------ tests/cli-v2.rs | 65 +++++++++++++++++++++++++++++++++++++++ 4 files changed, 147 insertions(+), 17 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index ead20c6c51..b5d963a098 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -370,6 +370,11 @@ name = "dtoa" version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" +[[package]] +name = "either" +version = "1.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" + [[package]] name = "encoding_rs" version = "0.8.17" @@ -536,6 +541,15 @@ dependencies = [ "tokio-io 0.1.12 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "hashbrown" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "byteorder 1.3.1 (registry+https://github.com/rust-lang/crates.io-index)", + "scopeguard 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "http" version = "0.1.17" @@ -614,6 +628,14 @@ dependencies = [ "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "itertools" +version = "0.7.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "either 1.5.2 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "itoa" version = "0.4.3" @@ -638,11 +660,6 @@ name = "lazycell" version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -[[package]] -name = "levenshtein" -version = "1.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" - [[package]] name = "libc" version = "0.2.51" @@ -700,6 +717,14 @@ name = "matches" version = "0.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" +[[package]] +name = "matrixmultiply" +version = "0.1.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "rawpointer 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "memchr" version = "0.1.11" @@ -811,6 +836,17 @@ dependencies = [ "tempfile 3.0.7 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "ndarray" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "itertools 0.7.11 (registry+https://github.com/rust-lang/crates.io-index)", + "matrixmultiply 0.1.15 (registry+https://github.com/rust-lang/crates.io-index)", + "num-complex 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", + "num-traits 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "net2" version = "0.2.33" @@ -826,6 +862,14 @@ name = "nodrop" version = "0.1.13" source = "registry+https://github.com/rust-lang/crates.io-index" +[[package]] +name = "num-complex" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "num-traits 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "num-integer" version = "0.1.39" @@ -1106,6 +1150,11 @@ dependencies = [ "rand_core 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "rawpointer" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + [[package]] name = "rdrand" version = "0.4.0" @@ -1241,7 +1290,6 @@ dependencies = [ "flate2 1.0.7 (registry+https://github.com/rust-lang/crates.io-index)", "git-testament 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)", "lazy_static 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)", - "levenshtein 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)", "libc 0.2.51 (registry+https://github.com/rust-lang/crates.io-index)", "markdown 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", "openssl 0.10.20 (registry+https://github.com/rust-lang/crates.io-index)", @@ -1252,6 +1300,7 @@ dependencies = [ "scopeguard 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", "semver 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)", "sha2 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)", + "strsim 0.9.1 (registry+https://github.com/rust-lang/crates.io-index)", "tar 0.4.22 (registry+https://github.com/rust-lang/crates.io-index)", "tempdir 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)", "term 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", @@ -1435,6 +1484,15 @@ name = "strsim" version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" +[[package]] +name = "strsim" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "hashbrown 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)", + "ndarray 0.12.1 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "syn" version = "0.15.31" @@ -1929,6 +1987,7 @@ dependencies = [ "checksum digest 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)" = "05f47366984d3ad862010e22c7ce81a7dbcaebbdfb37241a620f8b6596ee135c" "checksum dirs 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)" = "3fd78930633bd1c6e35c4b42b1df7b0cbc6bc191146e512bb3bedf243fcc3901" "checksum dtoa 0.4.3 (registry+https://github.com/rust-lang/crates.io-index)" = "6d301140eb411af13d3115f9a562c85cc6b541ade9dfa314132244aaee7489dd" +"checksum either 1.5.2 (registry+https://github.com/rust-lang/crates.io-index)" = "5527cfe0d098f36e3f8839852688e63c8fff1c90b2b405aef730615f9a7bcf7b" "checksum encoding_rs 0.8.17 (registry+https://github.com/rust-lang/crates.io-index)" = "4155785c79f2f6701f185eb2e6b4caf0555ec03477cb4c70db67b465311620ed" "checksum env_proxy 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "700798562fcbc0a4c89546df5dfa8586e82345026e3992242646d527dec948e4" "checksum error-chain 0.12.0 (registry+https://github.com/rust-lang/crates.io-index)" = "07e791d3be96241c77c43846b665ef1384606da2cd2a48730abe606a12906e02" @@ -1949,6 +2008,7 @@ dependencies = [ "checksum git-testament 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)" = "0b00f56c9844ed5d927a5cfccfe5d4a32fffa3b8a56f14c0634e70c613dc584b" "checksum git-testament-derive 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "4abfb23d91a9da7cec4796bc94b3009fe87812d909035faea726681d2f3e3b5e" "checksum h2 0.1.18 (registry+https://github.com/rust-lang/crates.io-index)" = "85ab6286db06040ddefb71641b50017c06874614001a134b423783e2db2920bd" +"checksum hashbrown 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)" = "3bae29b6653b3412c2e71e9d486db9f9df5d701941d86683005efb9f2d28e3da" "checksum http 0.1.17 (registry+https://github.com/rust-lang/crates.io-index)" = "eed324f0f0daf6ec10c474f150505af2c143f251722bf9dbd1261bd1f2ee2c1a" "checksum httparse 1.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "e8734b0cfd3bc3e101ec59100e101c2eecd19282202e87808b3037b442777a83" "checksum hyper 0.12.27 (registry+https://github.com/rust-lang/crates.io-index)" = "4f2777434f26af6e4ce4fdcdccd3bed9d861d11e87bcbe72c0f51ddaca8ff848" @@ -1956,11 +2016,11 @@ dependencies = [ "checksum idna 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "38f09e0f0b1fb55fdee1f17470ad800da77af5186a1a76c026b679358b7e844e" "checksum indexmap 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "7e81a7c05f79578dbc15793d8b619db9ba32b4577003ef3af1a91c416798c58d" "checksum iovec 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "dbe6e417e7d0975db6512b90796e8ce223145ac4e33c377e4a42882a0e88bb08" +"checksum itertools 0.7.11 (registry+https://github.com/rust-lang/crates.io-index)" = "0d47946d458e94a1b7bcabbf6521ea7c037062c81f534615abcad76e84d4970d" "checksum itoa 0.4.3 (registry+https://github.com/rust-lang/crates.io-index)" = "1306f3464951f30e30d12373d31c79fbd52d236e5e896fd92f96ec7babbbe60b" "checksum kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "7507624b29483431c0ba2d82aece8ca6cdba9382bff4ddd0f7490560c056098d" "checksum lazy_static 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "bc5729f27f159ddd61f4df6228e827e86643d4d3e7c32183cb30a1c08f604a14" "checksum lazycell 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "b294d6fa9ee409a054354afc4352b0b9ef7ca222c69b8812cbea9e7d2bf3783f" -"checksum levenshtein 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)" = "66189c12161c65c0023ceb53e2fccc0013311bcb36a7cbd0f9c5e938b408ac96" "checksum libc 0.2.51 (registry+https://github.com/rust-lang/crates.io-index)" = "bedcc7a809076656486ffe045abeeac163da1b558e963a31e29fbfbeba916917" "checksum libz-sys 1.0.25 (registry+https://github.com/rust-lang/crates.io-index)" = "2eb5e43362e38e2bca2fd5f5134c4d4564a23a5c28e9b95411652021a8675ebe" "checksum lock_api 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "62ebf1391f6acad60e5c8b43706dde4582df75c06698ab44511d15016bc2442c" @@ -1968,6 +2028,7 @@ dependencies = [ "checksum lzma-sys 0.1.14 (registry+https://github.com/rust-lang/crates.io-index)" = "16b5c59c57cc4d39e7999f50431aa312ea78af7c93b23fbb0c3567bd672e7f35" "checksum markdown 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "bdb7e864aa1dccbebb05751e899bc84c639df47490c0c24caf4b1a77770b6566" "checksum matches 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)" = "7ffc5c5338469d4d3ea17d269fa8ea3512ad247247c30bd2df69e68309ed0a08" +"checksum matrixmultiply 0.1.15 (registry+https://github.com/rust-lang/crates.io-index)" = "dcad67dcec2d58ff56f6292582377e6921afdf3bfbd533e26fb8900ae575e002" "checksum memchr 0.1.11 (registry+https://github.com/rust-lang/crates.io-index)" = "d8b629fb514376c675b98c1421e80b151d3817ac42d7c667717d282761418d20" "checksum memchr 2.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "2efc7bc57c883d4a4d6e3246905283d8dae951bb3bd32f49d6ef297f546e1c39" "checksum memoffset 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "0f9dc261e2b62d7a622bf416ea3c5245cdd5d9a7fcc428c0d06804dfce1775b3" @@ -1979,8 +2040,10 @@ dependencies = [ "checksum mio 0.6.16 (registry+https://github.com/rust-lang/crates.io-index)" = "71646331f2619b1026cc302f87a2b8b648d5c6dd6937846a16cc8ce0f347f432" "checksum miow 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "8c1f2f3b1cf331de6896aabf6e9d55dca90356cc9960cca7eaaf408a355ae919" "checksum native-tls 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "ff8e08de0070bbf4c31f452ea2a70db092f36f6f2e4d897adf5674477d488fb2" +"checksum ndarray 0.12.1 (registry+https://github.com/rust-lang/crates.io-index)" = "7cf380a8af901ad627594013a3bbac903ae0a6f94e176e47e46b5bbc1877b928" "checksum net2 0.2.33 (registry+https://github.com/rust-lang/crates.io-index)" = "42550d9fb7b6684a6d404d9fa7250c2eb2646df731d1c06afc06dcee9e1bcf88" "checksum nodrop 0.1.13 (registry+https://github.com/rust-lang/crates.io-index)" = "2f9667ddcc6cc8a43afc9b7917599d7216aa09c463919ea32c59ed6cac8bc945" +"checksum num-complex 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "107b9be86cd2481930688277b675b0114578227f034674726605b8a482d8baf8" "checksum num-integer 0.1.39 (registry+https://github.com/rust-lang/crates.io-index)" = "e83d528d2677f0518c570baf2b7abdcf0cd2d248860b68507bdcb3e91d4c0cea" "checksum num-traits 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)" = "0b3a5d7cc97d6d30d8b9bc8fa19bf45349ffe46241e8816f50f62f6d6aaabee1" "checksum num_cpus 1.10.0 (registry+https://github.com/rust-lang/crates.io-index)" = "1a23f0ed30a54abaa0c7e83b1d2d87ada7c3c23078d1d87815af3e3b6385fbba" @@ -2013,6 +2076,7 @@ dependencies = [ "checksum rand_os 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "7b75f676a1e053fc562eafbb47838d67c84801e38fc1ba459e8f180deabd5071" "checksum rand_pcg 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "abf9b09b01790cfe0364f52bf32995ea3c39f4d2dd011eac241d2914146d0b44" "checksum rand_xorshift 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "cbf7e9e623549b0e21f6e97cf8ecf247c1a8fd2e8a992ae265314300b2455d5c" +"checksum rawpointer 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ebac11a9d2e11f2af219b8b8d833b76b1ea0e054aa0e8d8e9e4cbde353bdf019" "checksum rdrand 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "678054eb77286b51581ba43620cc911abf02758c91f93f479767aed0f90458b2" "checksum redox_syscall 0.1.54 (registry+https://github.com/rust-lang/crates.io-index)" = "12229c14a0f65c4f1cb046a3b52047cdd9da1f4b30f8a39c5063c8bae515e252" "checksum redox_termios 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "7e891cfe48e9100a70a3b6eb652fef28920c117d366339687bd5576160db0f76" @@ -2048,6 +2112,7 @@ dependencies = [ "checksum stable_deref_trait 1.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "dba1a27d3efae4351c8051072d619e3ade2820635c3958d826bfea39d59b54c8" "checksum string 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "b639411d0b9c738748b5397d5ceba08e648f4f1992231aa859af1a017f31f60b" "checksum strsim 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)" = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a" +"checksum strsim 0.9.1 (registry+https://github.com/rust-lang/crates.io-index)" = "34ac666ab1423aa93bbd4cd47b6e62db5a846df4e28b959d823776eed5b57643" "checksum syn 0.15.31 (registry+https://github.com/rust-lang/crates.io-index)" = "d2b4cfac95805274c6afdb12d8f770fa2d27c045953e7b630a81801953699a9a" "checksum synstructure 0.10.1 (registry+https://github.com/rust-lang/crates.io-index)" = "73687139bf99285483c96ac0add482c3776528beac1d97d444f6e91f203a2015" "checksum tar 0.4.22 (registry+https://github.com/rust-lang/crates.io-index)" = "c2167ff53da2a661702b3299f71a91b61b1dffef36b4b2884b1f9c67254c0133" diff --git a/Cargo.toml b/Cargo.toml index 87b5ab649c..92768da850 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -29,7 +29,6 @@ error-chain = "0.12" flate2 = "1" git-testament = "0.1.4" lazy_static = "1" -levenshtein = "1.0.4" libc = "0.2" markdown = "0.2" # Used by `curl` or `reqwest` backend although it isn't imported @@ -41,6 +40,7 @@ remove_dir_all = "0.5.1" same-file = "1" scopeguard = "1" semver = "0.9" +strsim = "0.9.1" sha2 = "0.8" tar = "0.4" tempdir = "0.3.4" diff --git a/src/toolchain.rs b/src/toolchain.rs index 60f1066193..ab77f3628a 100644 --- a/src/toolchain.rs +++ b/src/toolchain.rs @@ -604,7 +604,7 @@ impl<'a> Toolchain<'a> { return Err(ErrorKind::UnknownComponent( self.name.to_string(), component.description(&manifest), - self.get_component_suggestion(&component), + self.get_component_suggestion(&component, false), ) .into()); } @@ -630,8 +630,8 @@ impl<'a> Toolchain<'a> { } } - fn get_component_suggestion(&self, component: &Component) -> String { - use levenshtein::levenshtein; + fn get_component_suggestion(&self, component: &Component, only_instaled: bool) -> String { + use strsim::damerau_levenshtein;; // Suggest only for very small differences // High number can result in innacurate suggestions for short queries e.g. `rls` const MAX_DISTANCE: usize = 3; @@ -640,10 +640,10 @@ impl<'a> Toolchain<'a> { if let Ok(components) = components { let min = components .iter() - .filter(|c| c.installed) + .filter(|c| !only_instaled || c.installed) .map(|c| { ( - levenshtein( + damerau_levenshtein( &c.component.name_in_manifest()[..], &component.name_in_manifest()[..], ), @@ -653,11 +653,11 @@ impl<'a> Toolchain<'a> { .min_by_key(|t| t.0) .expect("There should be always at least one component"); - // If suggestion is to different don't suggest anything + // If suggestion is too different don't suggest anything if min.0 > MAX_DISTANCE { return String::new(); } - // If compnent differs suggest only component + // If component parts are the same include whole description if min.1.component.short_name_in_manifest() == component.short_name_in_manifest() { return format!( " - Did you mean {} (targets differ)?", @@ -666,7 +666,7 @@ impl<'a> Toolchain<'a> { } format!( - " - Did you mean {}?", + " - Did you mean '{}'?", min.1.component.short_name_in_manifest() ) } else { @@ -719,7 +719,7 @@ impl<'a> Toolchain<'a> { return Err(ErrorKind::UnknownComponent( self.name.to_string(), component.description(&manifest), - self.get_component_suggestion(&component), + self.get_component_suggestion(&component, true), ) .into()); } diff --git a/tests/cli-v2.rs b/tests/cli-v2.rs index 0fe2a9cbda..09659132f2 100644 --- a/tests/cli-v2.rs +++ b/tests/cli-v2.rs @@ -920,3 +920,68 @@ fn update_unavailable_force() { ); }); } + +#[test] +fn add_component_suggest_best_match() { + setup(&|config| { + expect_ok(config, &["rustup", "default", "nightly"]); + expect_err( + config, + &["rustup", "component", "add", "rustd"], + "Did you mean 'rustc'?", + ); + }); +} + +#[test] +fn remove_component_suggest_best_match() { + setup(&|config| { + expect_ok(config, &["rustup", "default", "nightly"]); + expect_err( + config, + &["rustup", "component", "remove", "rustd"], + "Did you mean 'rustc'?", + ); + }); +} + +#[test] +fn add_target_suggest_best_match() { + setup(&|config| { + expect_ok(config, &["rustup", "default", "nightly"]); + expect_err( + config, + &[ + "rustup", + "target", + "add", + &format!("{}a", clitools::CROSS_ARCH1)[..], + ], + &format!( + "Did you mean 'rust-std' for target '{}' (targets differ)?", + clitools::CROSS_ARCH1 + )[..], + ); + }); +} + +#[test] +fn remove_target_suggest_best_match() { + setup(&|config| { + expect_ok(config, &["rustup", "default", "nightly"]); + expect_ok(config, &["rustup", "target", "add", clitools::CROSS_ARCH1]); + expect_err( + config, + &[ + "rustup", + "target", + "remove", + &format!("{}a", clitools::CROSS_ARCH1)[..], + ], + &format!( + "Did you mean 'rust-std' for target '{}' (targets differ)?", + clitools::CROSS_ARCH1 + )[..], + ); + }); +}