From e278ca61d12155f26bb5c09d8e5f14908b9643a9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9s=20N=2E=20Robalino?= Date: Sat, 3 Apr 2021 13:40:54 -0500 Subject: [PATCH] commands: any? all? (#3252) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * commands: any? all? We can check if `any` (or `all`) rows of tables match predicates. Small `all?` example: Given the following table with `services` running: ``` > echo [[status]; [UP] [UP]] ───┬──────── # │ status ───┼──────── 0 │ UP 1 │ UP ───┴──────── ``` We can ask if all services are UP, like so: ``` > echo [[status]; [UP] [UP]] | all? status == UP true ``` * Fix any? signature. --- crates/nu-command/src/commands.rs | 6 +- crates/nu-command/src/commands/all.rs | 138 ++++++++++++++++++ crates/nu-command/src/commands/any.rs | 138 ++++++++++++++++++ .../src/commands/default_context.rs | 2 + crates/nu-command/src/commands/where_.rs | 12 +- crates/nu-command/tests/commands/all.rs | 41 ++++++ crates/nu-command/tests/commands/any.rs | 41 ++++++ crates/nu-command/tests/commands/mod.rs | 2 + 8 files changed, 373 insertions(+), 7 deletions(-) create mode 100644 crates/nu-command/src/commands/all.rs create mode 100644 crates/nu-command/src/commands/any.rs create mode 100644 crates/nu-command/tests/commands/all.rs create mode 100644 crates/nu-command/tests/commands/any.rs diff --git a/crates/nu-command/src/commands.rs b/crates/nu-command/src/commands.rs index bce150c552..adf6785561 100644 --- a/crates/nu-command/src/commands.rs +++ b/crates/nu-command/src/commands.rs @@ -4,7 +4,9 @@ pub(crate) mod macros; mod from_delimited_data; mod to_delimited_data; +pub(crate) mod all; pub(crate) mod ansi; +pub(crate) mod any; pub(crate) mod append; pub(crate) mod args; pub mod autoenv; @@ -176,6 +178,8 @@ pub(crate) use kill::Kill; pub(crate) mod clear; pub(crate) use clear::Clear; pub(crate) mod touch; +pub(crate) use all::Command as All; +pub(crate) use any::Command as Any; pub(crate) use enter::Enter; pub(crate) use every::Every; pub(crate) use exec::Exec; @@ -286,7 +290,7 @@ pub(crate) use touch::Touch; pub(crate) use uniq::Uniq; pub(crate) use url_::{UrlCommand, UrlHost, UrlPath, UrlQuery, UrlScheme}; pub(crate) use version::Version; -pub(crate) use where_::Where; +pub(crate) use where_::Command as Where; pub(crate) use which_::Which; pub(crate) use with_env::WithEnv; pub(crate) use wrap::Wrap; diff --git a/crates/nu-command/src/commands/all.rs b/crates/nu-command/src/commands/all.rs new file mode 100644 index 0000000000..da17626f33 --- /dev/null +++ b/crates/nu-command/src/commands/all.rs @@ -0,0 +1,138 @@ +use crate::prelude::*; +use nu_engine::evaluate_baseline_expr; +use nu_engine::WholeStreamCommand; +use nu_errors::ShellError; +use nu_protocol::{ + hir::CapturedBlock, hir::ClassifiedCommand, Signature, SyntaxShape, UntaggedValue, +}; + +pub struct Command; + +#[derive(Deserialize)] +pub struct Arguments { + block: CapturedBlock, +} + +#[async_trait] +impl WholeStreamCommand for Command { + fn name(&self) -> &str { + "all?" + } + + fn signature(&self) -> Signature { + Signature::build("all?").required( + "condition", + SyntaxShape::RowCondition, + "the condition that must match", + ) + } + + fn usage(&self) -> &str { + "Find if the table rows matches the condition." + } + + async fn run(&self, args: CommandArgs) -> Result { + all(args).await + } + + fn examples(&self) -> Vec { + use nu_protocol::Value; + + vec![ + Example { + description: "Find if services are running", + example: "echo [[status]; [UP] [UP]] | all? status == UP", + result: Some(vec![Value::from(true)]), + }, + Example { + description: "Check that all values are even", + example: "echo [2 4 6 8] | all? $(= $it mod 2) == 0", + result: Some(vec![Value::from(true)]), + }, + ] + } +} + +async fn all(args: CommandArgs) -> Result { + let ctx = Arc::new(EvaluationContext::from_args(&args)); + let tag = args.call_info.name_tag.clone(); + let (Arguments { block }, input) = args.process().await?; + + let condition = { + if block.block.block.len() != 1 { + return Err(ShellError::labeled_error( + "Expected a condition", + "expected a condition", + tag, + )); + } + match block.block.block[0].pipelines.get(0) { + Some(item) => match item.list.get(0) { + Some(ClassifiedCommand::Expr(expr)) => expr.clone(), + _ => { + return Err(ShellError::labeled_error( + "Expected a condition", + "expected a condition", + tag, + )); + } + }, + None => { + return Err(ShellError::labeled_error( + "Expected a condition", + "expected a condition", + tag, + )); + } + } + }; + + let init = Ok(InputStream::one( + UntaggedValue::boolean(true).into_value(&tag), + )); + + Ok(input + .fold(init, move |acc, row| { + let condition = condition.clone(); + let ctx = ctx.clone(); + ctx.scope.enter_scope(); + ctx.scope.add_vars(&block.captured.entries); + ctx.scope.add_var("$it", row); + + async move { + let condition = evaluate_baseline_expr(&condition, &*ctx).await.clone(); + ctx.scope.exit_scope(); + + let curr = acc?.drain_vec().await; + let curr = curr + .get(0) + .ok_or_else(|| ShellError::unexpected("No value to check with"))?; + let cond = curr.as_bool()?; + + match condition { + Ok(condition) => match condition.as_bool() { + Ok(b) => Ok(InputStream::one( + UntaggedValue::boolean(cond && b).into_value(&curr.tag), + )), + Err(e) => Err(e), + }, + Err(e) => Err(e), + } + } + }) + .await? + .to_output_stream()) +} + +#[cfg(test)] +mod tests { + use super::Command; + use super::ShellError; + + #[test] + fn examples_work_as_expected() -> Result<(), ShellError> { + use crate::examples::test as test_examples; + + test_examples(Command {}) + } +} diff --git a/crates/nu-command/src/commands/any.rs b/crates/nu-command/src/commands/any.rs new file mode 100644 index 0000000000..d9aec6b635 --- /dev/null +++ b/crates/nu-command/src/commands/any.rs @@ -0,0 +1,138 @@ +use crate::prelude::*; +use nu_engine::evaluate_baseline_expr; +use nu_engine::WholeStreamCommand; +use nu_errors::ShellError; +use nu_protocol::{ + hir::CapturedBlock, hir::ClassifiedCommand, Signature, SyntaxShape, UntaggedValue, +}; + +pub struct Command; + +#[derive(Deserialize)] +pub struct Arguments { + block: CapturedBlock, +} + +#[async_trait] +impl WholeStreamCommand for Command { + fn name(&self) -> &str { + "any?" + } + + fn signature(&self) -> Signature { + Signature::build("any?").required( + "condition", + SyntaxShape::RowCondition, + "the condition that must match", + ) + } + + fn usage(&self) -> &str { + "Find if the table rows matches the condition." + } + + async fn run(&self, args: CommandArgs) -> Result { + any(args).await + } + + fn examples(&self) -> Vec { + use nu_protocol::Value; + + vec![ + Example { + description: "Find if a service is not running", + example: "echo [[status]; [UP] [DOWN] [UP]] | any? status == DOWN", + result: Some(vec![Value::from(true)]), + }, + Example { + description: "Check if any of the values is odd", + example: "echo [2 4 1 6 8] | any? $(= $it mod 2) == 1", + result: Some(vec![Value::from(true)]), + }, + ] + } +} + +async fn any(args: CommandArgs) -> Result { + let ctx = Arc::new(EvaluationContext::from_args(&args)); + let tag = args.call_info.name_tag.clone(); + let (Arguments { block }, input) = args.process().await?; + + let condition = { + if block.block.block.len() != 1 { + return Err(ShellError::labeled_error( + "Expected a condition", + "expected a condition", + tag, + )); + } + match block.block.block[0].pipelines.get(0) { + Some(item) => match item.list.get(0) { + Some(ClassifiedCommand::Expr(expr)) => expr.clone(), + _ => { + return Err(ShellError::labeled_error( + "Expected a condition", + "expected a condition", + tag, + )); + } + }, + None => { + return Err(ShellError::labeled_error( + "Expected a condition", + "expected a condition", + tag, + )); + } + } + }; + + let cond = Ok(InputStream::one( + UntaggedValue::boolean(false).into_value(&tag), + )); + + Ok(input + .fold(cond, move |cond, row| { + let condition = condition.clone(); + let ctx = ctx.clone(); + ctx.scope.enter_scope(); + ctx.scope.add_vars(&block.captured.entries); + ctx.scope.add_var("$it", row); + + async move { + let condition = evaluate_baseline_expr(&condition, &*ctx).await.clone(); + ctx.scope.exit_scope(); + + let curr = cond?.drain_vec().await; + let curr = curr + .get(0) + .ok_or_else(|| ShellError::unexpected("No value to check with"))?; + let cond = curr.as_bool()?; + + match condition { + Ok(condition) => match condition.as_bool() { + Ok(b) => Ok(InputStream::one( + UntaggedValue::boolean(cond || b).into_value(&curr.tag), + )), + Err(e) => Err(e), + }, + Err(e) => Err(e), + } + } + }) + .await? + .to_output_stream()) +} + +#[cfg(test)] +mod tests { + use super::Command; + use super::ShellError; + + #[test] + fn examples_work_as_expected() -> Result<(), ShellError> { + use crate::examples::test as test_examples; + + test_examples(Command {}) + } +} diff --git a/crates/nu-command/src/commands/default_context.rs b/crates/nu-command/src/commands/default_context.rs index dd83bd9291..e564bf3d32 100644 --- a/crates/nu-command/src/commands/default_context.rs +++ b/crates/nu-command/src/commands/default_context.rs @@ -124,6 +124,8 @@ pub fn create_default_context(interactive: bool) -> Result &str { "where" } @@ -63,7 +63,7 @@ impl WholeStreamCommand for Where { async fn where_command(raw_args: CommandArgs) -> Result { let ctx = Arc::new(EvaluationContext::from_args(&raw_args)); let tag = raw_args.call_info.name_tag.clone(); - let (WhereArgs { block }, input) = raw_args.process().await?; + let (Arguments { block }, input) = raw_args.process().await?; let condition = { if block.block.block.len() != 1 { return Err(ShellError::labeled_error( @@ -127,13 +127,13 @@ async fn where_command(raw_args: CommandArgs) -> Result Result<(), ShellError> { use crate::examples::test as test_examples; - test_examples(Where {}) + test_examples(Command {}) } } diff --git a/crates/nu-command/tests/commands/all.rs b/crates/nu-command/tests/commands/all.rs new file mode 100644 index 0000000000..ddffb79464 --- /dev/null +++ b/crates/nu-command/tests/commands/all.rs @@ -0,0 +1,41 @@ +use nu_test_support::pipeline as input; +use nu_test_support::playground::{says, Playground}; + +use hamcrest2::assert_that; +use hamcrest2::prelude::*; + +#[test] +fn checks_all_rows_are_true() { + Playground::setup("all_test_1", |_, nu| { + assert_that!( + nu.pipeline(&input( + r#" + echo [ "Andrés", "Andrés", "Andrés" ] + | all? $it == "Andrés" + "# + )), + says().to_stdout("true") + ); + }) +} + +#[test] +fn checks_all_columns_of_a_table_is_true() { + Playground::setup("any_test_1", |_, nu| { + assert_that!( + nu.pipeline(&input( + r#" + echo [ + [ first_name, last_name, rusty_at, likes ]; + [ Andrés, Robalino, 10/11/2013, 1 ] + [ Jonathan, Turner, 10/12/2013, 1 ] + [ Darren, Schroeder, 10/11/2013, 1 ] + [ Yehuda, Katz, 10/11/2013, 1 ] + ] + | all? likes > 0 + "# + )), + says().to_stdout("true") + ); + }) +} diff --git a/crates/nu-command/tests/commands/any.rs b/crates/nu-command/tests/commands/any.rs new file mode 100644 index 0000000000..cc60229c6a --- /dev/null +++ b/crates/nu-command/tests/commands/any.rs @@ -0,0 +1,41 @@ +use nu_test_support::pipeline as input; +use nu_test_support::playground::{says, Playground}; + +use hamcrest2::assert_that; +use hamcrest2::prelude::*; + +#[test] +fn checks_any_row_is_true() { + Playground::setup("any_test_1", |_, nu| { + assert_that!( + nu.pipeline(&input( + r#" + echo [ "Ecuador", "USA", "New Zealand" ] + | any? $it == "New Zealand" + "# + )), + says().to_stdout("true") + ); + }) +} + +#[test] +fn checks_any_column_of_a_table_is_true() { + Playground::setup("any_test_1", |_, nu| { + assert_that!( + nu.pipeline(&input( + r#" + echo [ + [ first_name, last_name, rusty_at, likes ]; + [ Andrés, Robalino, 10/11/2013, 1 ] + [ Jonathan, Turner, 10/12/2013, 1 ] + [ Darren, Schroeder, 10/11/2013, 1 ] + [ Yehuda, Katz, 10/11/2013, 1 ] + ] + | any? rusty_at == 10/12/2013 + "# + )), + says().to_stdout("true") + ); + }) +} diff --git a/crates/nu-command/tests/commands/mod.rs b/crates/nu-command/tests/commands/mod.rs index d4987a7e8a..e7ca5f22a4 100644 --- a/crates/nu-command/tests/commands/mod.rs +++ b/crates/nu-command/tests/commands/mod.rs @@ -1,3 +1,5 @@ +mod all; +mod any; mod append; mod cal; mod cd;