From 9fe1259bdd413ed04eaf03cc7498d76ea83f52ea Mon Sep 17 00:00:00 2001 From: Ken Matsui <26405363+ken-matsui@users.noreply.github.com> Date: Wed, 2 Feb 2022 01:51:47 +0900 Subject: [PATCH] Support providing distance --- Cargo.lock | 4 +- Cargo.toml | 6 +-- README.md | 19 ++++++--- src/main.rs | 10 +++-- suggestion/Cargo.toml | 6 +-- suggestion/README.md | 34 ++++++++++++++++- suggestion/src/lib.rs | 89 ++++++++++++++++++++++++++++++++----------- 7 files changed, 126 insertions(+), 42 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 0be5d77..08d9d20 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -169,14 +169,14 @@ checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" [[package]] name = "suggestion" -version = "0.2.0" +version = "0.3.0" dependencies = [ "lev_distance", ] [[package]] name = "suggestion-cli" -version = "0.2.0" +version = "0.3.0" dependencies = [ "clap", "suggestion", diff --git a/Cargo.toml b/Cargo.toml index 9f2b2ef..b0a6b9c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "suggestion-cli" -version = "0.2.0" +version = "0.3.0" edition = "2021" authors = ["Ken Matsui <26405363+ken-matsui@users.noreply.github.com>"] description = "A CLI tool for similar name suggestions to provide helps like \"Did you mean?\"" @@ -8,7 +8,7 @@ license = "MIT" readme = "README.md" repository = "https://github.com/ken-matsui/suggestion/" homepage = "https://github.com/ken-matsui/suggestion#readme" -documentation = "https://docs.rs/suggest" +documentation = "https://docs.rs/suggestion-cli" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html @@ -17,7 +17,7 @@ members = ["suggestion"] [dependencies] clap = { version = "3.0.13", features = ["derive"] } -suggestion = { path = "suggestion", version = "0.2.0" } +suggestion = { path = "suggestion", version = "0.3.0" } [[bin]] name = "suggest" diff --git a/README.md b/README.md index 545f4b5..f1fca86 100644 --- a/README.md +++ b/README.md @@ -14,19 +14,20 @@ cargo install suggestion-cli ```bash $ suggest --help -suggestion-cli 0.1.0 +suggestion-cli 0.3.0 A CLI tool for similar name suggestions to provide helps like "Did you mean?" USAGE: - suggest [POSSIBLE_VALUES]... + suggest [OPTIONS] [VALUES]... ARGS: - Input to check if similar name exists - ... Values of similar names + Input to check if similar name exists + ... Values of similar names OPTIONS: - -h, --help Print help information - -V, --version Print version information + -d, --distance Levenshtein Distance + -h, --help Print help information + -V, --version Print version information ``` ## Examples @@ -40,6 +41,12 @@ No similar name for the `hoge` input was found. $ suggest install update install The same value with the `install` input exists. + +$ suggest paoc poac poacpp +No similar name for the `paoc` input was found. + +$ suggest paoc poac poacpp --distance 2 +The `paoc` input is similar to `poac`. ``` ## Contribution diff --git a/src/main.rs b/src/main.rs index c4571c5..0f4441f 100644 --- a/src/main.rs +++ b/src/main.rs @@ -8,16 +8,20 @@ struct Args { input: String, /// Values of similar names - possible_values: Vec, + values: Vec, + + /// Levenshtein Distance + #[clap(short, long)] + distance: Option, } fn main() { let args = Args::parse(); - let exit_code = if args.possible_values.contains(&args.input) { + let exit_code = if args.values.contains(&args.input) { eprintln!("The same value with the `{}` input exists.", args.input); 1 - } else if let Some(sugg) = args.possible_values.suggest(&args.input) { + } else if let Some(sugg) = args.values.suggest_with_dist(&args.input, args.distance) { println!("The `{}` input is similar to `{}`.", args.input, sugg); 0 } else { diff --git a/suggestion/Cargo.toml b/suggestion/Cargo.toml index 7724b3d..c92a318 100644 --- a/suggestion/Cargo.toml +++ b/suggestion/Cargo.toml @@ -1,13 +1,13 @@ [package] name = "suggestion" -version = "0.2.0" +version = "0.3.0" edition = "2021" authors = ["Ken Matsui <26405363+ken-matsui@users.noreply.github.com>"] description = "A minimal library for similar name suggestions to provide helps like \"Did you mean?\"" license = "MIT" readme = "README.md" -repository = "https://github.com/ken-matsui/suggestion/suggestion/" -homepage = "https://github.com/ken-matsui/suggestion/suggestion#readme" +repository = "https://github.com/ken-matsui/suggestion/tree/main/suggestion/" +homepage = "https://github.com/ken-matsui/suggestion/tree/main/suggestion#readme" documentation = "https://docs.rs/suggestion" [dependencies] diff --git a/suggestion/README.md b/suggestion/README.md index c977d2f..7854ac6 100644 --- a/suggestion/README.md +++ b/suggestion/README.md @@ -3,7 +3,9 @@ A minimal library for similar name suggestions to provide helps like "Did you mean?" This library provides suggestion traits for all collection types in the standard library. -## Example +## Examples + +### Simple case This example can be executed by the `cargo run --example simple` command. @@ -31,9 +33,35 @@ No command named `instakk` found. Did you mean `install`? ``` +### Specifying distance + +```rust +use suggestion::Suggest; + +fn main() { + let input = "paoc"; + + let list_commands = vec!["poac", "poacpp"]; + if list_commands.contains(&input) { + return; + } + + if let Some(sugg) = list_commands.suggest_with_dist(input, Some(2)) { + println!("No command named `{}` found.", input); + println!("Did you mean `{}`?", sugg); + } +} +``` + +```shell +$ cargo run +No command named `paoc` found. +Did you mean `poac`? +``` + ## Supported types -Please let me know by issues or pull requests if there is anything left out. +Please let me know if anything is left out through issues or pull requests. ### Sequences @@ -46,6 +74,8 @@ Please let me know by issues or pull requests if there is anything left out. * `HashMap` * `BTreeMap` +To suggest keys, use `suggestion::SuggestKey` trait. + ### Sets * `BTreeSet` diff --git a/suggestion/src/lib.rs b/suggestion/src/lib.rs index 2ac0864..7e38866 100644 --- a/suggestion/src/lib.rs +++ b/suggestion/src/lib.rs @@ -44,10 +44,16 @@ use std::collections::BinaryHeap; pub trait Suggest { /// Find similar name in values for all collections fn suggest(&self, query: &str) -> Option; + + /// Find similar name with dist in values for all collections + fn suggest_with_dist(&self, query: &str, dist: Option) -> Option; } pub trait SuggestKey { /// Find similar name in keys for Map collections fn suggest_key(&self, query: &str) -> Option; + + /// Find similar name with dist in keys for Map collections + fn suggest_key_with_dist(&self, query: &str, dist: Option) -> Option; } macro_rules! impl_suggest { @@ -56,6 +62,9 @@ macro_rules! impl_suggest { fn suggest(&self, query: &str) -> Option { find_best_match_for_name(self.iter(), query, None) } + fn suggest_with_dist(&self, query: &str, dist: Option) -> Option { + find_best_match_for_name(self.iter(), query, dist) + } } }; } @@ -65,6 +74,9 @@ macro_rules! impl_suggest_key { fn suggest_key(&self, query: &str) -> Option { find_best_match_for_name(self.keys(), query, None) } + fn suggest_key_with_dist(&self, query: &str, dist: Option) -> Option { + find_best_match_for_name(self.keys(), query, dist) + } } }; } @@ -74,6 +86,9 @@ macro_rules! impl_suggest_value { fn suggest(&self, query: &str) -> Option { find_best_match_for_name(self.values(), query, None) } + fn suggest_with_dist(&self, query: &str, dist: Option) -> Option { + find_best_match_for_name(self.values(), query, dist) + } } }; } @@ -83,6 +98,9 @@ impl, const N: usize> Suggest for [T; N] { fn suggest(&self, query: &str) -> Option { find_best_match_for_name(self.iter(), query, None) } + fn suggest_with_dist(&self, query: &str, dist: Option) -> Option { + find_best_match_for_name(self.iter(), query, dist) + } } // Slices @@ -90,6 +108,9 @@ impl> Suggest for [T] { fn suggest(&self, query: &str) -> Option { find_best_match_for_name(self.iter(), query, None) } + fn suggest_with_dist(&self, query: &str, dist: Option) -> Option { + find_best_match_for_name(self.iter(), query, dist) + } } // Sequences @@ -114,12 +135,39 @@ impl_suggest!(BinaryHeap); mod tests { use super::*; + macro_rules! test_suggest_primitive { + ($t:ty, $f:ident) => { + #[test] + fn $f() { + let tmp = ["aaab", "aaabc"]; + let input: $t = &tmp; + assert_eq!(input.suggest("aaaa"), Some("aaab".to_string())); + + let tmp = ["poac", "poacpp"]; + let input: $t = &tmp; + assert_eq!(input.suggest("paoc"), None); + assert_eq!(input.suggest_with_dist("paoc", Some(1)), None); + assert_eq!( + input.suggest_with_dist("paoc", Some(2)), + Some("poac".to_string()) + ); + } + }; + } macro_rules! test_suggest { ($t:ident, $f:ident) => { #[test] fn $f() { let input: $t<_> = vec!["aaab", "aaabc"].into_iter().collect(); assert_eq!(input.suggest("aaaa"), Some("aaab".to_string())); + + let input: $t<_> = vec!["poac", "poacpp"].into_iter().collect(); + assert_eq!(input.suggest("paoc"), None); + assert_eq!(input.suggest_with_dist("paoc", Some(1)), None); + assert_eq!( + input.suggest_with_dist("paoc", Some(2)), + Some("poac".to_string()) + ); } }; } @@ -134,35 +182,30 @@ mod tests { let input = $t::<_, _>::from_iter(IntoIter::new([(2, "aaab"), (4, "aaabc")])); assert_eq!(input.suggest("aaaa"), Some("aaab".to_string())); + + let input = $t::<_, _>::from_iter(IntoIter::new([("poac", 2), ("poacpp", 4)])); + assert_eq!(input.suggest_key("paoc"), None); + assert_eq!(input.suggest_key_with_dist("paoc", Some(1)), None); + assert_eq!( + input.suggest_key_with_dist("paoc", Some(2)), + Some("poac".to_string()) + ); + + let input = $t::<_, _>::from_iter(IntoIter::new([(2, "poac"), (4, "poacpp")])); + assert_eq!(input.suggest("paoc"), None); + assert_eq!(input.suggest_with_dist("paoc", Some(1)), None); + assert_eq!( + input.suggest_with_dist("paoc", Some(2)), + Some("poac".to_string()) + ); } }; } // Primitive Array Type - #[test] - fn test_array() { - let input = ["aaab", "aaabc"]; - assert_eq!(input.suggest("aaaa"), Some("aaab".to_string())); - - let ref input = ["aaab", "aaabc"]; - assert_eq!(input.suggest("aaaa"), Some("aaab".to_string())); - - let ref mut input = ["aaab", "aaabc"]; - assert_eq!(input.suggest("aaaa"), Some("aaab".to_string())); - } - + test_suggest_primitive!(&[&str; 2], test_array); // Slices - #[test] - fn test_slices() { - let input = ["", "aaab", "aaabc"]; - assert_eq!(input[1..].suggest("aaaa"), Some("aaab".to_string())); - - let ref input = ["", "aaab", "aaabc"]; - assert_eq!(input[1..].suggest("aaaa"), Some("aaab".to_string())); - - let ref mut input = ["", "aaab", "aaabc"]; - assert_eq!(input[1..].suggest("aaaa"), Some("aaab".to_string())); - } + test_suggest_primitive!(&[&str], test_slices); // Sequences test_suggest!(LinkedList, test_suggest_linked_list);