twilight_command_parser/
parser.rs1use crate::{Arguments, CommandParserConfig};
2
3#[derive(Clone, Debug)]
5#[non_exhaustive]
6pub struct Command<'a> {
7 pub arguments: Arguments<'a>,
10 pub name: &'a str,
12 pub prefix: &'a str,
14}
15
16#[derive(Clone, Debug)]
56pub struct Parser<'a> {
57 config: CommandParserConfig<'a>,
58}
59
60impl<'a> Parser<'a> {
61 pub fn new(config: impl Into<CommandParserConfig<'a>>) -> Self {
63 Self {
64 config: config.into(),
65 }
66 }
67
68 pub const fn config(&self) -> &CommandParserConfig<'a> {
70 &self.config
71 }
72
73 pub fn config_mut(&mut self) -> &mut CommandParserConfig<'a> {
75 &mut self.config
76 }
77
78 pub fn parse(&'a self, buf: &'a str) -> Option<Command<'a>> {
88 let prefix = self.find_prefix(buf)?;
89 self.parse_with_prefix(prefix, buf)
90 }
91
92 pub fn parse_with_prefix(&'a self, prefix: &'a str, buf: &'a str) -> Option<Command<'a>> {
117 if !buf.starts_with(prefix) {
118 return None;
119 }
120
121 let mut idx = prefix.len();
122 let command_buf = buf.get(idx..)?;
123 let command = self.find_command(command_buf)?;
124
125 idx += command.len();
126
127 idx += command_buf.len() - command_buf.trim_start().len();
130
131 Some(Command {
132 arguments: Arguments::new(buf.get(idx..)?),
133 name: command,
134 prefix,
135 })
136 }
137
138 fn find_command(&'a self, buf: &'a str) -> Option<&'a str> {
139 let buf = buf.split_whitespace().next()?;
140 self.config.commands.iter().find_map(|command| {
141 if command == buf {
142 Some(command.as_ref())
143 } else {
144 None
145 }
146 })
147 }
148
149 fn find_prefix(&self, buf: &str) -> Option<&str> {
150 self.config.prefixes.iter().find_map(|prefix| {
151 if buf.starts_with(prefix.as_ref()) {
152 Some(prefix.as_ref())
153 } else {
154 None
155 }
156 })
157 }
158}
159
160impl<'a, T: Into<CommandParserConfig<'a>>> From<T> for Parser<'a> {
161 fn from(config: T) -> Self {
162 Self::new(config)
163 }
164}
165
166#[cfg(test)]
167mod tests {
168 use crate::{Command, CommandParserConfig, Parser};
169 use static_assertions::{assert_fields, assert_impl_all};
170 use std::fmt::Debug;
171
172 assert_fields!(Command<'_>: arguments, name, prefix);
173 assert_impl_all!(Command<'_>: Clone, Debug, Send, Sync);
174 assert_impl_all!(Parser<'_>: Clone, Debug, Send, Sync);
175
176 fn simple_config() -> Parser<'static> {
177 let mut config = CommandParserConfig::new();
178 config.add_prefix("!");
179 config.add_command("echo", false);
180
181 Parser::new(config)
182 }
183
184 #[test]
185 fn double_command() {
186 let parser = simple_config();
187
188 assert!(parser.parse("!echoecho").is_none(), "double match");
189 }
190
191 #[test]
192 fn test_case_sensitive() {
193 let mut parser = simple_config();
194 let message_ascii = "!EcHo this is case insensitive";
195 let message_unicode = "!wEiSS is white";
196 let message_unicode_2 = "!\u{3b4} is delta";
197
198 let command = parser
200 .parse(message_ascii)
201 .expect("Parser is case sensitive");
202 assert_eq!(
203 "echo", command.name,
204 "Command name should have the same case as in the CommandParserConfig"
205 );
206
207 parser.config.add_command("wei\u{df}", false);
209 let command = parser
210 .parse(message_unicode)
211 .expect("Parser is case sensitive");
212 assert_eq!(
213 "wei\u{df}", command.name,
214 "Command name should have the same case as in the CommandParserConfig"
215 );
216
217 parser.config.add_command("\u{394}", false);
218 let command = parser
219 .parse(message_unicode_2)
220 .expect("Parser is case sensitive");
221 assert_eq!(
222 "\u{394}", command.name,
223 "Command name should have the same case as in the CommandParserConfig"
224 );
225
226 let config = parser.config_mut();
228 config.commands.clear();
229 config.add_command("echo", true);
230 config.add_command("wei\u{df}", true);
231 config.add_command("\u{394}", true);
232 assert!(
233 parser.parse(message_ascii).is_none(),
234 "Parser is not case sensitive"
235 );
236 assert!(
237 parser.parse(message_unicode).is_none(),
238 "Parser is not case sensitive"
239 );
240 assert!(
241 parser.parse(message_unicode_2).is_none(),
242 "Parser is not case sensitive"
243 );
244 }
245
246 #[test]
247 fn test_simple_config_no_prefix() {
248 let mut parser = simple_config();
249 parser.config_mut().remove_prefix("!");
250 }
251
252 #[test]
253 fn test_simple_config_parser() {
254 let parser = simple_config();
255
256 match parser.parse("!echo what a test") {
257 Some(Command { name, prefix, .. }) => {
258 assert_eq!("echo", name);
259 assert_eq!("!", prefix);
260 }
261 other => panic!("Not command: {:?}", other),
262 }
263 }
264
265 #[test]
266 fn test_unicode_command() {
267 let mut parser = simple_config();
268 parser.config_mut().add_command("\u{1f44e}", false);
269
270 assert!(parser.parse("!\u{1f44e}").is_some());
271 }
272
273 #[test]
274 fn test_unicode_prefix() {
275 let mut parser = simple_config();
276 parser.config_mut().add_prefix("\u{1f44d}"); assert!(parser.parse("\u{1f44d}echo foo").is_some());
279 }
280
281 #[test]
282 fn test_dynamic_prefix() {
283 let parser = simple_config();
284
285 let command = parser.parse_with_prefix("=", "=echo foo").unwrap();
286
287 assert_eq!("=", command.prefix);
288 assert_eq!("echo", command.name);
289 }
290
291 #[test]
292 fn test_prefix_mention() {
293 let mut config = CommandParserConfig::new();
294 config.add_prefix("foo");
295 config.add_command("dump", false);
296 let parser = Parser::new(config);
297
298 let Command {
299 mut arguments,
300 name,
301 prefix,
302 } = parser.parse("foo dump test").unwrap();
303 assert_eq!("foo", prefix);
304 assert_eq!("dump", name);
305 assert_eq!(Some("test"), arguments.next());
306 assert!(arguments.next().is_none());
307 }
308}