#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Command {
pub name: String,
pub args: Vec<String>,
pub raw_args: String,
}
#[derive(Debug, thiserror::Error)]
pub enum ParseError {
#[error("input does not start with '/'")]
NotACommand,
#[error("empty command after slash")]
EmptyCommandName,
#[error("unterminated quoted argument")]
UnterminatedQuote,
}
pub fn parse_command(input: &str) -> Result<Command, ParseError> {
if !input.starts_with('/') {
return Err(ParseError::NotACommand);
}
let name_start = input[1..]
.char_indices()
.find(|(_, c)| !c.is_whitespace())
.map(|(i, _)| 1 + i)
.unwrap_or(input.len());
if name_start >= input.len() {
return Err(ParseError::EmptyCommandName);
}
let name_end = input[name_start..]
.char_indices()
.find(|(_, c)| c.is_whitespace())
.map(|(i, _)| name_start + i)
.unwrap_or(input.len());
let name = input[name_start..name_end].to_string();
let raw_args = if name_end < input.len() {
let after_name = &input[name_end..];
let skip = after_name.chars().next().map_or(1, |c| c.len_utf8());
input[name_end + skip..].to_string()
} else {
String::new()
};
let args = if raw_args.is_empty() {
Vec::new()
} else {
shlex::split(&raw_args).ok_or(ParseError::UnterminatedQuote)?
};
Ok(Command {
name,
args,
raw_args,
})
}