nu_command/generators/
seq_char.rs

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