did_you_mean returns just the word matches (#2595)

This commit is contained in:
Chris Gillespie 2020-09-23 20:56:19 -07:00 committed by GitHub
parent 75f8247af1
commit bd9e598bf0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 65 additions and 15 deletions

View File

@ -132,7 +132,7 @@ pub fn get_column_path(path: &ColumnPath, obj: &Value) -> Result<Value, ShellErr
if let Some(suggestions) = did_you_mean(&obj_source, column_path_tried) { if let Some(suggestions) = did_you_mean(&obj_source, column_path_tried) {
ShellError::labeled_error( ShellError::labeled_error(
"Unknown column", "Unknown column",
format!("did you mean '{}'?", suggestions[0].1), format!("did you mean '{}'?", suggestions[0]),
column_path_tried.span.since(path_members_span), column_path_tried.span.since(path_members_span),
) )
} else { } else {
@ -156,7 +156,7 @@ pub fn get_column_path_from_table_error(
let suggestions: IndexSet<_> = rows let suggestions: IndexSet<_> = rows
.iter() .iter()
.filter_map(|r| did_you_mean(&r, &column_path_tried)) .filter_map(|r| did_you_mean(&r, &column_path_tried))
.map(|s| s[0].1.to_owned()) .map(|s| s[0].to_owned())
.collect(); .collect();
let mut existing_columns: IndexSet<_> = IndexSet::default(); let mut existing_columns: IndexSet<_> = IndexSet::default();
let mut names: Vec<String> = vec![]; let mut names: Vec<String> = vec![];
@ -239,7 +239,7 @@ pub fn get_column_from_row_error(
column_path_tried.span, column_path_tried.span,
format!( format!(
"Perhaps you meant '{}'? Columns available: {}", "Perhaps you meant '{}'? Columns available: {}",
suggestions[0].1, suggestions[0],
&obj_source.data_descriptors().join(", ") &obj_source.data_descriptors().join(", ")
), ),
column_path_tried.span.since(path_members_span), column_path_tried.span.since(path_members_span),

View File

@ -113,8 +113,8 @@ impl PathMember {
} }
} }
/// Prepares a list of "sounds like" matches for the string you're trying to find /// 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<Vec<(usize, String)>> { pub fn did_you_mean(obj_source: &Value, field_tried: &PathMember) -> Option<Vec<String>> {
let field_tried = match &field_tried.unspanned { let field_tried = match &field_tried.unspanned {
UnspannedPathMember::String(string) => string.clone(), UnspannedPathMember::String(string) => string.clone(),
UnspannedPathMember::Int(int) => format!("{}", int), UnspannedPathMember::Int(int) => format!("{}", int),
@ -124,18 +124,68 @@ pub fn did_you_mean(obj_source: &Value, field_tried: &PathMember) -> Option<Vec<
let mut possible_matches: Vec<_> = possibilities let mut possible_matches: Vec<_> = possibilities
.into_iter() .into_iter()
.map(|x| { .map(|word| {
let word = x; let edit_distance = natural::distance::levenshtein_distance(&word, &field_tried);
let distance = natural::distance::levenshtein_distance(&word, &field_tried); (edit_distance, word)
(distance, word)
}) })
.collect(); .collect();
if !possible_matches.is_empty() { if !possible_matches.is_empty() {
possible_matches.sort(); possible_matches.sort();
Some(possible_matches) let words_matched: Vec<String> = possible_matches.into_iter().map(|m| m.1).collect();
Some(words_matched)
} else { } else {
None 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))
}
}

View File

@ -239,7 +239,7 @@ where
let suggestions: IndexSet<_> = rows let suggestions: IndexSet<_> = rows
.iter() .iter()
.filter_map(|r| nu_protocol::did_you_mean(&r, &column_path_tried)) .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(); .collect();
let mut existing_columns: IndexSet<_> = IndexSet::default(); let mut existing_columns: IndexSet<_> = IndexSet::default();
let mut names: Vec<String> = vec![]; let mut names: Vec<String> = vec![];
@ -316,7 +316,7 @@ where
column_path_tried.span, column_path_tried.span,
format!( format!(
"Perhaps you meant '{}'? Columns available: {}", "Perhaps you meant '{}'? Columns available: {}",
suggestions[0].1, suggestions[0],
&obj_source.data_descriptors().join(",") &obj_source.data_descriptors().join(",")
), ),
column_path_tried.span.since(path_members_span), 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) { if let Some(suggestions) = nu_protocol::did_you_mean(&obj_source, &column_path_tried) {
return ShellError::labeled_error( return ShellError::labeled_error(
"Unknown column", "Unknown column",
format!("did you mean '{}'?", suggestions[0].1), format!("did you mean '{}'?", suggestions[0]),
column_path_tried.span.since(path_members_span), column_path_tried.span.since(path_members_span),
); );
} }

View File

@ -109,7 +109,7 @@ impl Inc {
) { ) {
Some(suggestions) => ShellError::labeled_error( Some(suggestions) => ShellError::labeled_error(
"Unknown column", "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)), span_for_spanned_list(fields.iter().map(|p| p.span)),
), ),
None => ShellError::labeled_error( None => ShellError::labeled_error(