twilight_command_parser/
arguments.rs

1use std::{
2    fmt::{Debug, Formatter, Result as FmtResult},
3    str::CharIndices,
4};
5
6/// An iterator over command arguments.
7#[derive(Clone)]
8pub struct Arguments<'a> {
9    buf: &'a str,
10    indices: CharIndices<'a>,
11    idx: usize,
12}
13
14impl<'a> Arguments<'a> {
15    /// Returns a new iterator of arguments from a buffer.
16    pub fn new(buf: &'a str) -> Self {
17        Self {
18            buf: buf.trim(),
19            indices: buf.trim().char_indices(),
20            idx: 0,
21        }
22    }
23
24    /// Returns a view of the underlying buffer of arguments.
25    ///
26    /// This is exactly like [`std::str::Chars::as_str`].
27    ///
28    /// # Examples
29    ///
30    /// When the command is `"!echo foo bar baz"` and the command is `"echo"`,
31    /// then this returns `"foo bar baz"`.
32    ///
33    /// ```
34    /// use twilight_command_parser::{Command, CommandParserConfig, Parser};
35    ///
36    /// let mut config = CommandParserConfig::new();
37    /// config.add_prefix("!");
38    /// config.add_command("echo", false);
39    /// let parser = Parser::new(config);
40    ///
41    /// if let Some(Command { arguments, .. }) = parser.parse("!echo foo bar baz") {
42    ///     assert_eq!("foo bar baz", arguments.as_str());
43    /// }
44    /// # else { panic!("Not command match"); }
45    /// ```
46    pub const fn as_str(&self) -> &'a str {
47        self.buf
48    }
49
50    /// Returns the remainder of the buffer that hasn't been parsed.
51    ///
52    /// # Examples
53    ///
54    /// If you have extracted two arguments already, then you can consume the
55    /// rest of the arguments:
56    ///
57    /// ```
58    /// use twilight_command_parser::Arguments;
59    ///
60    /// let mut args = Arguments::new("1 2 3 4 5");
61    /// assert_eq!(Some("1"), args.next());
62    /// assert_eq!(Some("2"), args.next());
63    /// assert_eq!(Some("3 4 5"), args.into_remainder());
64    /// ```
65    pub fn into_remainder(self) -> Option<&'a str> {
66        self.buf.get(self.idx..)
67    }
68}
69
70impl<'a> Debug for Arguments<'a> {
71    fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
72        f.debug_struct("Arguments")
73            .field("buf", &self.buf)
74            .field("idx", &self.idx)
75            .finish()
76    }
77}
78
79impl<'a> Iterator for Arguments<'a> {
80    type Item = &'a str;
81
82    // todo: clean this up
83    fn next(&mut self) -> Option<Self::Item> {
84        if self.idx > self.buf.len() {
85            return None;
86        }
87
88        let mut start_idx = self.idx;
89        let mut quoted = false;
90        let mut started = false;
91
92        for (i, ch) in &mut self.indices {
93            if quoted {
94                if ch == '"' {
95                    let v = self.buf.get(start_idx..i);
96                    self.idx = i + 1;
97
98                    return v.map(str::trim);
99                }
100            } else if ch == ' ' {
101                if started {
102                    let v = self.buf.get(start_idx..i);
103                    self.idx = i + 1;
104
105                    return v.map(str::trim);
106                }
107                self.idx = i;
108                start_idx = i;
109                started = true;
110                continue;
111            } else if ch == '"' {
112                start_idx = i + 1;
113                quoted = true;
114            }
115
116            self.idx = i;
117            started = true;
118        }
119
120        self.idx = usize::max_value();
121
122        match self.buf.get(start_idx..) {
123            Some("") | None => None,
124            Some(v) => Some(v.trim()),
125        }
126    }
127}
128
129#[allow(clippy::non_ascii_literal)]
130#[cfg(test)]
131mod tests {
132    use super::Arguments;
133    use static_assertions::assert_impl_all;
134    use std::fmt::Debug;
135
136    assert_impl_all!(Arguments<'_>: Clone, Debug, Iterator, Send, Sync);
137
138    #[test]
139    fn test_as_str() {
140        let args = Arguments::new("foo bar baz");
141        assert_eq!("foo bar baz", args.as_str());
142    }
143
144    #[test]
145    fn test_simple_args() {
146        let mut args = Arguments::new("foo bar baz");
147        assert_eq!(Some("foo"), args.next());
148        assert_eq!(Some("bar"), args.next());
149        assert_eq!(Some("baz"), args.next());
150        assert_eq!(None, args.next());
151    }
152
153    #[test]
154    fn test_quoted_args() {
155        let mut args = Arguments::new(r#"this "is a longer argument" here"#);
156        assert_eq!(Some("this"), args.next());
157        assert_eq!(Some("is a longer argument"), args.next());
158        assert_eq!(Some("here"), args.next());
159        assert_eq!(None, args.next());
160    }
161
162    #[test]
163    fn test_quoted_close_args() {
164        let mut args = Arguments::new(r#""kind of weird""but okay"#);
165        assert_eq!(Some("kind of weird"), args.next());
166        assert_eq!(Some("but okay"), args.next());
167        assert_eq!(None, args.next());
168    }
169
170    #[test]
171    fn test_unicode_chars_1() {
172        let mut args = Arguments::new("๐“’๐“ข๐“ nice try");
173        assert_eq!(Some("๐“’๐“ข๐“"), args.next());
174        assert_eq!(Some("nice"), args.next());
175        assert_eq!(Some("try"), args.next());
176        assert_eq!(None, args.next());
177    }
178
179    #[test]
180    fn test_unicode_chars_2() {
181        let mut args = Arguments::new("Saighdiรบr rรฉalta what even");
182        assert_eq!(Some("Saighdiรบr"), args.next());
183        assert_eq!(Some("rรฉalta"), args.next());
184        assert_eq!(Some("what"), args.next());
185        assert_eq!(Some("even"), args.next());
186        assert_eq!(None, args.next());
187    }
188
189    #[test]
190    fn test_quoted_unicode_chars() {
191        let mut args = Arguments::new(r#""๐“’๐“ข๐“ | CSA" amazing try"#);
192        assert_eq!(Some("๐“’๐“ข๐“ | CSA"), args.next());
193        assert_eq!(Some("amazing"), args.next());
194        assert_eq!(Some("try"), args.next());
195        assert_eq!(None, args.next());
196    }
197
198    #[test]
199    fn test_emote() {
200        let mut args = Arguments::new("why an emote ๐Ÿ™ƒ");
201        assert_eq!(Some("why"), args.next());
202        assert_eq!(Some("an"), args.next());
203        assert_eq!(Some("emote"), args.next());
204        assert_eq!(Some("๐Ÿ™ƒ"), args.next());
205        assert_eq!(None, args.next());
206    }
207
208    #[test]
209    fn test_quoted_emote() {
210        let mut args = Arguments::new(r#"omg "๐Ÿ˜• - ๐Ÿ˜Ÿ" kewl"#);
211        assert_eq!(Some("omg"), args.next());
212        assert_eq!(Some("๐Ÿ˜• - ๐Ÿ˜Ÿ"), args.next());
213        assert_eq!(Some("kewl"), args.next());
214        assert_eq!(None, args.next());
215    }
216}