1#[derive(Debug, Clone, PartialEq, Eq)]
11pub struct ParsedLine {
12 pub head: String,
13 pub args: Vec<String>,
14}
15
16impl ParsedLine {
17 #[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 #[must_use]
29 pub fn is_empty(&self) -> bool {
30 self.head.is_empty()
31 }
32}
33
34#[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}