diff --git a/crates/nu-command/src/commands/core_commands/find.rs b/crates/nu-command/src/commands/core_commands/find.rs new file mode 100644 index 0000000000..7b27838f5e --- /dev/null +++ b/crates/nu-command/src/commands/core_commands/find.rs @@ -0,0 +1,110 @@ +use crate::prelude::*; +use nu_engine::WholeStreamCommand; +use nu_errors::ShellError; +use nu_protocol::{Dictionary, Signature, SyntaxShape, UntaggedValue, Value}; + +pub struct Find; + +impl WholeStreamCommand for Find { + fn name(&self) -> &str { + "find" + } + + fn signature(&self) -> Signature { + Signature::build("find").rest("rest", SyntaxShape::String, "search term") + } + + fn usage(&self) -> &str { + "Find text in the output of a previous command" + } + + fn run(&self, args: CommandArgs) -> Result { + find(args) + } + + fn examples(&self) -> Vec { + vec![ + Example { + description: "Search pipeline output for multiple terms", + example: r#"ls | find toml md sh"#, + result: None, + }, + Example { + description: "Search strings for term(s)", + example: r#"echo Cargo.toml | find toml"#, + result: Some(vec![Value::from("Cargo.toml")]), + }, + Example { + description: "Search a number list for term(s)", + example: r#"[1 2 3 4 5] | find 5"#, + result: Some(vec![UntaggedValue::int(5).into()]), + }, + Example { + description: "Search string list for term(s)", + example: r#"[moe larry curly] | find l"#, + result: Some(vec![Value::from("larry"), Value::from("curly")]), + }, + ] + } +} + +fn row_contains(row: &Dictionary, search_terms: Vec) -> bool { + for term in search_terms { + for (k, v) in row.entries.iter() { + let key = k.to_string().trim().to_lowercase(); + let value = v.convert_to_string().trim().to_lowercase(); + if key.contains(&term) || value.contains(&term) { + return true; + } + } + } + + false +} + +fn find(args: CommandArgs) -> Result { + let rest: Vec = args.rest(0)?; + + Ok(args + .input + .filter(move |row| match &row.value { + UntaggedValue::Row(row) => { + let sterms: Vec = rest + .iter() + .map(|t| t.convert_to_string().trim().to_lowercase()) + .collect(); + row_contains(row, sterms) + } + UntaggedValue::Primitive(_p) => { + // eprint!("prim {}", p.type_name()); + let sterms: Vec = rest + .iter() + .map(|t| t.convert_to_string().trim().to_lowercase()) + .collect(); + + let prim_string = &row.convert_to_string().trim().to_lowercase(); + for term in sterms { + if prim_string.contains(&term) { + return true; + } + } + + false + } + _ => false, + }) + .into_output_stream()) +} + +#[cfg(test)] +mod tests { + use super::Find; + use super::ShellError; + + #[test] + fn examples_work_as_expected() -> Result<(), ShellError> { + use crate::examples::test as test_examples; + + test_examples(Find {}) + } +} diff --git a/crates/nu-command/src/commands/core_commands/mod.rs b/crates/nu-command/src/commands/core_commands/mod.rs index 0becc5231d..a366862749 100644 --- a/crates/nu-command/src/commands/core_commands/mod.rs +++ b/crates/nu-command/src/commands/core_commands/mod.rs @@ -4,6 +4,7 @@ mod def; mod describe; mod do_; pub(crate) mod echo; +mod find; mod help; mod history; mod if_; @@ -27,6 +28,7 @@ pub use def::Def; pub use describe::Describe; pub use do_::Do; pub use echo::Echo; +pub use find::Find; pub use help::Help; pub use history::History; pub use if_::If; diff --git a/crates/nu-command/src/default_context.rs b/crates/nu-command/src/default_context.rs index 46f2c5d723..b3c5053a85 100644 --- a/crates/nu-command/src/default_context.rs +++ b/crates/nu-command/src/default_context.rs @@ -21,6 +21,7 @@ pub fn create_default_context(interactive: bool) -> Result