mirror of
https://github.com/nushell/nushell.git
synced 2025-06-20 09:58:15 +02:00
Search nested structures recursively in find
command (#15850)
# Description Instead of converting nested structures into strings and pattern-matching the strings, the `find` command will recursively search the nested structures for matches. - fixes #15618 # User-Facing Changes Text in nested structures will now be highlighted as well. Error values will always passed on instead of testing them against the search term There will be slight changes in match behavior, such as characters that are part of the string representations of data structures no longer matching all nested data structures.
This commit is contained in:
parent
55240d98a5
commit
3db9c81958
@ -163,7 +163,12 @@ impl Command for Find {
|
|||||||
example: r#"[["Larry", "Moe"], ["Victor", "Marina"]] | find --regex "rr""#,
|
example: r#"[["Larry", "Moe"], ["Victor", "Marina"]] | find --regex "rr""#,
|
||||||
result: Some(Value::list(
|
result: Some(Value::list(
|
||||||
vec![Value::list(
|
vec![Value::list(
|
||||||
vec![Value::test_string("Larry"), Value::test_string("Moe")],
|
vec![
|
||||||
|
Value::test_string(
|
||||||
|
"\u{1b}[37mLa\u{1b}[0m\u{1b}[41;37mrr\u{1b}[0m\u{1b}[37my\u{1b}[0m",
|
||||||
|
),
|
||||||
|
Value::test_string("Moe"),
|
||||||
|
],
|
||||||
Span::test_data(),
|
Span::test_data(),
|
||||||
)],
|
)],
|
||||||
Span::test_data(),
|
Span::test_data(),
|
||||||
@ -344,7 +349,10 @@ fn get_match_pattern_from_arguments(
|
|||||||
// map functions
|
// map functions
|
||||||
|
|
||||||
fn highlight_matches_in_string(pattern: &MatchPattern, val: String) -> String {
|
fn highlight_matches_in_string(pattern: &MatchPattern, val: String) -> String {
|
||||||
// strip haystack to remove existing ansi style
|
if !pattern.regex.is_match(&val).unwrap_or(false) {
|
||||||
|
return val;
|
||||||
|
}
|
||||||
|
|
||||||
let stripped_val = nu_utils::strip_ansi_string_unlikely(val);
|
let stripped_val = nu_utils::strip_ansi_string_unlikely(val);
|
||||||
let mut last_match_end = 0;
|
let mut last_match_end = 0;
|
||||||
let mut highlighted = String::new();
|
let mut highlighted = String::new();
|
||||||
@ -390,7 +398,7 @@ fn highlight_matches_in_string(pattern: &MatchPattern, val: String) -> String {
|
|||||||
highlighted
|
highlighted
|
||||||
}
|
}
|
||||||
|
|
||||||
fn highlight_matches_in_record_or_value(
|
fn highlight_matches_in_value(
|
||||||
pattern: &MatchPattern,
|
pattern: &MatchPattern,
|
||||||
value: Value,
|
value: Value,
|
||||||
columns_to_search: &[String],
|
columns_to_search: &[String],
|
||||||
@ -412,16 +420,16 @@ fn highlight_matches_in_record_or_value(
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Value::String { val: val_str, .. } = val {
|
*val = highlight_matches_in_value(pattern, std::mem::take(val), &[]);
|
||||||
if pattern.regex.is_match(val_str).unwrap_or(false) {
|
|
||||||
let val_str = std::mem::take(val_str);
|
|
||||||
*val = highlight_matches_in_string(pattern, val_str).into_value(span)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Value::record(record, span)
|
Value::record(record, span)
|
||||||
}
|
}
|
||||||
|
Value::List { vals, .. } => vals
|
||||||
|
.into_iter()
|
||||||
|
.map(|item| highlight_matches_in_value(pattern, item, &[]))
|
||||||
|
.collect::<Vec<Value>>()
|
||||||
|
.into_value(span),
|
||||||
Value::String { val, .. } => highlight_matches_in_string(pattern, val).into_value(span),
|
Value::String { val, .. } => highlight_matches_in_string(pattern, val).into_value(span),
|
||||||
_ => value,
|
_ => value,
|
||||||
}
|
}
|
||||||
@ -444,24 +452,22 @@ fn find_in_pipelinedata(
|
|||||||
PipelineData::Value(_, _) => input
|
PipelineData::Value(_, _) => input
|
||||||
.filter(
|
.filter(
|
||||||
move |value| {
|
move |value| {
|
||||||
record_or_value_should_be_printed(&pattern, value, &columns_to_search, &config)
|
value_should_be_printed(&pattern, value, &columns_to_search, &config)
|
||||||
|
!= pattern.invert
|
||||||
},
|
},
|
||||||
engine_state.signals(),
|
engine_state.signals(),
|
||||||
)?
|
)?
|
||||||
.map(
|
.map(
|
||||||
move |x| {
|
move |x| highlight_matches_in_value(&map_pattern, x, &map_columns_to_search),
|
||||||
highlight_matches_in_record_or_value(&map_pattern, x, &map_columns_to_search)
|
|
||||||
},
|
|
||||||
engine_state.signals(),
|
engine_state.signals(),
|
||||||
),
|
),
|
||||||
PipelineData::ListStream(stream, metadata) => {
|
PipelineData::ListStream(stream, metadata) => {
|
||||||
let stream = stream.modify(|iter| {
|
let stream = stream.modify(|iter| {
|
||||||
iter.filter(move |value| {
|
iter.filter(move |value| {
|
||||||
record_or_value_should_be_printed(&pattern, value, &columns_to_search, &config)
|
value_should_be_printed(&pattern, value, &columns_to_search, &config)
|
||||||
})
|
!= pattern.invert
|
||||||
.map(move |x| {
|
|
||||||
highlight_matches_in_record_or_value(&map_pattern, x, &map_columns_to_search)
|
|
||||||
})
|
})
|
||||||
|
.map(move |x| highlight_matches_in_value(&map_pattern, x, &map_columns_to_search))
|
||||||
});
|
});
|
||||||
|
|
||||||
Ok(PipelineData::ListStream(stream, metadata))
|
Ok(PipelineData::ListStream(stream, metadata))
|
||||||
@ -495,7 +501,12 @@ fn string_should_be_printed(pattern: &MatchPattern, value: &str) -> bool {
|
|||||||
pattern.regex.is_match(value).unwrap_or(false)
|
pattern.regex.is_match(value).unwrap_or(false)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn value_should_be_printed(pattern: &MatchPattern, value: &Value, config: &Config) -> bool {
|
fn value_should_be_printed(
|
||||||
|
pattern: &MatchPattern,
|
||||||
|
value: &Value,
|
||||||
|
columns_to_search: &[String],
|
||||||
|
config: &Config,
|
||||||
|
) -> bool {
|
||||||
let lower_value = value.to_expanded_string("", config).to_lowercase();
|
let lower_value = value.to_expanded_string("", config).to_lowercase();
|
||||||
|
|
||||||
match value {
|
match value {
|
||||||
@ -507,8 +518,7 @@ fn value_should_be_printed(pattern: &MatchPattern, value: &Value, config: &Confi
|
|||||||
| Value::Range { .. }
|
| Value::Range { .. }
|
||||||
| Value::Float { .. }
|
| Value::Float { .. }
|
||||||
| Value::Closure { .. }
|
| Value::Closure { .. }
|
||||||
| Value::Nothing { .. }
|
| Value::Nothing { .. } => {
|
||||||
| Value::Error { .. } => {
|
|
||||||
if !pattern.lower_terms.is_empty() {
|
if !pattern.lower_terms.is_empty() {
|
||||||
// look for exact match when searching with terms
|
// look for exact match when searching with terms
|
||||||
pattern
|
pattern
|
||||||
@ -519,37 +529,25 @@ fn value_should_be_printed(pattern: &MatchPattern, value: &Value, config: &Confi
|
|||||||
string_should_be_printed(pattern, &lower_value)
|
string_should_be_printed(pattern, &lower_value)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Value::Glob { .. }
|
Value::Glob { .. } | Value::CellPath { .. } | Value::Custom { .. } => {
|
||||||
| Value::List { .. }
|
string_should_be_printed(pattern, &lower_value)
|
||||||
| Value::CellPath { .. }
|
}
|
||||||
| Value::Record { .. }
|
|
||||||
| Value::Custom { .. } => string_should_be_printed(pattern, &lower_value),
|
|
||||||
Value::String { val, .. } => string_should_be_printed(pattern, val),
|
Value::String { val, .. } => string_should_be_printed(pattern, val),
|
||||||
Value::Binary { .. } => false,
|
Value::List { vals, .. } => vals
|
||||||
}
|
.iter()
|
||||||
}
|
.any(|item| value_should_be_printed(pattern, item, &[], config)),
|
||||||
|
|
||||||
fn record_or_value_should_be_printed(
|
|
||||||
pattern: &MatchPattern,
|
|
||||||
value: &Value,
|
|
||||||
columns_to_search: &[String],
|
|
||||||
config: &Config,
|
|
||||||
) -> bool {
|
|
||||||
let match_found = match value {
|
|
||||||
Value::Record { val: record, .. } => {
|
Value::Record { val: record, .. } => {
|
||||||
// Only perform column selection if given columns.
|
|
||||||
let col_select = !columns_to_search.is_empty();
|
let col_select = !columns_to_search.is_empty();
|
||||||
record.iter().any(|(col, val)| {
|
record.iter().any(|(col, val)| {
|
||||||
if col_select && !columns_to_search.contains(col) {
|
if col_select && !columns_to_search.contains(col) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
value_should_be_printed(pattern, val, config)
|
value_should_be_printed(pattern, val, &[], config)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
_ => value_should_be_printed(pattern, value, config),
|
Value::Binary { .. } => false,
|
||||||
};
|
Value::Error { .. } => true,
|
||||||
|
}
|
||||||
match_found != pattern.invert
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// utility
|
// utility
|
||||||
|
@ -293,3 +293,20 @@ fn find_with_string_search_with_special_char_6() {
|
|||||||
);
|
);
|
||||||
assert_eq!(actual_no_highlight.out, "[{\"d\":\"a[]b\"}]");
|
assert_eq!(actual_no_highlight.out, "[{\"d\":\"a[]b\"}]");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn find_in_nested_list_dont_match_bracket() {
|
||||||
|
let actual = nu!(r#"[ [foo bar] [foo baz] ] | find "[" | to json -r"#);
|
||||||
|
|
||||||
|
assert_eq!(actual.out, "[]");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn find_and_highlight_in_nested_list() {
|
||||||
|
let actual = nu!(r#"[ [foo bar] [foo baz] ] | find "foo" | to json -r"#);
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
actual.out,
|
||||||
|
r#"[["\u001b[37m\u001b[0m\u001b[41;37mfoo\u001b[0m\u001b[37m\u001b[0m","bar"],["\u001b[37m\u001b[0m\u001b[41;37mfoo\u001b[0m\u001b[37m\u001b[0m","baz"]]"#
|
||||||
|
);
|
||||||
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user