From bab8f6bd2873e4bb3cb17aa0c787a3a5a6e6d560 Mon Sep 17 00:00:00 2001 From: Arthur Targaryen Date: Mon, 29 Nov 2021 07:52:23 +0100 Subject: [PATCH] Port `skip`, `skip while` and `skip until` commands (#380) * Add `Skip` command * Add `SkipUntil` sub-command * Add `SkipWhile` sub-command * Add and use `Expression::as_row_condition_block` --- crates/nu-command/src/default_context.rs | 3 + crates/nu-command/src/filters/all.rs | 12 +-- crates/nu-command/src/filters/any.rs | 12 +-- crates/nu-command/src/filters/mod.rs | 2 + crates/nu-command/src/filters/skip/command.rs | 90 +++++++++++++++++++ crates/nu-command/src/filters/skip/mod.rs | 7 ++ crates/nu-command/src/filters/skip/until.rs | 89 ++++++++++++++++++ crates/nu-command/src/filters/skip/while_.rs | 89 ++++++++++++++++++ crates/nu-command/src/filters/where_.rs | 15 ++-- crates/nu-protocol/src/ast/expression.rs | 7 ++ 10 files changed, 300 insertions(+), 26 deletions(-) create mode 100644 crates/nu-command/src/filters/skip/command.rs create mode 100644 crates/nu-command/src/filters/skip/mod.rs create mode 100644 crates/nu-command/src/filters/skip/until.rs create mode 100644 crates/nu-command/src/filters/skip/while_.rs diff --git a/crates/nu-command/src/default_context.rs b/crates/nu-command/src/default_context.rs index 3630501db3..e693788053 100644 --- a/crates/nu-command/src/default_context.rs +++ b/crates/nu-command/src/default_context.rs @@ -116,6 +116,9 @@ pub fn create_default_context() -> EngineState { Select, Shuffle, Size, + Skip, + SkipUntil, + SkipWhile, Sleep, Source, Split, diff --git a/crates/nu-command/src/filters/all.rs b/crates/nu-command/src/filters/all.rs index e4ea5d5400..8389789e07 100644 --- a/crates/nu-command/src/filters/all.rs +++ b/crates/nu-command/src/filters/all.rs @@ -1,6 +1,6 @@ use nu_engine::eval_block; use nu_protocol::{ - ast::{Call, Expr, Expression}, + ast::Call, engine::{Command, EngineState, Stack}, Category, Example, IntoPipelineData, PipelineData, ShellError, Signature, SyntaxShape, }; @@ -52,13 +52,9 @@ impl Command for All { input: PipelineData, ) -> Result { let predicate = &call.positional[0]; - let block_id = match predicate { - Expression { - expr: Expr::RowCondition(block_id), - .. - } => *block_id, - _ => return Err(ShellError::InternalError("Expected row condition".into())), - }; + let block_id = predicate + .as_row_condition_block() + .ok_or_else(|| ShellError::InternalError("Expected row condition".to_owned()))?; let span = call.head; diff --git a/crates/nu-command/src/filters/any.rs b/crates/nu-command/src/filters/any.rs index 7cb4519560..36bfe912d8 100644 --- a/crates/nu-command/src/filters/any.rs +++ b/crates/nu-command/src/filters/any.rs @@ -1,6 +1,6 @@ use nu_engine::eval_block; use nu_protocol::{ - ast::{Call, Expr, Expression}, + ast::Call, engine::{Command, EngineState, Stack}, Category, Example, IntoPipelineData, PipelineData, ShellError, Signature, SyntaxShape, }; @@ -52,13 +52,9 @@ impl Command for Any { input: PipelineData, ) -> Result { let predicate = &call.positional[0]; - let block_id = match predicate { - Expression { - expr: Expr::RowCondition(block_id), - .. - } => *block_id, - _ => return Err(ShellError::InternalError("Expected row condition".into())), - }; + let block_id = predicate + .as_row_condition_block() + .ok_or_else(|| ShellError::InternalError("Expected row condition".to_owned()))?; let span = call.head; diff --git a/crates/nu-command/src/filters/mod.rs b/crates/nu-command/src/filters/mod.rs index 98e1f8c404..61f4f15939 100644 --- a/crates/nu-command/src/filters/mod.rs +++ b/crates/nu-command/src/filters/mod.rs @@ -14,6 +14,7 @@ mod range; mod reverse; mod select; mod shuffle; +mod skip; mod update; mod where_; mod wrap; @@ -35,6 +36,7 @@ pub use range::Range; pub use reverse::Reverse; pub use select::Select; pub use shuffle::Shuffle; +pub use skip::*; pub use update::Update; pub use where_::Where; pub use wrap::Wrap; diff --git a/crates/nu-command/src/filters/skip/command.rs b/crates/nu-command/src/filters/skip/command.rs new file mode 100644 index 0000000000..9a416afba5 --- /dev/null +++ b/crates/nu-command/src/filters/skip/command.rs @@ -0,0 +1,90 @@ +use std::convert::TryInto; + +use nu_engine::CallExt; +use nu_protocol::{ + ast::Call, + engine::{Command, EngineState, Stack}, + Category, Example, IntoInterruptiblePipelineData, PipelineData, ShellError, Signature, Span, + SyntaxShape, Value, +}; + +#[derive(Clone)] +pub struct Skip; + +impl Command for Skip { + fn name(&self) -> &str { + "skip" + } + + fn signature(&self) -> Signature { + Signature::build(self.name()) + .optional("n", SyntaxShape::Int, "the number of elements to skip") + .category(Category::Filters) + } + + fn usage(&self) -> &str { + "Skip the first n elements of the input." + } + + fn examples(&self) -> Vec { + vec![ + Example { + description: "Skip two elements", + example: "echo [[editions]; [2015] [2018] [2021]] | skip 2", + result: Some(Value::List { + vals: vec![Value::Record { + cols: vec!["editions".to_owned()], + vals: vec![Value::from(2021)], + span: Span::unknown(), + }], + span: Span::unknown(), + }), + }, + Example { + description: "Skip the first value", + example: "echo [2 4 6 8] | skip", + result: Some(Value::List { + vals: vec![Value::from(4), Value::from(6), Value::from(8)], + span: Span::unknown(), + }), + }, + ] + } + + fn run( + &self, + engine_state: &EngineState, + stack: &mut Stack, + call: &Call, + input: PipelineData, + ) -> Result { + let n: Option = call.opt(engine_state, stack, 0)?; + + let n: usize = match n { + Some(Value::Int { val, span }) => val.try_into().map_err(|err| { + ShellError::UnsupportedInput( + format!("Could not convert {} to unsigned integer: {}", val, err), + span, + ) + })?, + Some(_) => return Err(ShellError::InternalError("Expected integer".into())), + None => 1, + }; + + let ctrlc = engine_state.ctrlc.clone(); + + Ok(input.into_iter().skip(n).into_pipeline_data(ctrlc)) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_examples() { + use crate::test_examples; + + test_examples(Skip {}) + } +} diff --git a/crates/nu-command/src/filters/skip/mod.rs b/crates/nu-command/src/filters/skip/mod.rs new file mode 100644 index 0000000000..164cf5081a --- /dev/null +++ b/crates/nu-command/src/filters/skip/mod.rs @@ -0,0 +1,7 @@ +mod command; +mod until; +mod while_; + +pub use command::Skip; +pub use until::SkipUntil; +pub use while_::SkipWhile; diff --git a/crates/nu-command/src/filters/skip/until.rs b/crates/nu-command/src/filters/skip/until.rs new file mode 100644 index 0000000000..5c09adcf60 --- /dev/null +++ b/crates/nu-command/src/filters/skip/until.rs @@ -0,0 +1,89 @@ +use nu_engine::eval_block; +use nu_protocol::{ + ast::Call, + engine::{Command, EngineState, Stack}, + Category, Example, IntoInterruptiblePipelineData, PipelineData, ShellError, Signature, Span, + SyntaxShape, Value, +}; + +#[derive(Clone)] +pub struct SkipUntil; + +impl Command for SkipUntil { + fn name(&self) -> &str { + "skip until" + } + + fn signature(&self) -> Signature { + Signature::build(self.name()) + .required( + "predicate", + SyntaxShape::RowCondition, + "the predicate that skipped element must not match", + ) + .category(Category::Filters) + } + + fn usage(&self) -> &str { + "Skip elements of the input until a predicate is true." + } + + fn examples(&self) -> Vec { + vec![Example { + description: "Skip until the element is positive", + example: "echo [-2 0 2 -1] | skip until $it > 0", + result: Some(Value::List { + vals: vec![Value::from(2), Value::from(-1)], + span: Span::unknown(), + }), + }] + } + + fn run( + &self, + engine_state: &EngineState, + stack: &mut Stack, + call: &Call, + input: PipelineData, + ) -> Result { + let predicate = &call.positional[0]; + let block_id = predicate + .as_row_condition_block() + .ok_or_else(|| ShellError::InternalError("Expected row condition".to_owned()))?; + + let span = call.head; + + let block = engine_state.get_block(block_id).clone(); + let var_id = block.signature.get_positional(0).and_then(|arg| arg.var_id); + let mut stack = stack.collect_captures(&block.captures); + + let ctrlc = engine_state.ctrlc.clone(); + let engine_state = engine_state.clone(); + + Ok(input + .into_iter() + .skip_while(move |value| { + if let Some(var_id) = var_id { + stack.add_var(var_id, value.clone()); + } + + !eval_block(&engine_state, &mut stack, &block, PipelineData::new(span)) + .map_or(false, |pipeline_data| { + pipeline_data.into_value(span).is_true() + }) + }) + .into_pipeline_data(ctrlc)) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_examples() { + use crate::test_examples; + + test_examples(SkipUntil) + } +} diff --git a/crates/nu-command/src/filters/skip/while_.rs b/crates/nu-command/src/filters/skip/while_.rs new file mode 100644 index 0000000000..cc125d47b6 --- /dev/null +++ b/crates/nu-command/src/filters/skip/while_.rs @@ -0,0 +1,89 @@ +use nu_engine::eval_block; +use nu_protocol::{ + ast::Call, + engine::{Command, EngineState, Stack}, + Category, Example, IntoInterruptiblePipelineData, PipelineData, ShellError, Signature, Span, + SyntaxShape, Value, +}; + +#[derive(Clone)] +pub struct SkipWhile; + +impl Command for SkipWhile { + fn name(&self) -> &str { + "skip while" + } + + fn signature(&self) -> Signature { + Signature::build(self.name()) + .required( + "predicate", + SyntaxShape::RowCondition, + "the predicate that skipped element must match", + ) + .category(Category::Filters) + } + + fn usage(&self) -> &str { + "Skip elements of the input while a predicate is true." + } + + fn examples(&self) -> Vec { + vec![Example { + description: "Skip while the element is negative", + example: "echo [-2 0 2 -1] | skip while $it < 0", + result: Some(Value::List { + vals: vec![Value::from(0), Value::from(2), Value::from(-1)], + span: Span::unknown(), + }), + }] + } + + fn run( + &self, + engine_state: &EngineState, + stack: &mut Stack, + call: &Call, + input: PipelineData, + ) -> Result { + let predicate = &call.positional[0]; + let block_id = predicate + .as_row_condition_block() + .ok_or_else(|| ShellError::InternalError("Expected row condition".to_owned()))?; + + let span = call.head; + + let block = engine_state.get_block(block_id).clone(); + let var_id = block.signature.get_positional(0).and_then(|arg| arg.var_id); + let mut stack = stack.collect_captures(&block.captures); + + let ctrlc = engine_state.ctrlc.clone(); + let engine_state = engine_state.clone(); + + Ok(input + .into_iter() + .skip_while(move |value| { + if let Some(var_id) = var_id { + stack.add_var(var_id, value.clone()); + } + + eval_block(&engine_state, &mut stack, &block, PipelineData::new(span)) + .map_or(false, |pipeline_data| { + pipeline_data.into_value(span).is_true() + }) + }) + .into_pipeline_data(ctrlc)) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_examples() { + use crate::test_examples; + + test_examples(SkipWhile) + } +} diff --git a/crates/nu-command/src/filters/where_.rs b/crates/nu-command/src/filters/where_.rs index c411d2fe51..7adc5473ac 100644 --- a/crates/nu-command/src/filters/where_.rs +++ b/crates/nu-command/src/filters/where_.rs @@ -1,5 +1,5 @@ use nu_engine::eval_block; -use nu_protocol::ast::{Call, Expr, Expression}; +use nu_protocol::ast::Call; use nu_protocol::engine::{Command, EngineState, Stack}; use nu_protocol::{Category, PipelineData, ShellError, Signature, SyntaxShape}; @@ -29,19 +29,14 @@ impl Command for Where { input: PipelineData, ) -> Result { let span = call.head; - let cond = call.positional[0].clone(); + let cond = &call.positional[0]; + let block_id = cond + .as_row_condition_block() + .ok_or_else(|| ShellError::InternalError("Expected row condition".to_owned()))?; let ctrlc = engine_state.ctrlc.clone(); let engine_state = engine_state.clone(); - let block_id = match cond { - Expression { - expr: Expr::RowCondition(block_id), - .. - } => block_id, - _ => return Err(ShellError::InternalError("Expected row condition".into())), - }; - let block = engine_state.get_block(block_id).clone(); let mut stack = stack.collect_captures(&block.captures); diff --git a/crates/nu-protocol/src/ast/expression.rs b/crates/nu-protocol/src/ast/expression.rs index b3cf0743a7..374a970781 100644 --- a/crates/nu-protocol/src/ast/expression.rs +++ b/crates/nu-protocol/src/ast/expression.rs @@ -53,6 +53,13 @@ impl Expression { } } + pub fn as_row_condition_block(&self) -> Option { + match self.expr { + Expr::RowCondition(block_id) => Some(block_id), + _ => None, + } + } + pub fn as_signature(&self) -> Option> { match &self.expr { Expr::Signature(sig) => Some(sig.clone()),