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
<!-- List of all changes that impact the user experience here. This
helps us keep track of breaking changes. -->

# Tests + Formatting
<!--
Don't forget to add tests that cover your changes.

Make sure you've run and fixed any issues with these commands:

- `cargo fmt --all -- --check` to check standard code formatting (`cargo
fmt --all` applies these changes)
- `cargo clippy --workspace -- -D warnings -D clippy::unwrap_used` to
check that you're using the standard code style
- `cargo test --workspace` to check that all tests pass (on Windows make
sure to [enable developer
mode](https://learn.microsoft.com/en-us/windows/apps/get-started/developer-mode-features-and-debugging))
- `cargo run -- -c "use std testing; testing run-tests --path
crates/nu-std"` to run the tests for the standard library

> **Note**
> from `nushell` you can also use the `toolkit` as follows
> ```bash
> use toolkit.nu # or use an `env_change` hook to activate it
automatically
> toolkit check pr
> ```
-->

# After Submitting
<!-- If your PR had any user-facing changes, update [the
documentation](https://github.com/nushell/nushell.github.io) after the
PR is merged, if necessary. This will help us keep the docs up to date.
-->
This commit is contained in:
Darren Schroeder 2024-02-28 17:11:44 -06:00 committed by GitHub
parent e69a02d379
commit 345edbbe10
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 194 additions and 81 deletions

View File

@ -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,

View File

@ -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<PipelineData, ShellError> {
empty(engine_state, stack, call, input)
}
fn examples(&self) -> Vec<Example> {
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<PipelineData, ShellError> {
let head = call.head;
let columns: Vec<CellPath> = 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),
}
}
}
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, ..) => {
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, ..) => {
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 {})
}
}

View File

@ -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<PipelineData, ShellError> {
empty(engine_state, stack, call, input, false)
}
fn examples(&self) -> Vec<Example> {
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 {})
}
}

View File

@ -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<PipelineData, ShellError> {
// Call the same `empty` function but negate the result
empty(engine_state, stack, call, input, true)
}
fn examples(&self) -> Vec<Example> {
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 {})
}
}

View File

@ -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;