1use core::iter::FusedIterator;
18
19use crate::parse::{Char, Quote, StrChars, StrIndex, StrRange};
20
21pub struct SplitArgs<'a> {
27 iter: StrChars<'a>,
28}
29
30impl<'a> SplitArgs<'a> {
31 #[must_use]
37 pub const fn new(cmdline: &'a str) -> Self {
38 Self {
39 iter: StrChars::new(cmdline),
40 }
41 }
42
43 fn get_range(&self, start: StrIndex, end: StrIndex) -> &'a str {
49 let range = StrRange { start, end };
50 range.get(self.iter.get()).expect("range should be valid")
51 }
52}
53
54impl<'a> Iterator for SplitArgs<'a> {
55 type Item = &'a str;
56
57 fn next(&mut self) -> Option<Self::Item> {
58 loop {
59 let c = self.iter.peek()?;
60
61 match c {
62 Char::Whitespace => {
63 self.iter.advance();
64 }
65
66 Char::Letter(_) => {
67 let start = self.iter.pos();
68 self.iter.advance();
69
70 while let Some(c) = self.iter.peek() {
71 match c {
72 Char::Letter(_) | Char::Quote(_) => {
73 self.iter.advance();
74 }
75
76 Char::Whitespace => {
77 let end = self.iter.pos();
78 self.iter.advance();
79
80 return Some(self.get_range(start, end));
83 }
84 }
85 }
86
87 return Some(self.get_range(start, self.iter.pos()));
90 }
91
92 Char::Quote(Quote::Single) => {
93 self.iter.advance();
94 let start = self.iter.pos();
95
96 while let Some(c) = self.iter.peek() {
97 match c {
98 Char::Letter(_) | Char::Whitespace | Char::Quote(Quote::Double) => {
99 self.iter.advance();
100 }
101
102 Char::Quote(Quote::Single) => {
103 let end = self.iter.pos();
104 self.iter.advance();
105
106 return Some(self.get_range(start, end));
109 }
110 }
111 }
112
113 return Some(self.get_range(start, self.iter.pos()));
116 }
117
118 Char::Quote(Quote::Double) => {
119 self.iter.advance();
120 let start = self.iter.pos();
121
122 while let Some(c) = self.iter.peek() {
123 match c {
124 Char::Letter(_) | Char::Whitespace | Char::Quote(Quote::Single) => {
125 self.iter.advance();
126 }
127
128 Char::Quote(Quote::Double) => {
129 let end = self.iter.pos();
130 self.iter.advance();
131
132 return Some(self.get_range(start, end));
135 }
136 }
137 }
138
139 return Some(self.get_range(start, self.iter.pos()));
142 }
143 }
144 }
145 }
146}
147
148impl FusedIterator for SplitArgs<'_> {}
149
150#[cfg(test)]
151mod tests {
152 use super::*;
153
154 macro_rules! test {
155 ($test:ident: $cmdline:expr => [ $($arg:expr),* ]) => {
156 #[test]
157 fn $test() {
158 let mut parsed = SplitArgs::new($cmdline);
159 $(
160 assert_eq!(parsed.next(), Some($arg));
161 )*
162 assert_eq!(parsed.next(), None);
163 }
164 };
165 }
166
167 test!(basic: "string" => ["string"]);
168 test!(two: "string1 string2" => ["string1", "string2"]);
169 test!(single_quotes: "string1 'string2 string3' string4" => ["string1", "string2 string3", "string4"]);
170 test!(double_quotes: "string1 \"string2 string3\" string4" => ["string1", "string2 string3", "string4"]);
171 test!(single_quotes_two: "'1 2' '3 4'" => ["1 2", "3 4"]);
172 test!(double_quotes_two: "\"1 2\" \"3 4\"" => ["1 2", "3 4"]);
173 test!(unterminated_single_quotes: "1 \"2 3 4" => ["1", "2 3 4"]);
174 test!(unterminated_double_quotes: "1 '2 3 4" => ["1", "2 3 4"]);
175 test!(other_whitespace: "1\t2\n3 4\r5" => ["1", "2", "3", "4", "5"]);
176 test!(non_ascii: "string1 rusty🦀 party🎉time string2" => ["string1", "rusty🦀", "party🎉time", "string2"]);
177 test!(non_ascii_basic: "strÄng" => ["strÄng"]);
178 test!(non_ascii_two: "sträng1 sträng2" => ["sträng1", "sträng2"]);
179 test!(non_acsii_quotes: "\"sträng1 sträng2\"" => ["sträng1 sträng2"]);
180}