nu_command/generators/
seq_char.rs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
use nu_engine::command_prelude::*;

#[derive(Clone)]
pub struct SeqChar;

impl Command for SeqChar {
    fn name(&self) -> &str {
        "seq char"
    }

    fn description(&self) -> &str {
        "Print a sequence of ASCII characters."
    }

    fn signature(&self) -> Signature {
        Signature::build("seq char")
            .input_output_types(vec![(Type::Nothing, Type::List(Box::new(Type::String)))])
            .required(
                "start",
                SyntaxShape::String,
                "Start of character sequence (inclusive).",
            )
            .required(
                "end",
                SyntaxShape::String,
                "End of character sequence (inclusive).",
            )
            .category(Category::Generators)
    }

    fn examples(&self) -> Vec<Example> {
        vec![
            Example {
                description: "sequence a to e",
                example: "seq char a e",
                result: Some(Value::list(
                    vec![
                        Value::test_string('a'),
                        Value::test_string('b'),
                        Value::test_string('c'),
                        Value::test_string('d'),
                        Value::test_string('e'),
                    ],
                    Span::test_data(),
                )),
            },
            Example {
                description: "Sequence a to e, and join the characters with a pipe",
                example: "seq char a e | str join '|'",
                // TODO: it would be nice to test this example, but it currently breaks the input/output type tests
                // result: Some(Value::test_string("a|b|c|d|e")),
                result: None,
            },
        ]
    }

    fn run(
        &self,
        engine_state: &EngineState,
        stack: &mut Stack,
        call: &Call,
        _input: PipelineData,
    ) -> Result<PipelineData, ShellError> {
        seq_char(engine_state, stack, call)
    }
}

fn is_single_character(ch: &str) -> bool {
    ch.is_ascii() && (ch.len() == 1)
}

fn seq_char(
    engine_state: &EngineState,
    stack: &mut Stack,
    call: &Call,
) -> Result<PipelineData, ShellError> {
    let start: Spanned<String> = call.req(engine_state, stack, 0)?;
    let end: Spanned<String> = call.req(engine_state, stack, 1)?;

    if !is_single_character(&start.item) {
        return Err(ShellError::GenericError {
            error: "seq char only accepts individual ASCII characters as parameters".into(),
            msg: "input should be a single ASCII character".into(),
            span: Some(start.span),
            help: None,
            inner: vec![],
        });
    }

    if !is_single_character(&end.item) {
        return Err(ShellError::GenericError {
            error: "seq char only accepts individual ASCII characters as parameters".into(),
            msg: "input should be a single ASCII character".into(),
            span: Some(end.span),
            help: None,
            inner: vec![],
        });
    }

    let start = start
        .item
        .chars()
        .next()
        // expect is ok here, because we just checked the length
        .expect("seq char input must contains 2 inputs");

    let end = end
        .item
        .chars()
        .next()
        // expect is ok here, because we just checked the length
        .expect("seq char input must contains 2 inputs");

    let span = call.head;
    run_seq_char(start, end, span)
}

fn run_seq_char(start_ch: char, end_ch: char, span: Span) -> Result<PipelineData, ShellError> {
    let start = start_ch as u8;
    let end = end_ch as u8;
    let range = if start <= end {
        start..=end
    } else {
        end..=start
    };
    let result_vec = if start <= end {
        range.map(|c| (c as char).to_string()).collect::<Vec<_>>()
    } else {
        range
            .rev()
            .map(|c| (c as char).to_string())
            .collect::<Vec<_>>()
    };
    let result = result_vec
        .into_iter()
        .map(|x| Value::string(x, span))
        .collect::<Vec<Value>>();
    Ok(Value::list(result, span).into_pipeline_data())
}
#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_examples() {
        use crate::test_examples;

        test_examples(SeqChar {})
    }
}