aboutsummaryrefslogtreecommitdiff
path: root/src/util/rest_vec.rs
diff options
context:
space:
mode:
Diffstat (limited to 'src/util/rest_vec.rs')
-rw-r--r--src/util/rest_vec.rs84
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)))
+ }
+}