1use cli_core::{CommandNode, find_leaf};
17
18#[derive(Debug, PartialEq, Eq)]
19pub struct PaletteQuery {
20 pub path: Vec<String>,
21 pub args: Vec<(String, String)>,
22}
23
24#[must_use]
26pub fn parse(input: &str) -> PaletteQuery {
27 let mut path = Vec::new();
28 let mut args = Vec::new();
29 for token in input.split_whitespace() {
30 if let Some((k, v)) = token.split_once('=') {
31 args.push((k.to_string(), v.to_string()));
32 } else {
33 path.push(token.to_string());
34 }
35 }
36 PaletteQuery { path, args }
37}
38
39#[must_use]
43pub fn resolve<'a>(tree: &'a [CommandNode], query: &PaletteQuery) -> Option<&'a CommandNode> {
44 let path_refs: Vec<&str> = query.path.iter().map(String::as_str).collect();
45 find_leaf(tree, &path_refs)
46}
47
48#[cfg(test)]
49mod tests {
50 use super::*;
51 use cli_core::command_tree;
52
53 #[test]
54 fn parses_empty_input_to_empty_query() {
55 let q = parse("");
56 assert!(q.path.is_empty());
57 assert!(q.args.is_empty());
58 }
59
60 #[test]
61 fn parses_single_word_path() {
62 let q = parse("version");
63 assert_eq!(q.path, vec!["version"]);
64 assert!(q.args.is_empty());
65 }
66
67 #[test]
68 fn parses_nested_path() {
69 let q = parse("reports balance");
70 assert_eq!(q.path, vec!["reports", "balance"]);
71 }
72
73 #[test]
74 fn parses_key_value_arguments() {
75 let q = parse("reports balance from=2026-01-01 to=2026-04-30 chart=bar");
76 assert_eq!(q.path, vec!["reports", "balance"]);
77 assert_eq!(q.args.len(), 3);
78 assert_eq!(q.args[0], ("from".to_string(), "2026-01-01".to_string()));
79 assert_eq!(q.args[2], ("chart".to_string(), "bar".to_string()));
80 }
81
82 #[test]
83 fn resolves_version_leaf() {
84 let tree = command_tree();
85 let q = parse("version");
86 let leaf = resolve(&tree, &q).expect("version resolves");
87 assert_eq!(leaf.name, "version");
88 assert!(leaf.command.is_some());
89 }
90
91 #[test]
92 fn resolves_reports_balance_leaf() {
93 let tree = command_tree();
94 let q = parse("reports balance from=2026-01-01");
95 let leaf = resolve(&tree, &q).expect("reports balance resolves");
96 assert_eq!(leaf.name, "balance");
97 assert!(leaf.command.is_some());
98 }
99
100 #[test]
101 fn rejects_unknown_path() {
102 let tree = command_tree();
103 let q = parse("nope");
104 assert!(resolve(&tree, &q).is_none());
105 }
106
107 #[test]
108 fn rejects_group_without_command() {
109 let tree = command_tree();
110 let q = parse("reports");
111 assert!(resolve(&tree, &q).is_none());
112 }
113}