pub enum CliCommand {
Quit,
Clear,
CustomCommand(String), InvokeSkill(String, Option<String>), ColonCommand(String), FileRef(String), Message(String),
}
pub fn parse_input(line: &str) -> Option<CliCommand> {
let trimmed = line.trim();
if trimmed.is_empty() {
return None;
}
if trimmed.starts_with('$') && trimmed.len() > 1 {
let mut parts = trimmed[1..].split_whitespace();
let skill_name = parts.next().unwrap_or("");
if !skill_name.is_empty() {
let trailing = parts.collect::<Vec<_>>().join(" ");
let trailing = if trailing.is_empty() {
None
} else {
Some(trailing)
};
return Some(CliCommand::InvokeSkill(skill_name.to_string(), trailing));
}
}
if trimmed.starts_with(':') && trimmed.len() > 1 {
let command = trimmed[1..].trim();
if !command.is_empty() {
return Some(CliCommand::ColonCommand(command.to_string()));
}
}
if trimmed.starts_with('@') && trimmed.len() > 1 && !trimmed.contains(char::is_whitespace) {
return Some(CliCommand::FileRef(trimmed[1..].to_string()));
}
if trimmed.starts_with('/') && trimmed.len() > 1 {
let command_name = trimmed[1..].split_whitespace().next().unwrap_or("");
if !command_name.is_empty() && command_name != "q" && command_name != "c" {
return Some(CliCommand::CustomCommand(trimmed[1..].to_string()));
}
}
match trimmed {
"/q" | "exit" | "quit" => Some(CliCommand::Quit),
"/c" | "clear" => Some(CliCommand::Clear),
msg => Some(CliCommand::Message(msg.to_string())),
}
}
#[cfg(test)]
mod tests {
use super::CliCommand;
use super::parse_input;
#[test]
fn parse_quit_commands() {
assert!(matches!(parse_input("/q"), Some(CliCommand::Quit)));
assert!(matches!(parse_input("exit"), Some(CliCommand::Quit)));
assert!(matches!(parse_input("quit"), Some(CliCommand::Quit)));
}
#[test]
fn parse_clear_commands() {
assert!(matches!(parse_input("/c"), Some(CliCommand::Clear)));
assert!(matches!(parse_input("clear"), Some(CliCommand::Clear)));
}
#[test]
fn parse_message_commands() {
assert!(matches!(parse_input("hello"), Some(CliCommand::Message(_))));
}
#[test]
fn parse_custom_commands() {
assert!(matches!(
parse_input("/refactor"),
Some(CliCommand::CustomCommand(_))
));
assert!(matches!(
parse_input("/lint --fix"),
Some(CliCommand::CustomCommand(_))
));
}
#[test]
fn ignore_empty_input() {
assert!(parse_input("").is_none());
assert!(parse_input(" ").is_none());
}
#[test]
fn parse_skill_invocation() {
assert!(matches!(
parse_input("$rust-testing"),
Some(CliCommand::InvokeSkill(_, _))
));
if let Some(CliCommand::InvokeSkill(name, _)) = parse_input("$rust-testing") {
assert_eq!(name, "rust-testing");
}
}
#[test]
fn parse_skill_invocation_ignores_trailing_text() {
if let Some(CliCommand::InvokeSkill(name, trailing)) = parse_input("$rust-testing help me")
{
assert_eq!(name, "rust-testing");
assert_eq!(trailing, Some("help me".to_string()));
}
}
#[test]
fn parse_empty_skill_name() {
assert!(matches!(parse_input("$"), Some(CliCommand::Message(_))));
}
#[test]
fn parse_colon_command() {
assert!(matches!(
parse_input(":set provider openai"),
Some(CliCommand::ColonCommand(_))
));
}
#[test]
fn parse_file_ref() {
assert!(matches!(
parse_input("@src/main.rs"),
Some(CliCommand::FileRef(_))
));
}
}