From bd9e598bf01cdd4562d1db64dd47b142f404a306 Mon Sep 17 00:00:00 2001 From: Chris Gillespie <6572184+gillespiecd@users.noreply.github.com> Date: Wed, 23 Sep 2020 20:56:19 -0700 Subject: [PATCH] did_you_mean returns just the word matches (#2595) --- crates/nu-cli/src/commands/get.rs | 6 +- crates/nu-protocol/src/value/column_path.rs | 66 ++++++++++++++++++--- crates/nu-value-ext/src/lib.rs | 6 +- crates/nu_plugin_inc/src/inc.rs | 2 +- 4 files changed, 65 insertions(+), 15 deletions(-) diff --git a/crates/nu-cli/src/commands/get.rs b/crates/nu-cli/src/commands/get.rs index 5840a84b8d..bd9c72e29b 100644 --- a/crates/nu-cli/src/commands/get.rs +++ b/crates/nu-cli/src/commands/get.rs @@ -132,7 +132,7 @@ pub fn get_column_path(path: &ColumnPath, obj: &Value) -> Result = rows .iter() .filter_map(|r| did_you_mean(&r, &column_path_tried)) - .map(|s| s[0].1.to_owned()) + .map(|s| s[0].to_owned()) .collect(); let mut existing_columns: IndexSet<_> = IndexSet::default(); let mut names: Vec = vec![]; @@ -239,7 +239,7 @@ pub fn get_column_from_row_error( column_path_tried.span, format!( "Perhaps you meant '{}'? Columns available: {}", - suggestions[0].1, + suggestions[0], &obj_source.data_descriptors().join(", ") ), column_path_tried.span.since(path_members_span), diff --git a/crates/nu-protocol/src/value/column_path.rs b/crates/nu-protocol/src/value/column_path.rs index 0c9e5a0c2c..ca19ea3705 100644 --- a/crates/nu-protocol/src/value/column_path.rs +++ b/crates/nu-protocol/src/value/column_path.rs @@ -113,8 +113,8 @@ impl PathMember { } } -/// Prepares a list of "sounds like" matches for the string you're trying to find -pub fn did_you_mean(obj_source: &Value, field_tried: &PathMember) -> Option> { +/// Prepares a list of "sounds like" matches (using edit distance) for the string you're trying to find +pub fn did_you_mean(obj_source: &Value, field_tried: &PathMember) -> Option> { let field_tried = match &field_tried.unspanned { UnspannedPathMember::String(string) => string.clone(), UnspannedPathMember::Int(int) => format!("{}", int), @@ -124,18 +124,68 @@ pub fn did_you_mean(obj_source: &Value, field_tried: &PathMember) -> Option = possibilities .into_iter() - .map(|x| { - let word = x; - let distance = natural::distance::levenshtein_distance(&word, &field_tried); - - (distance, word) + .map(|word| { + let edit_distance = natural::distance::levenshtein_distance(&word, &field_tried); + (edit_distance, word) }) .collect(); if !possible_matches.is_empty() { possible_matches.sort(); - Some(possible_matches) + let words_matched: Vec = possible_matches.into_iter().map(|m| m.1).collect(); + Some(words_matched) } else { None } } + +#[cfg(test)] +mod test { + use super::*; + use crate::UntaggedValue; + use indexmap::indexmap; + use nu_source::Tag; + + #[test] + fn did_you_mean_returns_possible_column_matches() { + let value = UntaggedValue::row(indexmap! { + "dog".to_string() => UntaggedValue::int(1).into(), + "cat".to_string() => UntaggedValue::int(1).into(), + "alt".to_string() => UntaggedValue::int(1).into(), + }); + + let source = Value { + tag: Tag::unknown(), + value, + }; + + let guess = PathMember { + unspanned: UnspannedPathMember::String("hat".to_string()), + span: Default::default(), + }; + + assert_eq!( + Some(vec![ + "cat".to_string(), + "alt".to_string(), + "dog".to_string() + ]), + did_you_mean(&source, &guess) + ) + } + + #[test] + fn did_you_mean_returns_no_matches_when_empty() { + let empty_source = Value { + tag: Tag::unknown(), + value: UntaggedValue::row(indexmap! {}), + }; + + let guess = PathMember { + unspanned: UnspannedPathMember::String("hat".to_string()), + span: Default::default(), + }; + + assert_eq!(None, did_you_mean(&empty_source, &guess)) + } +} diff --git a/crates/nu-value-ext/src/lib.rs b/crates/nu-value-ext/src/lib.rs index 6eaf8084a1..716ffef5fe 100644 --- a/crates/nu-value-ext/src/lib.rs +++ b/crates/nu-value-ext/src/lib.rs @@ -239,7 +239,7 @@ where let suggestions: IndexSet<_> = rows .iter() .filter_map(|r| nu_protocol::did_you_mean(&r, &column_path_tried)) - .map(|s| s[0].1.to_owned()) + .map(|s| s[0].to_owned()) .collect(); let mut existing_columns: IndexSet<_> = IndexSet::default(); let mut names: Vec = vec![]; @@ -316,7 +316,7 @@ where column_path_tried.span, format!( "Perhaps you meant '{}'? Columns available: {}", - suggestions[0].1, + suggestions[0], &obj_source.data_descriptors().join(",") ), column_path_tried.span.since(path_members_span), @@ -345,7 +345,7 @@ where if let Some(suggestions) = nu_protocol::did_you_mean(&obj_source, &column_path_tried) { return ShellError::labeled_error( "Unknown column", - format!("did you mean '{}'?", suggestions[0].1), + format!("did you mean '{}'?", suggestions[0]), column_path_tried.span.since(path_members_span), ); } diff --git a/crates/nu_plugin_inc/src/inc.rs b/crates/nu_plugin_inc/src/inc.rs index 4d09675223..4a944c6a4c 100644 --- a/crates/nu_plugin_inc/src/inc.rs +++ b/crates/nu_plugin_inc/src/inc.rs @@ -109,7 +109,7 @@ impl Inc { ) { Some(suggestions) => ShellError::labeled_error( "Unknown column", - format!("did you mean '{}'?", suggestions[0].1), + format!("did you mean '{}'?", suggestions[0]), span_for_spanned_list(fields.iter().map(|p| p.span)), ), None => ShellError::labeled_error(