From 43972db131fbc1a6373e11ba1f4866a7c7cdd202 Mon Sep 17 00:00:00 2001 From: Jae-Heon Ji <32578710+jaeheonji@users.noreply.github.com> Date: Fri, 3 Dec 2021 02:26:12 +0900 Subject: [PATCH] feat(random): add random-decimal (#402) --- crates/nu-command/src/random/decimal.rs | 116 ++++++++++++++++++++++++ crates/nu-command/src/random/mod.rs | 2 + crates/nu-protocol/src/shell_error.rs | 4 + crates/nu-protocol/src/value/mod.rs | 15 +++ 4 files changed, 137 insertions(+) create mode 100644 crates/nu-command/src/random/decimal.rs diff --git a/crates/nu-command/src/random/decimal.rs b/crates/nu-command/src/random/decimal.rs new file mode 100644 index 0000000000..845584cfc7 --- /dev/null +++ b/crates/nu-command/src/random/decimal.rs @@ -0,0 +1,116 @@ +use nu_engine::CallExt; +use nu_protocol::ast::Call; +use nu_protocol::engine::{Command, EngineState, Stack}; +use nu_protocol::{ + Category, Example, PipelineData, Range, ShellError, Signature, Span, SyntaxShape, Value, +}; +use rand::prelude::{thread_rng, Rng}; +use std::cmp::Ordering; + +#[derive(Clone)] +pub struct SubCommand; + +impl Command for SubCommand { + fn name(&self) -> &str { + "random decimal" + } + + fn signature(&self) -> Signature { + Signature::build("random decimal") + .optional("range", SyntaxShape::Range, "Range of values") + .category(Category::Random) + } + + fn usage(&self) -> &str { + "Generate a random decimal within a range [min..max]" + } + + fn run( + &self, + engine_state: &EngineState, + stack: &mut Stack, + call: &Call, + _input: PipelineData, + ) -> Result { + decimal(engine_state, stack, call) + } + + fn examples(&self) -> Vec { + vec![ + Example { + description: "Generate a default decimal value between 0 and 1", + example: "random decimal", + result: None, + }, + Example { + description: "Generate a random decimal less than or equal to 500", + example: "random decimal ..500", + result: None, + }, + Example { + description: "Generate a random decimal greater than or equal to 100000", + example: "random decimal 100000..", + result: None, + }, + Example { + description: "Generate a random decimal between 1.0 and 1.1", + example: "random decimal 1.0..1.1", + result: None, + }, + ] + } +} + +fn decimal( + engine_state: &EngineState, + stack: &mut Stack, + call: &Call, +) -> Result { + let span = call.head; + let range: Option = call.opt(engine_state, stack, 0)?; + + let (min, max) = if let Some(r) = range { + (r.from.as_float()?, r.to.as_float()?) + } else { + (0.0, 1.0) + }; + + match min.partial_cmp(&max) { + Some(Ordering::Greater) => Err(ShellError::InvalidRange( + min.to_string(), + max.to_string(), + span, + )), + Some(Ordering::Equal) => Ok(PipelineData::Value( + Value::Float { + val: min, + span: Span::new(64, 64), + }, + None, + )), + _ => { + let mut thread_rng = thread_rng(); + let result: f64 = thread_rng.gen_range(min..max); + + Ok(PipelineData::Value( + Value::Float { + val: result, + span: Span::new(64, 64), + }, + None, + )) + } + } +} + +#[cfg(test)] +mod test { + use super::*; + + #[test] + fn test_examples() { + use crate::test_examples; + + test_examples(SubCommand {}) + } +} diff --git a/crates/nu-command/src/random/mod.rs b/crates/nu-command/src/random/mod.rs index 9d429742c2..c199b66a9a 100644 --- a/crates/nu-command/src/random/mod.rs +++ b/crates/nu-command/src/random/mod.rs @@ -1,7 +1,9 @@ mod bool; mod chars; mod command; +mod decimal; pub use self::bool::SubCommand as Bool; pub use self::chars::SubCommand as Chars; +pub use self::decimal::SubCommand as Decimal; pub use command::RandomCommand as Random; diff --git a/crates/nu-protocol/src/shell_error.rs b/crates/nu-protocol/src/shell_error.rs index 16159b06da..63a559f3f8 100644 --- a/crates/nu-protocol/src/shell_error.rs +++ b/crates/nu-protocol/src/shell_error.rs @@ -80,6 +80,10 @@ pub enum ShellError { #[diagnostic(code(nu::shell::invalid_probability), url(docsrs))] InvalidProbability(#[label = "invalid probability"] Span), + #[error("Invalid range {0}..{1}")] + #[diagnostic(code(nu::shell::invalid_range), url(docsrs))] + InvalidRange(String, String, #[label = "expected a valid range"] Span), + #[error("Internal error: {0}.")] #[diagnostic(code(nu::shell::internal_error), url(docsrs))] InternalError(String), diff --git a/crates/nu-protocol/src/value/mod.rs b/crates/nu-protocol/src/value/mod.rs index 161b380b3c..501a4d4c02 100644 --- a/crates/nu-protocol/src/value/mod.rs +++ b/crates/nu-protocol/src/value/mod.rs @@ -203,6 +203,17 @@ impl Value { } } + pub fn as_float(&self) -> Result { + match self { + Value::Float { val, .. } => Ok(*val), + x => Err(ShellError::CantConvert( + "float".into(), + x.get_type().to_string(), + self.span()?, + )), + } + } + /// Get the span for the current value pub fn span(&self) -> Result { match self { @@ -565,6 +576,10 @@ impl Value { Value::Int { val, span } } + pub fn float(val: f64, span: Span) -> Value { + Value::Float { val, span } + } + // Only use these for test data. Span::unknown() should not be used in user data pub fn test_string(s: impl Into) -> Value { Value::String {