From 63c3d19c67c1217739782772701d2dd1176435b0 Mon Sep 17 00:00:00 2001 From: Arthur Date: Sat, 27 Nov 2021 18:49:03 +0100 Subject: [PATCH] Port `all?` command (#365) * Implement `From` for `Value` * Add `All` command * Change `IntoPipelineData` and `IntoInterruptiblePipelineData` bounds * Refactor `PipelineIterator` impls * Add `PipelineData::into_interruptible_iter` * Use `into_interruptible_iter` instead of `all` helper * Merge imports * Refactor `PipelineData::{filter, map}` * Change comment pronoun * Treat `RowCondition` as a block * Remove unnecessary braces * Address cluppy warning --- crates/nu-command/src/default_context.rs | 1 + crates/nu-command/src/filters/all.rs | 98 ++++++++++++++++++++++++ crates/nu-command/src/filters/mod.rs | 2 + crates/nu-protocol/src/pipeline_data.rs | 56 ++++++++------ crates/nu-protocol/src/value/from.rs | 9 +++ 5 files changed, 142 insertions(+), 24 deletions(-) create mode 100644 crates/nu-command/src/filters/all.rs diff --git a/crates/nu-command/src/default_context.rs b/crates/nu-command/src/default_context.rs index 55d3d34def..b889eb5dca 100644 --- a/crates/nu-command/src/default_context.rs +++ b/crates/nu-command/src/default_context.rs @@ -20,6 +20,7 @@ pub fn create_default_context() -> EngineState { // TODO: sort default context items categorically bind_command!( Alias, + All, Append, Benchmark, BuildString, diff --git a/crates/nu-command/src/filters/all.rs b/crates/nu-command/src/filters/all.rs new file mode 100644 index 0000000000..e4ea5d5400 --- /dev/null +++ b/crates/nu-command/src/filters/all.rs @@ -0,0 +1,98 @@ +use nu_engine::eval_block; +use nu_protocol::{ + ast::{Call, Expr, Expression}, + engine::{Command, EngineState, Stack}, + Category, Example, IntoPipelineData, PipelineData, ShellError, Signature, SyntaxShape, +}; + +#[derive(Clone)] +pub struct All; + +impl Command for All { + fn name(&self) -> &str { + "all?" + } + + fn signature(&self) -> Signature { + Signature::build(self.name()) + .required( + "predicate", + SyntaxShape::RowCondition, + "the predicate that must match", + ) + .category(Category::Filters) + } + + fn usage(&self) -> &str { + "Test if every element of the input matches a predicate." + } + + 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(Value::from(true)), + }, + Example { + description: "Check that all values are even", + example: "echo [2 4 6 8] | all? ($it mod 2) == 0", + result: Some(Value::from(true)), + }, + ] + } + + fn run( + &self, + engine_state: &EngineState, + stack: &mut Stack, + call: &Call, + 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 span = call.head; + + let block = engine_state.get_block(block_id); + 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_interruptible_iter(ctrlc) + .all(move |value| { + if let Some(var_id) = var_id { + stack.add_var(var_id, value); + } + + 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()) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_examples() { + use crate::test_examples; + + test_examples(All) + } +} diff --git a/crates/nu-command/src/filters/mod.rs b/crates/nu-command/src/filters/mod.rs index aba51e488d..8cd39f229a 100644 --- a/crates/nu-command/src/filters/mod.rs +++ b/crates/nu-command/src/filters/mod.rs @@ -1,3 +1,4 @@ +mod all; mod append; mod collect; mod drop; @@ -17,6 +18,7 @@ mod where_; mod wrap; mod zip; +pub use all::All; pub use append::Append; pub use collect::Collect; pub use drop::*; diff --git a/crates/nu-protocol/src/pipeline_data.rs b/crates/nu-protocol/src/pipeline_data.rs index 67f63cad3c..27356826c6 100644 --- a/crates/nu-protocol/src/pipeline_data.rs +++ b/crates/nu-protocol/src/pipeline_data.rs @@ -51,6 +51,16 @@ impl PipelineData { } } + pub fn into_interruptible_iter(self, ctrlc: Option>) -> PipelineIterator { + let mut iter = self.into_iter(); + + if let PipelineIterator(PipelineData::Stream(s)) = &mut iter { + s.ctrlc = ctrlc; + } + + iter + } + pub fn collect_string(self, separator: &str, config: &Config) -> String { match self { PipelineData::Value(v) => v.into_string(separator, config), @@ -104,13 +114,10 @@ impl PipelineData { PipelineData::Value(Value::Range { val, .. }) => { Ok(val.into_range_iter()?.map(f).into_pipeline_data(ctrlc)) } - PipelineData::Value(v) => { - let output = f(v); - match output { - Value::Error { error } => Err(error), - v => Ok(v.into_pipeline_data()), - } - } + PipelineData::Value(v) => match f(v) { + Value::Error { error } => Err(error), + v => Ok(v.into_pipeline_data()), + }, } } @@ -153,10 +160,9 @@ impl PipelineData { Ok(vals.into_iter().filter(f).into_pipeline_data(ctrlc)) } PipelineData::Stream(stream) => Ok(stream.filter(f).into_pipeline_data(ctrlc)), - PipelineData::Value(Value::Range { val, .. }) => match val.into_range_iter() { - Ok(iter) => Ok(iter.filter(f).into_pipeline_data(ctrlc)), - Err(error) => Err(error), - }, + PipelineData::Value(Value::Range { val, .. }) => { + Ok(val.into_range_iter()?.filter(f).into_pipeline_data(ctrlc)) + } PipelineData::Value(v) => { if f(&v) { Ok(v.into_pipeline_data()) @@ -190,12 +196,12 @@ impl IntoIterator for PipelineData { })) } PipelineData::Value(Value::Range { val, .. }) => match val.into_range_iter() { - Ok(val) => PipelineIterator(PipelineData::Stream(ValueStream { - stream: Box::new(val), + Ok(iter) => PipelineIterator(PipelineData::Stream(ValueStream { + stream: Box::new(iter), ctrlc: None, })), - Err(e) => PipelineIterator(PipelineData::Stream(ValueStream { - stream: Box::new(vec![Value::Error { error: e }].into_iter()), + Err(error) => PipelineIterator(PipelineData::Stream(ValueStream { + stream: Box::new(std::iter::once(Value::Error { error })), ctrlc: None, })), }, @@ -210,10 +216,7 @@ impl Iterator for PipelineIterator { fn next(&mut self) -> Option { match &mut self.0 { PipelineData::Value(Value::Nothing { .. }) => None, - PipelineData::Value(v) => { - let prev = std::mem::take(v); - Some(prev) - } + PipelineData::Value(v) => Some(std::mem::take(v)), PipelineData::Stream(stream) => stream.next(), } } @@ -223,9 +226,12 @@ pub trait IntoPipelineData { fn into_pipeline_data(self) -> PipelineData; } -impl IntoPipelineData for Value { +impl IntoPipelineData for V +where + V: Into, +{ fn into_pipeline_data(self) -> PipelineData { - PipelineData::Value(self) + PipelineData::Value(self.into()) } } @@ -233,13 +239,15 @@ pub trait IntoInterruptiblePipelineData { fn into_pipeline_data(self, ctrlc: Option>) -> PipelineData; } -impl IntoInterruptiblePipelineData for T +impl IntoInterruptiblePipelineData for I where - T: Iterator + Send + 'static, + I: IntoIterator + Send + 'static, + I::IntoIter: Send + 'static, + ::Item: Into, { fn into_pipeline_data(self, ctrlc: Option>) -> PipelineData { PipelineData::Stream(ValueStream { - stream: Box::new(self), + stream: Box::new(self.into_iter().map(Into::into)), ctrlc, }) } diff --git a/crates/nu-protocol/src/value/from.rs b/crates/nu-protocol/src/value/from.rs index c5e3c253b3..f61253b3d6 100644 --- a/crates/nu-protocol/src/value/from.rs +++ b/crates/nu-protocol/src/value/from.rs @@ -1,5 +1,14 @@ use crate::{ShellError, Span, Value}; +impl From for Value { + fn from(val: bool) -> Self { + Value::Bool { + val, + span: Span::unknown(), + } + } +} + impl From for Value { fn from(val: u8) -> Self { Value::Int {