diff --git a/crates/nu-command/src/default_context.rs b/crates/nu-command/src/default_context.rs index 977aa3919..7b4e93e97 100644 --- a/crates/nu-command/src/default_context.rs +++ b/crates/nu-command/src/default_context.rs @@ -114,6 +114,7 @@ pub fn create_default_context() -> EngineState { Parse, Ps, Range, + Random, Reverse, Rm, Select, diff --git a/crates/nu-command/src/example_test.rs b/crates/nu-command/src/example_test.rs index 1c7d00673..da5789c91 100644 --- a/crates/nu-command/src/example_test.rs +++ b/crates/nu-command/src/example_test.rs @@ -7,7 +7,7 @@ use nu_protocol::{ use crate::To; -use super::{Date, From, Into, Math, Split, Str}; +use super::{Date, From, Into, Math, Random, Split, Str}; pub fn test_examples(cmd: impl Command + 'static) { let examples = cmd.examples(); @@ -21,6 +21,7 @@ pub fn test_examples(cmd: impl Command + 'static) { working_set.add_decl(Box::new(From)); working_set.add_decl(Box::new(To)); working_set.add_decl(Box::new(Into)); + working_set.add_decl(Box::new(Random)); working_set.add_decl(Box::new(Split)); working_set.add_decl(Box::new(Math)); working_set.add_decl(Box::new(Date)); diff --git a/crates/nu-command/src/lib.rs b/crates/nu-command/src/lib.rs index 3e8cbee22..12b0d9bb2 100644 --- a/crates/nu-command/src/lib.rs +++ b/crates/nu-command/src/lib.rs @@ -10,6 +10,7 @@ mod filters; mod formats; mod math; mod platform; +mod random; mod shells; mod strings; mod system; @@ -30,6 +31,7 @@ pub use filters::*; pub use formats::*; pub use math::*; pub use platform::*; +pub use random::*; pub use shells::*; pub use strings::*; pub use system::*; diff --git a/crates/nu-command/src/random/bool.rs b/crates/nu-command/src/random/bool.rs new file mode 100644 index 000000000..bd462d5c7 --- /dev/null +++ b/crates/nu-command/src/random/bool.rs @@ -0,0 +1,97 @@ +use nu_engine::CallExt; +use nu_protocol::ast::Call; +use nu_protocol::engine::{Command, EngineState, Stack}; +use nu_protocol::{ + Category, Example, PipelineData, ShellError, Signature, Spanned, SyntaxShape, Value, +}; +use rand::prelude::{thread_rng, Rng}; + +#[derive(Clone)] +pub struct SubCommand; + +impl Command for SubCommand { + fn name(&self) -> &str { + "random bool" + } + + fn signature(&self) -> Signature { + Signature::build("random bool") + .named( + "bias", + SyntaxShape::Number, + "Adjusts the probability of a \"true\" outcome", + Some('b'), + ) + .category(Category::Random) + } + + fn usage(&self) -> &str { + "Generate a random boolean value" + } + + fn run( + &self, + engine_state: &EngineState, + stack: &mut Stack, + call: &Call, + _input: PipelineData, + ) -> Result { + bool(engine_state, stack, call) + } + + fn examples(&self) -> Vec { + vec![ + Example { + description: "Generate a random boolean value", + example: "random bool", + result: None, + }, + Example { + description: "Generate a random boolean value with a 75% chance of \"true\"", + example: "random bool --bias 0.75", + result: None, + }, + ] + } +} + +fn bool( + engine_state: &EngineState, + stack: &mut Stack, + call: &Call, +) -> Result { + let span = call.head; + let bias: Option> = call.get_flag(engine_state, stack, "bias")?; + + let mut probability = 0.5; + + if let Some(prob) = bias { + probability = prob.item; + + let probability_is_valid = (0.0..=1.0).contains(&probability); + + if !probability_is_valid { + return Err(ShellError::InvalidProbability(prob.span)); + } + } + + let mut rng = thread_rng(); + let bool_result: bool = rng.gen_bool(probability); + + Ok(PipelineData::Value(Value::Bool { + val: bool_result, + span, + })) +} + +#[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/command.rs b/crates/nu-command/src/random/command.rs new file mode 100644 index 000000000..f7d0ece19 --- /dev/null +++ b/crates/nu-command/src/random/command.rs @@ -0,0 +1,41 @@ +use nu_engine::get_full_help; +use nu_protocol::{ + ast::Call, + engine::{Command, EngineState, Stack}, + Category, IntoPipelineData, PipelineData, Signature, Value, +}; + +#[derive(Clone)] +pub struct RandomCommand; + +impl Command for RandomCommand { + fn name(&self) -> &str { + "random" + } + + fn signature(&self) -> Signature { + Signature::build("random").category(Category::Random) + } + + fn usage(&self) -> &str { + "Generate a random values." + } + + fn run( + &self, + engine_state: &EngineState, + _stack: &mut Stack, + call: &Call, + _input: PipelineData, + ) -> Result { + Ok(Value::String { + val: get_full_help( + &RandomCommand.signature(), + &RandomCommand.examples(), + engine_state, + ), + span: call.head, + } + .into_pipeline_data()) + } +} diff --git a/crates/nu-command/src/random/mod.rs b/crates/nu-command/src/random/mod.rs new file mode 100644 index 000000000..1e3cdf0ac --- /dev/null +++ b/crates/nu-command/src/random/mod.rs @@ -0,0 +1,5 @@ +mod bool; +mod command; + +pub use self::bool::SubCommand as Bool; +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 0ee701590..56def77c4 100644 --- a/crates/nu-protocol/src/shell_error.rs +++ b/crates/nu-protocol/src/shell_error.rs @@ -76,6 +76,10 @@ pub enum ShellError { #[diagnostic(code(nu::shell::external_commands), url(docsrs))] ExternalNotSupported(#[label = "external not supported"] Span), + #[error("Invalid Probability.")] + #[diagnostic(code(nu::shell::invalid_probability), url(docsrs))] + InvalidProbability(#[label = "invalid probability"] Span), + #[error("Internal error: {0}.")] #[diagnostic(code(nu::shell::internal_error), url(docsrs))] InternalError(String), diff --git a/crates/nu-protocol/src/signature.rs b/crates/nu-protocol/src/signature.rs index 6908d9253..0b6b74015 100644 --- a/crates/nu-protocol/src/signature.rs +++ b/crates/nu-protocol/src/signature.rs @@ -42,6 +42,7 @@ pub enum Category { Filters, Formats, Math, + Random, Platform, Shells, Strings, @@ -63,6 +64,7 @@ impl std::fmt::Display for Category { Category::Filters => "filters", Category::Formats => "formats", Category::Math => "math", + Category::Random => "random", Category::Platform => "platform", Category::Shells => "shells", Category::Strings => "strings",