command_engine/shared/
instruction.rs

1use std::collections::HashMap;
2use crate::Error;
3
4const FLAG_PREFIX: &str = "--";
5const SYMBOL_SPACE: char = ' ';
6const SYMBOL_QUOTES: char = '"';
7const SYMBOL_HASH: char = '#';
8
9#[derive(Default, Debug)]
10struct State<'a> {
11    buffer: Vec<&'a str>,
12    start: Option<usize>,
13    end: Option<usize>,
14    ignore_space: bool,
15    collecting: bool,
16    previous: Option<char>,
17}
18
19impl<'a> State<'a> {
20    fn push_part(&mut self, pos: usize, input: &'a str) {
21        if self.start.is_none() {
22            return;
23        }
24
25        let start = self.start.take().unwrap();
26        let end = *self.end.insert(pos);
27        let part = &input[start..end];
28
29        self.buffer.push(part);
30    }
31}
32
33/// Arguments format structure used to deserialize raw inputs.
34///
35/// The format of instruction is as follows:
36/// ```pseudo
37/// <caller> <arg> --<o_arg> <sub_arg>
38/// ```
39///
40/// Where:
41/// - `caller` - Command caller, used to determine which command to use.
42/// - `arg` - Positional argument where the position matters.
43/// - `o_arg` - Optional argument. These arguments can be placed in any order.
44/// - `sub_arg` - Sub argument that is a child of `o_arg`.
45///
46/// These can also be chained:
47/// ```pseudo
48/// <caller> <arg> <arg> --<o_arg> <sub_arg> <sub_arg> --<o_arg>
49/// ```
50#[derive(Debug, Eq, PartialEq)]
51pub struct Instruction<'a> {
52    pub caller: &'a str,
53    pub args: Vec<&'a str>,
54    pub o_args: HashMap<&'a str, Option<Vec<&'a str>>>,
55    pub input: &'a str,
56}
57
58impl<'a> Instruction<'a> {
59    pub fn new<T: AsRef<str> + ?Sized>(input: &'a T) -> Result<Instruction<'a>, Error> {
60        let mut instruction = Self::empty();
61        instruction.input = input.as_ref();
62
63        let input = instruction.input;
64
65        let mut state = State::default();
66        for (pos, char) in input.chars().enumerate() {
67            if state.collecting || state.ignore_space || char != SYMBOL_SPACE {
68                if !state.collecting && char == SYMBOL_QUOTES {
69                    if state.previous == Some(SYMBOL_HASH) {
70                        state.collecting = true;
71                    } else {
72                        if state.ignore_space {
73                            state.push_part(pos, input);
74                            state.ignore_space = false
75                        } else {
76                            state.ignore_space = true;
77                        }
78                    }
79                } else {
80                    if state.collecting && char == SYMBOL_HASH && state.previous == Some(SYMBOL_QUOTES) {
81                        state.collecting = false;
82                        state.start = state.start.map(|pos| pos+2);
83                        state.push_part(pos-1, input);
84                    } else {
85                        if state.start.is_none() {
86                            state.start = Some(pos);
87                        } else {
88                            let _ = state.end.insert(pos);
89                        }
90                    }
91                }
92            } else {
93                state.push_part(pos, input);
94            }
95
96            state.previous = Some(char);
97        }
98
99        if let Some(start) = state.start {
100            if let Some(end) = state.end {
101                let part = &input[start..=end];
102                if !part.is_empty() {
103                    state.buffer.push(part);
104                }
105            }
106        }
107
108        let mut split = state.buffer.into_iter();
109
110        if let Some(part) = split.next() {
111            instruction.caller = part;
112        } else {
113            return Err(Error::InstructionMissingCaller);
114        }
115
116        let mut current_o_arg = "#";
117        let mut is_pos_args = true;
118        for part in split {
119            if is_pos_args {
120                if part.starts_with(FLAG_PREFIX) {
121                    is_pos_args = false;
122                } else {
123                    instruction.args.push(part);
124                }
125            }
126
127            if !is_pos_args {
128                if part.starts_with(FLAG_PREFIX) {
129                    instruction.o_args.insert(part, None);
130                    current_o_arg = part;
131                } else {
132                    let sub_args = instruction
133                        .o_args
134                        .get_mut(current_o_arg)
135                        .ok_or_else(|| Error::InstructionSubArgWithoutOArg)?;
136
137                    if let Some(sub_args) = sub_args {
138                        sub_args.push(part);
139                    } else {
140                        *sub_args = Some(vec![part]);
141                    }
142                }
143            }
144        }
145
146        Ok(instruction)
147    }
148
149    fn empty() -> Instruction<'a> {
150        Self {
151            caller: "",
152            args: Vec::new(),
153            o_args: HashMap::new(),
154            input: "",
155        }
156    }
157}
158
159#[cfg(test)]
160impl Default for Instruction<'_> {
161    fn default() -> Self {
162        Self::empty()
163    }
164}