Skip to main content

nu_command/generators/
seq_char.rs

1use nu_engine::command_prelude::*;
2use nu_protocol::shell_error::generic::GenericError;
3
4#[derive(Clone)]
5pub struct SeqChar;
6
7impl Command for SeqChar {
8    fn name(&self) -> &str {
9        "seq char"
10    }
11
12    fn description(&self) -> &str {
13        "Print a sequence of ASCII characters."
14    }
15
16    fn signature(&self) -> Signature {
17        Signature::build("seq char")
18            .input_output_types(vec![(Type::Nothing, Type::List(Box::new(Type::String)))])
19            .required(
20                "start",
21                SyntaxShape::String,
22                "Start of character sequence (inclusive).",
23            )
24            .required(
25                "end",
26                SyntaxShape::String,
27                "End of character sequence (inclusive).",
28            )
29            .category(Category::Generators)
30    }
31
32    fn examples(&self) -> Vec<Example<'_>> {
33        vec![
34            Example {
35                description: "sequence a to e",
36                example: "seq char a e",
37                result: Some(Value::list(
38                    vec![
39                        Value::test_string('a'),
40                        Value::test_string('b'),
41                        Value::test_string('c'),
42                        Value::test_string('d'),
43                        Value::test_string('e'),
44                    ],
45                    Span::test_data(),
46                )),
47            },
48            Example {
49                description: "Sequence a to e, and join the characters with a pipe",
50                example: "seq char a e | str join '|'",
51                // TODO: it would be nice to test this example, but it currently breaks the input/output type tests
52                // result: Some(Value::test_string("a|b|c|d|e")),
53                result: None,
54            },
55        ]
56    }
57
58    fn run(
59        &self,
60        engine_state: &EngineState,
61        stack: &mut Stack,
62        call: &Call,
63        _input: PipelineData,
64    ) -> Result<PipelineData, ShellError> {
65        seq_char(engine_state, stack, call)
66    }
67}
68
69fn is_single_character(ch: &str) -> bool {
70    ch.is_ascii() && (ch.len() == 1)
71}
72
73fn seq_char(
74    engine_state: &EngineState,
75    stack: &mut Stack,
76    call: &Call,
77) -> Result<PipelineData, ShellError> {
78    let start: Spanned<String> = call.req(engine_state, stack, 0)?;
79    let end: Spanned<String> = call.req(engine_state, stack, 1)?;
80
81    if !is_single_character(&start.item) {
82        return Err(ShellError::Generic(GenericError::new(
83            "seq char only accepts individual ASCII characters as parameters",
84            "input should be a single ASCII character",
85            start.span,
86        )));
87    }
88
89    if !is_single_character(&end.item) {
90        return Err(ShellError::Generic(GenericError::new(
91            "seq char only accepts individual ASCII characters as parameters",
92            "input should be a single ASCII character",
93            end.span,
94        )));
95    }
96
97    let start = start
98        .item
99        .chars()
100        .next()
101        // expect is ok here, because we just checked the length
102        .expect("seq char input must contains 2 inputs");
103
104    let end = end
105        .item
106        .chars()
107        .next()
108        // expect is ok here, because we just checked the length
109        .expect("seq char input must contains 2 inputs");
110
111    let span = call.head;
112    run_seq_char(start, end, span)
113}
114
115fn run_seq_char(start_ch: char, end_ch: char, span: Span) -> Result<PipelineData, ShellError> {
116    let start = start_ch as u8;
117    let end = end_ch as u8;
118    let range = if start <= end {
119        start..=end
120    } else {
121        end..=start
122    };
123    let result_vec = if start <= end {
124        range.map(|c| (c as char).to_string()).collect::<Vec<_>>()
125    } else {
126        range
127            .rev()
128            .map(|c| (c as char).to_string())
129            .collect::<Vec<_>>()
130    };
131    let result = result_vec
132        .into_iter()
133        .map(|x| Value::string(x, span))
134        .collect::<Vec<Value>>();
135    Ok(Value::list(result, span).into_pipeline_data())
136}
137#[cfg(test)]
138mod tests {
139    use super::*;
140
141    #[test]
142    fn test_examples() -> nu_test_support::Result {
143        nu_test_support::test().examples(SeqChar)
144    }
145}