aboutsummaryrefslogtreecommitdiff
path: root/src/util/rest_vec.rs
blob: 82889cd46418dd4aff5947c256b966338562dae6 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
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)))
    }
}