From 345edbbe109ceae3f69c67f4df69d5ae8c40a17c Mon Sep 17 00:00:00 2001 From: Darren Schroeder <343840+fdncred@users.noreply.github.com> Date: Wed, 28 Feb 2024 17:11:44 -0600 Subject: [PATCH] add `is-not-empty` command as a QOL improvement (#11991) # Description This PR adds `is-not-empty` as a counterpart to `is-empty`. It's the same code but negates the results. This command has been asked for many times. So, I thought it would be nice for our community to add it just as a quality-of-life improvement. This allows people to stop writing their `def is-not-empty [] { not ($in | is-empty) }` custom commands. I'm sure there will be some who disagree with adding this, I just think it's like we have `in` and `not-in` and helps fill out the language and makes it a little easier to use. # User-Facing Changes # Tests + Formatting # After Submitting --- crates/nu-command/src/default_context.rs | 3 +- crates/nu-command/src/filters/empty.rs | 119 ++++++------------ crates/nu-command/src/filters/is_empty.rs | 73 +++++++++++ crates/nu-command/src/filters/is_not_empty.rs | 74 +++++++++++ crates/nu-command/src/filters/mod.rs | 6 +- 5 files changed, 194 insertions(+), 81 deletions(-) create mode 100644 crates/nu-command/src/filters/is_empty.rs create mode 100644 crates/nu-command/src/filters/is_not_empty.rs diff --git a/crates/nu-command/src/default_context.rs b/crates/nu-command/src/default_context.rs index 15e02abeda..56e631dea6 100644 --- a/crates/nu-command/src/default_context.rs +++ b/crates/nu-command/src/default_context.rs @@ -41,7 +41,6 @@ pub fn add_shell_command_context(mut engine_state: EngineState) -> EngineState { DropColumn, DropNth, Each, - Empty, Enumerate, Every, Filter, @@ -53,6 +52,8 @@ pub fn add_shell_command_context(mut engine_state: EngineState) -> EngineState { GroupBy, Headers, Insert, + IsEmpty, + IsNotEmpty, Items, Join, SplitBy, diff --git a/crates/nu-command/src/filters/empty.rs b/crates/nu-command/src/filters/empty.rs index 8d110c958f..855f87d7cf 100644 --- a/crates/nu-command/src/filters/empty.rs +++ b/crates/nu-command/src/filters/empty.rs @@ -1,71 +1,14 @@ use nu_engine::CallExt; use nu_protocol::ast::{Call, CellPath}; -use nu_protocol::engine::{Command, EngineState, Stack}; -use nu_protocol::{ - Category, Example, IntoPipelineData, PipelineData, ShellError, Signature, SyntaxShape, Type, - Value, -}; +use nu_protocol::engine::{EngineState, Stack}; +use nu_protocol::{IntoPipelineData, PipelineData, ShellError, Value}; -#[derive(Clone)] -pub struct Empty; - -impl Command for Empty { - fn name(&self) -> &str { - "is-empty" - } - - fn signature(&self) -> Signature { - Signature::build("is-empty") - .input_output_types(vec![(Type::Any, Type::Bool)]) - .rest( - "rest", - SyntaxShape::CellPath, - "The names of the columns to check emptiness.", - ) - .category(Category::Filters) - } - - fn usage(&self) -> &str { - "Check for empty values." - } - - fn run( - &self, - engine_state: &EngineState, - stack: &mut Stack, - call: &Call, - input: PipelineData, - ) -> Result { - empty(engine_state, stack, call, input) - } - - fn examples(&self) -> Vec { - vec![ - Example { - description: "Check if a string is empty", - example: "'' | is-empty", - result: Some(Value::test_bool(true)), - }, - Example { - description: "Check if a list is empty", - example: "[] | is-empty", - result: Some(Value::test_bool(true)), - }, - Example { - // TODO: revisit empty cell path semantics for a record. - description: "Check if more than one column are empty", - example: "[[meal size]; [arepa small] [taco '']] | is-empty meal size", - result: Some(Value::test_bool(false)), - }, - ] - } -} - -fn empty( +pub fn empty( engine_state: &EngineState, stack: &mut Stack, call: &Call, input: PipelineData, + negate: bool, ) -> Result { let head = call.head; let columns: Vec = call.rest(engine_state, stack, 0)?; @@ -76,13 +19,23 @@ fn empty( let val = val.clone(); match val.follow_cell_path(&column.members, false) { Ok(Value::Nothing { .. }) => {} - Ok(_) => return Ok(Value::bool(false, head).into_pipeline_data()), + Ok(_) => { + if negate { + return Ok(Value::bool(true, head).into_pipeline_data()); + } else { + return Ok(Value::bool(false, head).into_pipeline_data()); + } + } Err(err) => return Err(err), } } } - Ok(Value::bool(true, head).into_pipeline_data()) + if negate { + Ok(Value::bool(false, head).into_pipeline_data()) + } else { + Ok(Value::bool(true, head).into_pipeline_data()) + } } else { match input { PipelineData::Empty => Ok(PipelineData::Empty), @@ -91,30 +44,38 @@ fn empty( let bytes = s.into_bytes(); match bytes { - Ok(s) => Ok(Value::bool(s.item.is_empty(), head).into_pipeline_data()), + Ok(s) => { + if negate { + Ok(Value::bool(!s.item.is_empty(), head).into_pipeline_data()) + } else { + Ok(Value::bool(s.item.is_empty(), head).into_pipeline_data()) + } + } Err(err) => Err(err), } } - None => Ok(Value::bool(true, head).into_pipeline_data()), + None => { + if negate { + Ok(Value::bool(false, head).into_pipeline_data()) + } else { + Ok(Value::bool(true, head).into_pipeline_data()) + } + } }, PipelineData::ListStream(s, ..) => { - Ok(Value::bool(s.count() == 0, head).into_pipeline_data()) + if negate { + Ok(Value::bool(s.count() != 0, head).into_pipeline_data()) + } else { + Ok(Value::bool(s.count() == 0, head).into_pipeline_data()) + } } PipelineData::Value(value, ..) => { - Ok(Value::bool(value.is_empty(), head).into_pipeline_data()) + if negate { + Ok(Value::bool(!value.is_empty(), head).into_pipeline_data()) + } else { + Ok(Value::bool(value.is_empty(), head).into_pipeline_data()) + } } } } } - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn test_examples() { - use crate::test_examples; - - test_examples(Empty {}) - } -} diff --git a/crates/nu-command/src/filters/is_empty.rs b/crates/nu-command/src/filters/is_empty.rs new file mode 100644 index 0000000000..7619332bd5 --- /dev/null +++ b/crates/nu-command/src/filters/is_empty.rs @@ -0,0 +1,73 @@ +use crate::filters::empty::empty; +use nu_protocol::ast::Call; +use nu_protocol::engine::{Command, EngineState, Stack}; +use nu_protocol::{ + Category, Example, PipelineData, ShellError, Signature, SyntaxShape, Type, Value, +}; + +#[derive(Clone)] +pub struct IsEmpty; + +impl Command for IsEmpty { + fn name(&self) -> &str { + "is-empty" + } + + fn signature(&self) -> Signature { + Signature::build("is-empty") + .input_output_types(vec![(Type::Any, Type::Bool)]) + .rest( + "rest", + SyntaxShape::CellPath, + "The names of the columns to check emptiness.", + ) + .category(Category::Filters) + } + + fn usage(&self) -> &str { + "Check for empty values." + } + + fn run( + &self, + engine_state: &EngineState, + stack: &mut Stack, + call: &Call, + input: PipelineData, + ) -> Result { + empty(engine_state, stack, call, input, false) + } + + fn examples(&self) -> Vec { + vec![ + Example { + description: "Check if a string is empty", + example: "'' | is-empty", + result: Some(Value::test_bool(true)), + }, + Example { + description: "Check if a list is empty", + example: "[] | is-empty", + result: Some(Value::test_bool(true)), + }, + Example { + // TODO: revisit empty cell path semantics for a record. + description: "Check if more than one column are empty", + example: "[[meal size]; [arepa small] [taco '']] | is-empty meal size", + result: Some(Value::test_bool(false)), + }, + ] + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_examples() { + use crate::test_examples; + + test_examples(IsEmpty {}) + } +} diff --git a/crates/nu-command/src/filters/is_not_empty.rs b/crates/nu-command/src/filters/is_not_empty.rs new file mode 100644 index 0000000000..e99344b8ca --- /dev/null +++ b/crates/nu-command/src/filters/is_not_empty.rs @@ -0,0 +1,74 @@ +use crate::filters::empty::empty; +use nu_protocol::ast::Call; +use nu_protocol::engine::{Command, EngineState, Stack}; +use nu_protocol::{ + Category, Example, PipelineData, ShellError, Signature, SyntaxShape, Type, Value, +}; + +#[derive(Clone)] +pub struct IsNotEmpty; + +impl Command for IsNotEmpty { + fn name(&self) -> &str { + "is-not-empty" + } + + fn signature(&self) -> Signature { + Signature::build("is-not-empty") + .input_output_types(vec![(Type::Any, Type::Bool)]) + .rest( + "rest", + SyntaxShape::CellPath, + "The names of the columns to check emptiness.", + ) + .category(Category::Filters) + } + + fn usage(&self) -> &str { + "Check for non-empty values." + } + + fn run( + &self, + engine_state: &EngineState, + stack: &mut Stack, + call: &Call, + input: PipelineData, + ) -> Result { + // Call the same `empty` function but negate the result + empty(engine_state, stack, call, input, true) + } + + fn examples(&self) -> Vec { + vec![ + Example { + description: "Check if a string is empty", + example: "'' | is-not-empty", + result: Some(Value::test_bool(false)), + }, + Example { + description: "Check if a list is empty", + example: "[] | is-not-empty", + result: Some(Value::test_bool(false)), + }, + Example { + // TODO: revisit empty cell path semantics for a record. + description: "Check if more than one column are empty", + example: "[[meal size]; [arepa small] [taco '']] | is-not-empty meal size", + result: Some(Value::test_bool(true)), + }, + ] + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_examples() { + use crate::test_examples; + + test_examples(IsNotEmpty {}) + } +} diff --git a/crates/nu-command/src/filters/mod.rs b/crates/nu-command/src/filters/mod.rs index 95e9ae3767..016c06a989 100644 --- a/crates/nu-command/src/filters/mod.rs +++ b/crates/nu-command/src/filters/mod.rs @@ -18,6 +18,8 @@ mod group; mod group_by; mod headers; mod insert; +mod is_empty; +mod is_not_empty; mod items; mod join; mod last; @@ -60,7 +62,7 @@ pub use compact::Compact; pub use default::Default; pub use drop::*; pub use each::Each; -pub use empty::Empty; +pub use empty::empty; pub use enumerate::Enumerate; pub use every::Every; pub use filter::Filter; @@ -72,6 +74,8 @@ pub use group::Group; pub use group_by::GroupBy; pub use headers::Headers; pub use insert::Insert; +pub use is_empty::IsEmpty; +pub use is_not_empty::IsNotEmpty; pub use items::Items; pub use join::Join; pub use last::Last;