diff --git a/Cargo.lock b/Cargo.lock index 03b0741023..849858d633 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1495,6 +1495,7 @@ dependencies = [ "toml", "trash", "unicode-segmentation", + "uuid", ] [[package]] @@ -2780,6 +2781,15 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "936e4b492acfd135421d8dca4b1aa80a7bfc26e702ef3af710e0752684df5372" +[[package]] +name = "uuid" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc5cf98d8186244414c848017f0e2676b3fcb46807f6668a97dfe67359a3c4b7" +dependencies = [ + "getrandom 0.2.3", +] + [[package]] name = "version_check" version = "0.1.5" diff --git a/crates/nu-command/Cargo.toml b/crates/nu-command/Cargo.toml index c603d77761..6c6c48eb61 100644 --- a/crates/nu-command/Cargo.toml +++ b/crates/nu-command/Cargo.toml @@ -47,6 +47,7 @@ roxmltree = "0.14.0" rand = "0.8" trash = { version = "1.3.0", optional = true } unicode-segmentation = "1.8.0" +uuid = { version = "0.8.2", features = ["v4"] } lazy_static = "1.4.0" strip-ansi-escapes = "0.1.1" crossterm = "0.22.1" diff --git a/crates/nu-command/src/random/integer.rs b/crates/nu-command/src/random/integer.rs new file mode 100644 index 0000000000..9750fa1f94 --- /dev/null +++ b/crates/nu-command/src/random/integer.rs @@ -0,0 +1,104 @@ +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, 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 integer" + } + + fn signature(&self) -> Signature { + Signature::build("random integer") + .optional("range", SyntaxShape::Range, "Range of values") + .category(Category::Random) + } + + fn usage(&self) -> &str { + "Generate a random integer [min..max]" + } + + fn run( + &self, + engine_state: &EngineState, + stack: &mut Stack, + call: &Call, + _input: PipelineData, + ) -> Result { + integer(engine_state, stack, call) + } + + fn examples(&self) -> Vec { + vec![ + Example { + description: "Generate an unconstrained random integer", + example: "random integer", + result: None, + }, + Example { + description: "Generate a random integer less than or equal to 500", + example: "random integer ..500", + result: None, + }, + Example { + description: "Generate a random integer greater than or equal to 100000", + example: "random integer 100000..", + result: None, + }, + Example { + description: "Generate a random integer between 1 and 10", + example: "random integer 1..10", + result: None, + }, + ] + } +} + +fn integer( + 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_integer()?, r.to.as_integer()?) + } else { + (0, i64::MAX) + }; + + 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::Int { val: min, span }, None)), + _ => { + let mut thread_rng = thread_rng(); + let result: i64 = thread_rng.gen_range(min..max); + + Ok(PipelineData::Value(Value::Int { val: result, span }, 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 b13a0834e0..0e36df2d71 100644 --- a/crates/nu-command/src/random/mod.rs +++ b/crates/nu-command/src/random/mod.rs @@ -3,9 +3,13 @@ mod chars; mod command; mod decimal; mod dice; +mod integer; +mod uuid; pub use self::bool::SubCommand as Bool; pub use self::chars::SubCommand as Chars; pub use self::decimal::SubCommand as Decimal; pub use self::dice::SubCommand as Dice; +pub use self::integer::SubCommand as Integer; +pub use self::uuid::SubCommand as Uuid; pub use command::RandomCommand as Random; diff --git a/crates/nu-command/src/random/uuid.rs b/crates/nu-command/src/random/uuid.rs new file mode 100644 index 0000000000..fd7a2929f6 --- /dev/null +++ b/crates/nu-command/src/random/uuid.rs @@ -0,0 +1,61 @@ +use nu_protocol::ast::Call; +use nu_protocol::engine::{Command, EngineState, Stack}; +use nu_protocol::{Category, Example, PipelineData, ShellError, Signature, Value}; +use uuid::Uuid; + +#[derive(Clone)] +pub struct SubCommand; + +impl Command for SubCommand { + fn name(&self) -> &str { + "random uuid" + } + + fn signature(&self) -> Signature { + Signature::build("random uuid").category(Category::Random) + } + + fn usage(&self) -> &str { + "Generate a random uuid4 string" + } + + fn run( + &self, + _engine_state: &EngineState, + _stack: &mut Stack, + call: &Call, + _input: PipelineData, + ) -> Result { + uuid(call) + } + + fn examples(&self) -> Vec { + vec![Example { + description: "Generate a random uuid4 string", + example: "random uuid", + result: None, + }] + } +} + +fn uuid(call: &Call) -> Result { + let span = call.head; + let uuid_4 = Uuid::new_v4().to_hyphenated().to_string(); + + Ok(PipelineData::Value( + Value::String { val: uuid_4, span }, + None, + )) +} + +#[cfg(test)] +mod test { + use super::*; + + #[test] + fn test_examples() { + use crate::test_examples; + + test_examples(SubCommand {}) + } +} diff --git a/crates/nu-protocol/src/value/mod.rs b/crates/nu-protocol/src/value/mod.rs index 1f2dd519db..6abaf6e151 100644 --- a/crates/nu-protocol/src/value/mod.rs +++ b/crates/nu-protocol/src/value/mod.rs @@ -214,6 +214,17 @@ impl Value { } } + pub fn as_integer(&self) -> Result { + match self { + Value::Int { 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 {