1use nu_engine::command_prelude::*;
2use nu_protocol::Range;
3use rand::random_range;
4use std::ops::Bound;
5
6#[derive(Clone)]
7pub struct RandomInt;
8
9impl Command for RandomInt {
10 fn name(&self) -> &str {
11 "random int"
12 }
13
14 fn signature(&self) -> Signature {
15 Signature::build("random int")
16 .input_output_types(vec![(Type::Nothing, Type::Int)])
17 .allow_variants_without_examples(true)
18 .optional(
19 "range",
20 SyntaxShape::Range,
21 "Range of potential values, inclusive of both start and end values.",
22 )
23 .category(Category::Random)
24 }
25
26 fn description(&self) -> &str {
27 "Generate a random integer [min..max]."
28 }
29
30 fn search_terms(&self) -> Vec<&str> {
31 vec!["generate", "natural", "number"]
32 }
33
34 fn run(
35 &self,
36 engine_state: &EngineState,
37 stack: &mut Stack,
38 call: &Call,
39 _input: PipelineData,
40 ) -> Result<PipelineData, ShellError> {
41 integer(engine_state, stack, call)
42 }
43
44 fn examples(&self) -> Vec<Example> {
45 vec![
46 Example {
47 description: "Generate a non-negative random integer",
48 example: "random int",
49 result: None,
50 },
51 Example {
52 description: "Generate a random integer between 0 (inclusive) and 500 (inclusive)",
53 example: "random int ..500",
54 result: None,
55 },
56 Example {
57 description: "Generate a random integer greater than or equal to 100000",
58 example: "random int 100000..",
59 result: None,
60 },
61 Example {
62 description: "Generate a random integer between -10 (inclusive) and 10 (inclusive)",
63 example: "random int (-10)..10",
64 result: None,
65 },
66 ]
67 }
68}
69
70fn integer(
71 engine_state: &EngineState,
72 stack: &mut Stack,
73 call: &Call,
74) -> Result<PipelineData, ShellError> {
75 let span = call.head;
76 let range: Option<Spanned<Range>> = call.opt(engine_state, stack, 0)?;
77
78 match range {
79 Some(range) => {
80 let range_span = range.span;
81 match range.item {
82 Range::IntRange(range) => {
83 if range.step() < 0 {
84 return Err(ShellError::InvalidRange {
85 left_flank: range.start().to_string(),
86 right_flank: match range.end() {
87 Bound::Included(end) | Bound::Excluded(end) => end.to_string(),
88 Bound::Unbounded => "".into(),
89 },
90 span: range_span,
91 });
92 }
93
94 let value = match range.end() {
95 Bound::Included(end) => random_range(range.start()..=end),
96 Bound::Excluded(end) => random_range(range.start()..end),
97 Bound::Unbounded => random_range(range.start()..=i64::MAX),
98 };
99
100 Ok(PipelineData::Value(Value::int(value, span), None))
101 }
102 Range::FloatRange(_) => Err(ShellError::UnsupportedInput {
103 msg: "float range".into(),
104 input: "value originates from here".into(),
105 msg_span: call.head,
106 input_span: range.span,
107 }),
108 }
109 }
110 None => Ok(PipelineData::Value(
111 Value::int(random_range(0..=i64::MAX), span),
112 None,
113 )),
114 }
115}
116
117#[cfg(test)]
118mod test {
119 use super::*;
120
121 #[test]
122 fn test_examples() {
123 use crate::test_examples;
124
125 test_examples(RandomInt {})
126 }
127}