Lines
100 %
Functions
83.33 %
Branches
//! `:`-driven command palette. Parses a typed command string into
//! (path, args), looks the path up in the shared [`cli_core`] command
//! tree, and returns the runnable leaf the event layer should execute.
//!
//! Grammar (matches the TUI's prior single-pane mode so automation and
//! interactive grammar agree):
//! ```text
//! word (word)* (key=value)*
//! ```
//! `word` tokens identify the path (e.g. `reports balance`). `key=value`
//! tokens become arguments. Values with embedded spaces are not
//! currently supported — this matches the existing CLI grammar.
use cli_core::{CommandNode, find_leaf};
#[derive(Debug, PartialEq, Eq)]
pub struct PaletteQuery {
pub path: Vec<String>,
pub args: Vec<(String, String)>,
}
/// Parse `input` into (path, args). Empty input yields an empty query.
#[must_use]
pub fn parse(input: &str) -> PaletteQuery {
let mut path = Vec::new();
let mut args = Vec::new();
for token in input.split_whitespace() {
if let Some((k, v)) = token.split_once('=') {
args.push((k.to_string(), v.to_string()));
} else {
path.push(token.to_string());
PaletteQuery { path, args }
/// Resolve a parsed query against the command tree. Returns the leaf
/// node's name and its argument list if the path is complete, or
/// `None` when the path refers to a group without a runnable command.
pub fn resolve<'a>(tree: &'a [CommandNode], query: &PaletteQuery) -> Option<&'a CommandNode> {
let path_refs: Vec<&str> = query.path.iter().map(String::as_str).collect();
find_leaf(tree, &path_refs)
#[cfg(test)]
mod tests {
use super::*;
use cli_core::command_tree;
#[test]
fn parses_empty_input_to_empty_query() {
let q = parse("");
assert!(q.path.is_empty());
assert!(q.args.is_empty());
fn parses_single_word_path() {
let q = parse("version");
assert_eq!(q.path, vec!["version"]);
fn parses_nested_path() {
let q = parse("reports balance");
assert_eq!(q.path, vec!["reports", "balance"]);
fn parses_key_value_arguments() {
let q = parse("reports balance from=2026-01-01 to=2026-04-30 chart=bar");
assert_eq!(q.args.len(), 3);
assert_eq!(q.args[0], ("from".to_string(), "2026-01-01".to_string()));
assert_eq!(q.args[2], ("chart".to_string(), "bar".to_string()));
fn resolves_version_leaf() {
let tree = command_tree();
let leaf = resolve(&tree, &q).expect("version resolves");
assert_eq!(leaf.name, "version");
assert!(leaf.command.is_some());
fn resolves_reports_balance_leaf() {
let q = parse("reports balance from=2026-01-01");
let leaf = resolve(&tree, &q).expect("reports balance resolves");
assert_eq!(leaf.name, "balance");
fn rejects_unknown_path() {
let q = parse("nope");
assert!(resolve(&tree, &q).is_none());
fn rejects_group_without_command() {
let q = parse("reports");