Skip to main content

zero_commands/
parse.rs

1//! Command-line parser.
2//!
3//! Input is a single line typed at the prompt. We lex it into a
4//! head word (the command name, optionally prefixed with `/`) and
5//! a tail of whitespace-separated tokens. Quoting is deliberately
6//! not supported — operator commands are terse by design, and
7//! anything that needs a sentence belongs in the conversation
8//! stream, not in a slash-command.
9
10#[derive(Debug, Clone, PartialEq, Eq)]
11pub struct ParsedLine {
12    pub head: String,
13    pub args: Vec<String>,
14}
15
16impl ParsedLine {
17    /// Lowercased, prefix-stripped head. `/BRIEF` and `brief` both
18    /// parse to `"brief"`.
19    #[must_use]
20    pub fn canonical_head(&self) -> String {
21        self.head
22            .strip_prefix('/')
23            .unwrap_or(&self.head)
24            .to_ascii_lowercase()
25    }
26
27    /// `true` if the input is empty or whitespace-only.
28    #[must_use]
29    pub fn is_empty(&self) -> bool {
30        self.head.is_empty()
31    }
32}
33
34/// Tokenize an operator input line.
35#[must_use]
36pub fn parse_line(line: &str) -> ParsedLine {
37    let mut tokens = line.split_whitespace();
38    let head = tokens.next().unwrap_or_default().to_string();
39    let args = tokens.map(str::to_string).collect();
40    ParsedLine { head, args }
41}
42
43#[cfg(test)]
44mod tests {
45    use super::parse_line;
46
47    #[test]
48    fn empty_input() {
49        let p = parse_line("   ");
50        assert!(p.is_empty());
51        assert_eq!(p.canonical_head(), "");
52    }
53
54    #[test]
55    fn slash_prefix_is_optional() {
56        assert_eq!(parse_line("/help").canonical_head(), "help");
57        assert_eq!(parse_line("help").canonical_head(), "help");
58    }
59
60    #[test]
61    fn case_insensitive_head() {
62        assert_eq!(parse_line("/BRIEF").canonical_head(), "brief");
63    }
64
65    #[test]
66    fn splits_args() {
67        let p = parse_line("/break 15 minutes");
68        assert_eq!(p.canonical_head(), "break");
69        assert_eq!(p.args, ["15", "minutes"]);
70    }
71}