use std::fmt;
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum DemoCommand {
Show,
Json,
Input { field: String, value: String },
Click { action_id: String },
Setup { provider: Option<String> },
Back,
Quit,
Help,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum CommandParseError {
Unknown(String),
InvalidFormat(String),
}
impl fmt::Display for CommandParseError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
CommandParseError::Unknown(command) => {
write!(f, "unknown command '{}'; try @show or @help", command)
}
CommandParseError::InvalidFormat(message) => write!(f, "{message}"),
}
}
}
impl std::error::Error for CommandParseError {}
pub fn parse_command(line: &str) -> Result<DemoCommand, CommandParseError> {
let trimmed = line.trim();
if !trimmed.starts_with('@') {
return Err(CommandParseError::InvalidFormat(
"commands must start with '@'".to_string(),
));
}
let mut parts = trimmed[1..].trim().splitn(2, char::is_whitespace);
let name = parts.next().unwrap_or_default().to_ascii_lowercase();
let rest = parts.next().unwrap_or("").trim();
match name.as_str() {
"show" => Ok(DemoCommand::Show),
"json" => Ok(DemoCommand::Json),
"back" => Ok(DemoCommand::Back),
"help" => Ok(DemoCommand::Help),
"quit" => Ok(DemoCommand::Quit),
"setup" => Ok(DemoCommand::Setup {
provider: if rest.is_empty() {
None
} else {
Some(rest.to_string())
},
}),
"click" => {
if rest.is_empty() {
Err(CommandParseError::InvalidFormat(
"@click requires an action id".to_string(),
))
} else {
Ok(DemoCommand::Click {
action_id: rest.to_string(),
})
}
}
"input" => {
if rest.is_empty() {
return Err(CommandParseError::InvalidFormat(
"@input requires <field>=<value>".to_string(),
));
}
let mut kv = rest.splitn(2, '=');
let field = kv.next().unwrap_or("").trim();
let value = kv.next().unwrap_or("").trim();
if field.is_empty() || value.is_empty() {
return Err(CommandParseError::InvalidFormat(
"@input requires a field and a value separated by '='".to_string(),
));
}
Ok(DemoCommand::Input {
field: field.to_string(),
value: value.to_string(),
})
}
unknown => Err(CommandParseError::Unknown(unknown.to_string())),
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn parses_input_command() {
let command = parse_command("@input foo=bar").unwrap();
assert_eq!(
command,
DemoCommand::Input {
field: "foo".into(),
value: "bar".into()
}
);
}
#[test]
fn parses_click_command() {
let command = parse_command("@click submit").unwrap();
assert_eq!(
command,
DemoCommand::Click {
action_id: "submit".into()
}
);
}
#[test]
fn handles_unknown_command() {
let err = parse_command("@foo").unwrap_err();
assert!(matches!(err, CommandParseError::Unknown(_)));
}
#[test]
fn rejects_invalid_input_format() {
let err = parse_command("@input missing").unwrap_err();
assert!(matches!(err, CommandParseError::InvalidFormat(_)));
}
#[test]
fn parses_back_command() {
let command = parse_command("@back").unwrap();
assert_eq!(command, DemoCommand::Back);
}
}