diff options
Diffstat (limited to 'src/util/rest_vec.rs')
| -rw-r--r-- | src/util/rest_vec.rs | 84 |
1 files changed, 84 insertions, 0 deletions
diff --git a/src/util/rest_vec.rs b/src/util/rest_vec.rs new file mode 100644 index 0000000..82889cd --- /dev/null +++ b/src/util/rest_vec.rs @@ -0,0 +1,84 @@ +use serenity::all::{ + Context, + Message, +}; +use std::error::Error; + +/// 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 +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<String>); + +impl RestVec { + #[inline] + pub fn into_inner(self) -> Vec<String> { + self.0 + } +} + +impl From<RestVec> for Vec<String> { + #[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<dyn Error + Send + Sync>, Option<String>)> { + let mut v = vec![]; + + while let Ok((remaining, s)) = pop_string(args) { + args = remaining; + v.push(s); + } + + Ok(("", attachment_index, Self(v))) + } +} |
