use std::error::Error; use serenity::all::{ Context, Message, }; /// Pop a whitespace-separated word from the front of the arguments. Supports quotes and quote /// escaping. /// /// Leading whitespace will be trimmed; trailing whitespace is not consumed. // From https://github.com/serenity-rs/poise/blob/current/src/prefix_argument/mod.rs pub fn pop_string(args: &str) -> Result<(&str, String), poise::TooFewArguments> { // TODO: consider changing the behavior to parse quotes literally if they're in the middle // of the string: // - `"hello world"` => `hello world` // - `"hello "world"` => `"hello "world` // - `"hello" world"` => `hello` let args = args.trim_start(); if args.is_empty() { return Err(poise::TooFewArguments::default()); } let mut output = String::new(); let mut inside_string = false; let mut escaping = false; let mut chars = args.chars(); // .clone().next() is poor man's .peek(), but we can't do peekable because then we can't // call as_str on the Chars iterator while let Some(c) = chars.clone().next() { if escaping { output.push(c); escaping = false; } else if !inside_string && c.is_whitespace() { break; } else if c == '"' { inside_string = !inside_string; } else if c == '\\' { escaping = true; } else { output.push(c); } chars.next(); } Ok((chars.as_str(), output)) } pub struct RestVec(Vec); impl RestVec { #[inline] pub fn into_inner(self) -> Vec { self.0 } } impl From for Vec { #[inline] fn from(value: RestVec) -> Self { value.0 } } #[poise::async_trait] impl<'a> poise::PopArgument<'a> for RestVec { async fn pop_from( mut args: &'a str, attachment_index: usize, _ctx: &Context, _msg: &Message, ) -> Result<(&'a str, usize, Self), (Box, Option)> { let mut v = vec![]; while let Ok((remaining, s)) = pop_string(args) { args = remaining; v.push(s); } Ok(("", attachment_index, Self(v))) } }