Skip to main content

tsu_editor/
command.rs

1#[derive(Debug, PartialEq)]
2/// Enumeration of possible command flags
3///
4/// Represents command modifier flags. At the moment, the only modifier is the Force flag ("!")
5pub enum CommandFlag {
6    /// The modifier flag which tells the command parser that the command should be executed with
7    /// force, typically bypassing certain safeguards
8    Force,
9}
10
11#[derive(Debug, Default, PartialEq)]
12/// Representation of a tsu command
13///
14/// A command that has been parsed from user input in Command mode
15pub struct ParsedCommand {
16    /// The list of commands to execute
17    pub commands: Vec<String>,
18    /// The list of arguments that correspond with the provided commands
19    pub args: Vec<String>,
20    /// Modifier flags
21    pub flags: Vec<CommandFlag>,
22}
23
24impl ParsedCommand {
25    #[must_use]
26    /// Checks if the command has the Force modifier
27    ///
28    /// # Returns
29    /// - `true` if the command has the Force modifier, `false` if otherwise
30    pub fn is_forced(&self) -> bool {
31        self.flags.contains(&CommandFlag::Force)
32    }
33}
34
35#[must_use]
36/// Attempt to parse a command
37///
38/// # Arguments
39/// - `commands`:
40/// - `input`:
41///
42/// # Returns
43/// - `Some(ParsedCommand)` if parsing is successful, otherwise `None`
44pub fn parse(commands: &[&str], input: &str) -> Option<ParsedCommand> {
45    let (flags, input) = parse_flags(input);
46    let mut parts = input.splitn(2, ' ');
47    let input = parts.next()?;
48    let args = parts
49        .next()
50        .map(|s| s.split(' ').map(ToString::to_string).collect())
51        .unwrap_or_default();
52    let commands = parse_commands(commands, input);
53
54    if commands.is_empty() {
55        return None;
56    }
57
58    Some(ParsedCommand {
59        commands,
60        args,
61        flags,
62    })
63}
64
65/// Check for flags and parse them appropriately
66///
67/// # Arguments
68/// - `input`:
69///
70/// # Returns
71/// - A tuple consisting of a vector of command flags as well as the remaining input string
72fn parse_flags(input: &str) -> (Vec<CommandFlag>, &str) {
73    if let Some(input) = input.strip_suffix("!") {
74        (vec![CommandFlag::Force], input)
75    } else {
76        (vec![], input)
77    }
78}
79
80/// Parse commands given user input
81///
82/// # Arguments
83/// - `commands`:
84/// - `input`:
85///
86/// # Returns
87/// - A vector of parsed commands represented as strings
88fn parse_commands(commands: &[&str], input: &str) -> Vec<String> {
89    for command in commands {
90        if &input == command {
91            return vec![(*command).to_string()];
92        }
93    }
94
95    let mut result = Vec::new();
96    for c in input.chars() {
97        if let Some(command) = commands.iter().find(|cmd| cmd.starts_with(c)) {
98            result.push((*command).to_string());
99        }
100    }
101
102    result
103}
104
105#[cfg(test)]
106mod test {
107    use super::*;
108
109    #[test]
110    fn test_parse() {
111        let commands = ["quit", "write"];
112        assert_eq!(
113            parse(&commands, "quit"),
114            Some(ParsedCommand {
115                commands: vec!["quit".to_string()],
116                flags: vec![],
117                ..Default::default()
118            })
119        );
120        assert_eq!(
121            parse(&commands, "q"),
122            Some(ParsedCommand {
123                commands: vec!["quit".to_string()],
124                flags: vec![],
125                ..Default::default()
126            })
127        );
128        assert_eq!(
129            parse(&commands, "q!"),
130            Some(ParsedCommand {
131                commands: vec!["quit".to_string()],
132                flags: vec![CommandFlag::Force],
133                ..Default::default()
134            })
135        );
136        assert_eq!(
137            parse(&commands, "wq"),
138            Some(ParsedCommand {
139                commands: vec!["write".to_string(), "quit".to_string()],
140                flags: vec![],
141                ..Default::default()
142            })
143        );
144        assert_eq!(
145            parse(&commands, "wq!"),
146            Some(ParsedCommand {
147                commands: vec!["write".to_string(), "quit".to_string()],
148                flags: vec![CommandFlag::Force],
149                ..Default::default()
150            })
151        );
152    }
153
154    #[test]
155    fn test_parse_command() {
156        let commands = ["quit", "write"];
157        assert_eq!(parse_commands(&commands, "quit"), vec!["quit"]);
158        assert_eq!(parse_commands(&commands, "q"), vec!["quit"]);
159        assert_eq!(parse_commands(&commands, "w"), vec!["write"]);
160        assert_eq!(parse_commands(&commands, "wq"), vec!["write", "quit"]);
161    }
162
163    #[test]
164    fn test_parse_flags() {
165        assert_eq!(parse_flags("q"), (vec![], "q"));
166        assert_eq!(parse_flags("q!"), (vec![CommandFlag::Force], "q"));
167    }
168}