about summary refs log tree commit diff
path: root/pkgs/profpatsch/execline/el_semicolon.rs
blob: 9dc08d9b5ae03b9d0e23fc7d9c68e8f5513a6911 (plain) (blame)
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

/// Every element inside the block starts with a space
const BLOCK_QUOTE_CHAR : u8 = b' ';
/// The end of a block is signified by an empty string
const BLOCK_END : &'static [u8] = &[];

/// A parsed execline argument.
#[derive(Debug, PartialEq, Eq)]
enum Arg<'a> {
    /// Normal argument.
    Arg(&'a [u8]),
    /// A block.
    ///
    /// On the command line a block is represented
    /// by a list of arguments which start with a space
    /// and end with an empty string.
    Block(Vec<&'a [u8]>)
}

#[derive(Debug, PartialEq, Eq)]
enum Error {
    /// The argument was not quoted, at index.
    UnquotedArgument(usize),
    /// The last block was not terminated
    UnterminatedBlock
}

/// Parse a command line into a list of `Arg`s.
///
/// Blocks can be nested by adding more spaces,
/// but `el_semicolon` will only parse one level.
/// Usually that is intended, because nested blocks
/// are intended to be parsed by nested programs.
fn el_semicolon<'a>(args: &'a [&'a [u8]]) -> Result<Vec<Arg<'a>>, Error> {
    let mut cur_block : Option<Vec<&'a [u8]>> = None;
    let mut res : Vec<Arg<'a>> = vec![];
    for (i, arg) in args.iter().enumerate() {
        if arg == &BLOCK_END {
            let bl = cur_block.take();
            match bl {
                None => res.push(Arg::Arg(arg)),
                Some(bl) => res.push(Arg::Block(bl))
            }
        } else {
            match arg[0] {
                BLOCK_QUOTE_CHAR => {
                    let new = &arg[1..];
                    cur_block = Some(cur_block.map_or_else(
                        || vec![new],
                        |mut bl| { bl.push(new); bl }
                    ))
                },
                _ => {
                    if cur_block != None {
                        return Err(Error::UnquotedArgument(i));
                    }
                    res.push(Arg::Arg(arg))
                }
            }
        }
    }
    if cur_block != None {
        Err(Error::UnterminatedBlock)
    } else {
        Ok(res)
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn success() {
        assert_eq!(
            el_semicolon(&vec![
                "-b".as_bytes(),
                " echo".as_bytes(),
                " hi".as_bytes(),
                "".as_bytes(),
                "test".as_bytes(),
                "".as_bytes(),
            ]),
            Ok(vec![
                Arg::Arg("-b".as_bytes()),
                Arg::Block(vec![
                    "echo".as_bytes(),
                    "hi".as_bytes(),
                ]),
                Arg::Arg("test".as_bytes()),
                Arg::Arg("".as_bytes()),
            ])
        )
    }

    #[test]
    fn unquoted_argument() {
        assert_eq!(
            el_semicolon(&vec![
                "-b".as_bytes(),
                " echo".as_bytes(),
                "hi".as_bytes(),
                "".as_bytes(),
                "test".as_bytes(),
                "".as_bytes(),
            ]),
            Err(Error::UnquotedArgument(2))
        );
        assert_eq!(
            el_semicolon(&vec![
                " -b".as_bytes(),
                " echo".as_bytes(),
                "".as_bytes(),
                " test".as_bytes(),
                "a".as_bytes(),
            ]),
            Err(Error::UnquotedArgument(4))
        )
    }

    #[test]
    fn unterminated_block() {
        assert_eq!(
            el_semicolon(&vec![
                "-b".as_bytes(),
                " echo".as_bytes(),
            ]),
            Err(Error::UnterminatedBlock)
        )
    }
}