diff --git a/crates/nu-command/src/default_context.rs b/crates/nu-command/src/default_context.rs index 32cb38aa89..cab72fac8d 100644 --- a/crates/nu-command/src/default_context.rs +++ b/crates/nu-command/src/default_context.rs @@ -53,6 +53,8 @@ pub fn create_default_context() -> EngineState { Math, MathAbs, MathAvg, + MathMax, + MathMin, Mkdir, Module, Mv, diff --git a/crates/nu-command/src/math/avg.rs b/crates/nu-command/src/math/avg.rs index 384d3493a5..f7de5be527 100644 --- a/crates/nu-command/src/math/avg.rs +++ b/crates/nu-command/src/math/avg.rs @@ -50,6 +50,7 @@ pub fn average(values: &[Value], head: &Span) -> Result { span: Span::unknown(), }, values.to_vec(), + *head, )?; match total { Value::Filesize { val, span } => Ok(Value::Filesize { diff --git a/crates/nu-command/src/math/max.rs b/crates/nu-command/src/math/max.rs new file mode 100644 index 0000000000..81d146502b --- /dev/null +++ b/crates/nu-command/src/math/max.rs @@ -0,0 +1,57 @@ +use crate::math::reducers::{reducer_for, Reduce}; +use crate::math::utils::run_with_function; +use nu_protocol::ast::Call; +use nu_protocol::engine::{Command, EngineState, Stack}; +use nu_protocol::{Example, PipelineData, ShellError, Signature, Span, Value}; + +#[derive(Clone)] +pub struct SubCommand; + +impl Command for SubCommand { + fn name(&self) -> &str { + "math max" + } + + fn signature(&self) -> Signature { + Signature::build("math max") + } + + fn usage(&self) -> &str { + "Finds the maximum within a list of numbers or tables" + } + + fn run( + &self, + _engine_state: &EngineState, + _stack: &mut Stack, + call: &Call, + input: PipelineData, + ) -> Result { + run_with_function(call, input, maximum) + } + + fn examples(&self) -> Vec { + vec![Example { + description: "Find the maximum of list of numbers", + example: "[-50 100 25] | math max", + result: Some(Value::test_int(100)), + }] + } +} + +pub fn maximum(values: &[Value], head: &Span) -> Result { + let max_func = reducer_for(Reduce::Maximum); + max_func(Value::nothing(), values.to_vec(), *head) +} + +#[cfg(test)] +mod test { + use super::*; + + #[test] + fn test_examples() { + use crate::test_examples; + + test_examples(SubCommand {}) + } +} diff --git a/crates/nu-command/src/math/min.rs b/crates/nu-command/src/math/min.rs new file mode 100644 index 0000000000..b232d13074 --- /dev/null +++ b/crates/nu-command/src/math/min.rs @@ -0,0 +1,57 @@ +use crate::math::reducers::{reducer_for, Reduce}; +use crate::math::utils::run_with_function; +use nu_protocol::ast::Call; +use nu_protocol::engine::{Command, EngineState, Stack}; +use nu_protocol::{Example, PipelineData, ShellError, Signature, Span, Value}; + +#[derive(Clone)] +pub struct SubCommand; + +impl Command for SubCommand { + fn name(&self) -> &str { + "math min" + } + + fn signature(&self) -> Signature { + Signature::build("math min") + } + + fn usage(&self) -> &str { + "Finds the minimum within a list of numbers or tables" + } + + fn run( + &self, + _engine_state: &EngineState, + _stack: &mut Stack, + call: &Call, + input: PipelineData, + ) -> Result { + run_with_function(call, input, minimum) + } + + fn examples(&self) -> Vec { + vec![Example { + description: "Get the minimum of a list of numbers", + example: "[-50 100 25] | math min", + result: Some(Value::test_int(-50)), + }] + } +} + +pub fn minimum(values: &[Value], head: &Span) -> Result { + let min_func = reducer_for(Reduce::Minimum); + min_func(Value::nothing(), values.to_vec(), *head) +} + +#[cfg(test)] +mod test { + use super::*; + + #[test] + fn test_examples() { + use crate::test_examples; + + test_examples(SubCommand {}) + } +} diff --git a/crates/nu-command/src/math/mod.rs b/crates/nu-command/src/math/mod.rs index c3f4a42934..6b2bcffc76 100644 --- a/crates/nu-command/src/math/mod.rs +++ b/crates/nu-command/src/math/mod.rs @@ -1,9 +1,13 @@ mod abs; mod avg; pub mod command; +mod max; +mod min; mod reducers; mod utils; pub use abs::SubCommand as MathAbs; pub use avg::SubCommand as MathAvg; pub use command::MathCommand as Math; +pub use max::SubCommand as MathMax; +pub use min::SubCommand as MathMin; diff --git a/crates/nu-command/src/math/reducers.rs b/crates/nu-command/src/math/reducers.rs index 2339fd97f8..9e64602ebe 100644 --- a/crates/nu-command/src/math/reducers.rs +++ b/crates/nu-command/src/math/reducers.rs @@ -1,19 +1,73 @@ use nu_protocol::{ShellError, Span, Value}; +use std::cmp::Ordering; #[allow(dead_code)] pub enum Reduce { Summation, + Minimum, + Maximum, } -pub fn reducer_for( - command: Reduce, -) -> Box) -> Result + Send + Sync + 'static> { +pub type ReducerFunction = + Box, Span) -> Result + Send + Sync + 'static>; + +pub fn reducer_for(command: Reduce) -> ReducerFunction { match command { - Reduce::Summation => Box::new(|_, values| sum(values)), + Reduce::Summation => Box::new(|_, values, head| sum(values, head)), + Reduce::Minimum => Box::new(|_, values, head| min(values, head)), + Reduce::Maximum => Box::new(|_, values, head| max(values, head)), } } -pub fn sum(data: Vec) -> Result { +pub fn max(data: Vec, head: Span) -> Result { + let mut biggest = data + .first() + .ok_or_else(|| ShellError::UnsupportedInput("Empty input".to_string(), Span::unknown()))? + .clone(); + + for value in &data { + if let Some(result) = value.partial_cmp(&biggest) { + if result == Ordering::Greater { + biggest = value.clone(); + } + } else { + return Err(ShellError::OperatorMismatch { + op_span: head, + lhs_ty: biggest.get_type(), + lhs_span: biggest.span()?, + rhs_ty: value.get_type(), + rhs_span: value.span()?, + }); + } + } + Ok(biggest) +} + +pub fn min(data: Vec, head: Span) -> Result { + let mut smallest = data + .first() + .ok_or_else(|| ShellError::UnsupportedInput("Empty input".to_string(), Span::unknown()))? + .clone(); + + for value in &data { + if let Some(result) = value.partial_cmp(&smallest) { + if result == Ordering::Less { + smallest = value.clone(); + } + } else { + return Err(ShellError::OperatorMismatch { + op_span: head, + lhs_ty: smallest.get_type(), + lhs_span: smallest.span()?, + rhs_ty: value.get_type(), + rhs_span: value.span()?, + }); + } + } + Ok(smallest) +} + +pub fn sum(data: Vec, head: Span) -> Result { let initial_value = data.get(0); let mut acc = match initial_value { @@ -42,7 +96,7 @@ pub fn sum(data: Vec) -> Result { | Value::Float { .. } | Value::Filesize { .. } | Value::Duration { .. } => { - let new_value = acc.add(acc.span().unwrap_or_else(|_| Span::unknown()), value); + let new_value = acc.add(head, value); if new_value.is_err() { return new_value; }