use crate::{Arguments, CommandParserConfig};
#[derive(Clone, Debug)]
#[non_exhaustive]
pub struct Command<'a> {
pub arguments: Arguments<'a>,
pub name: &'a str,
pub prefix: &'a str,
}
#[derive(Clone, Debug)]
pub struct Parser<'a> {
config: CommandParserConfig<'a>,
}
impl<'a> Parser<'a> {
pub fn new(config: impl Into<CommandParserConfig<'a>>) -> Self {
Self {
config: config.into(),
}
}
pub const fn config(&self) -> &CommandParserConfig<'a> {
&self.config
}
pub fn config_mut(&mut self) -> &mut CommandParserConfig<'a> {
&mut self.config
}
pub fn parse(&'a self, buf: &'a str) -> Option<Command<'a>> {
let prefix = self.find_prefix(buf)?;
self.parse_with_prefix(prefix, buf)
}
pub fn parse_with_prefix(&'a self, prefix: &'a str, buf: &'a str) -> Option<Command<'a>> {
if !buf.starts_with(prefix) {
return None;
}
let mut idx = prefix.len();
let command_buf = buf.get(idx..)?;
let command = self.find_command(command_buf)?;
idx += command.len();
idx += command_buf.len() - command_buf.trim_start().len();
Some(Command {
arguments: Arguments::new(buf.get(idx..)?),
name: command,
prefix,
})
}
fn find_command(&'a self, buf: &'a str) -> Option<&'a str> {
let buf = buf.split_whitespace().next()?;
self.config.commands.iter().find_map(|command| {
if command == buf {
Some(command.as_ref())
} else {
None
}
})
}
fn find_prefix(&self, buf: &str) -> Option<&str> {
self.config.prefixes.iter().find_map(|prefix| {
if buf.starts_with(prefix.as_ref()) {
Some(prefix.as_ref())
} else {
None
}
})
}
}
impl<'a, T: Into<CommandParserConfig<'a>>> From<T> for Parser<'a> {
fn from(config: T) -> Self {
Self::new(config)
}
}
#[cfg(test)]
mod tests {
use crate::{Command, CommandParserConfig, Parser};
use static_assertions::{assert_fields, assert_impl_all};
use std::fmt::Debug;
assert_fields!(Command<'_>: arguments, name, prefix);
assert_impl_all!(Command<'_>: Clone, Debug, Send, Sync);
assert_impl_all!(Parser<'_>: Clone, Debug, Send, Sync);
fn simple_config() -> Parser<'static> {
let mut config = CommandParserConfig::new();
config.add_prefix("!");
config.add_command("echo", false);
Parser::new(config)
}
#[test]
fn double_command() {
let parser = simple_config();
assert!(parser.parse("!echoecho").is_none(), "double match");
}
#[test]
fn test_case_sensitive() {
let mut parser = simple_config();
let message_ascii = "!EcHo this is case insensitive";
let message_unicode = "!wEiSS is white";
let message_unicode_2 = "!\u{3b4} is delta";
let command = parser
.parse(message_ascii)
.expect("Parser is case sensitive");
assert_eq!(
"echo", command.name,
"Command name should have the same case as in the CommandParserConfig"
);
parser.config.add_command("wei\u{df}", false);
let command = parser
.parse(message_unicode)
.expect("Parser is case sensitive");
assert_eq!(
"wei\u{df}", command.name,
"Command name should have the same case as in the CommandParserConfig"
);
parser.config.add_command("\u{394}", false);
let command = parser
.parse(message_unicode_2)
.expect("Parser is case sensitive");
assert_eq!(
"\u{394}", command.name,
"Command name should have the same case as in the CommandParserConfig"
);
let config = parser.config_mut();
config.commands.clear();
config.add_command("echo", true);
config.add_command("wei\u{df}", true);
config.add_command("\u{394}", true);
assert!(
parser.parse(message_ascii).is_none(),
"Parser is not case sensitive"
);
assert!(
parser.parse(message_unicode).is_none(),
"Parser is not case sensitive"
);
assert!(
parser.parse(message_unicode_2).is_none(),
"Parser is not case sensitive"
);
}
#[test]
fn test_simple_config_no_prefix() {
let mut parser = simple_config();
parser.config_mut().remove_prefix("!");
}
#[test]
fn test_simple_config_parser() {
let parser = simple_config();
match parser.parse("!echo what a test") {
Some(Command { name, prefix, .. }) => {
assert_eq!("echo", name);
assert_eq!("!", prefix);
}
other => panic!("Not command: {:?}", other),
}
}
#[test]
fn test_unicode_command() {
let mut parser = simple_config();
parser.config_mut().add_command("\u{1f44e}", false);
assert!(parser.parse("!\u{1f44e}").is_some());
}
#[test]
fn test_unicode_prefix() {
let mut parser = simple_config();
parser.config_mut().add_prefix("\u{1f44d}");
assert!(parser.parse("\u{1f44d}echo foo").is_some());
}
#[test]
fn test_dynamic_prefix() {
let parser = simple_config();
let command = parser.parse_with_prefix("=", "=echo foo").unwrap();
assert_eq!("=", command.prefix);
assert_eq!("echo", command.name);
}
#[test]
fn test_prefix_mention() {
let mut config = CommandParserConfig::new();
config.add_prefix("foo");
config.add_command("dump", false);
let parser = Parser::new(config);
let Command {
mut arguments,
name,
prefix,
} = parser.parse("foo dump test").unwrap();
assert_eq!("foo", prefix);
assert_eq!("dump", name);
assert_eq!(Some("test"), arguments.next());
assert!(arguments.next().is_none());
}
}