1use std::ffi::OsStr;
14
15use winnow::stream::{BStr, Location, Needed, Offset, SliceLen, Stream, StreamIsPartial};
16
17pub fn words<S: AsRef<OsStr>>(args: &[S]) -> Vec<&BStr> {
19 args.iter()
20 .map(|arg| BStr::new(arg.as_ref().as_encoded_bytes()))
21 .collect()
22}
23
24#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
26#[non_exhaustive]
27pub enum Mode {
28 Word,
30 Bundle,
33 Stopped,
35 Values,
38}
39
40#[derive(Copy, Clone, PartialEq, Eq)]
45pub struct Argv<'i> {
46 words: &'i [&'i BStr],
48 skip: u32,
50 remaining: u32,
52 total: u32,
53 mode: Mode,
54 plus: bool,
57}
58
59const _: () = assert!(std::mem::size_of::<Argv<'static>>() <= 32);
62
63impl<'i> Argv<'i> {
64 pub fn new(words: &'i [&'i BStr]) -> Self {
66 let total: usize = words.iter().map(|w| w.len() + 1).sum();
67 let total = u32::try_from(total).expect("a command line under 4 GiB");
69 Self {
70 words,
71 skip: 0,
72 remaining: total,
73 total,
74 mode: Mode::Word,
75 plus: false,
76 }
77 }
78
79 #[inline(always)]
81 pub fn is_empty(&self) -> bool {
82 self.words.is_empty()
83 }
84
85 #[inline(always)]
87 pub fn words_left(&self) -> usize {
88 self.words.len()
89 }
90
91 #[inline(always)]
93 pub fn mode(&self) -> Mode {
94 self.mode
95 }
96
97 #[inline]
101 pub fn stop_flags(&mut self) {
102 if self.mode == Mode::Word {
103 self.mode = Mode::Values;
104 }
105 }
106
107 #[inline]
110 pub fn resume_flags(&mut self) {
111 if matches!(self.mode, Mode::Stopped | Mode::Values) {
112 self.mode = Mode::Word;
113 }
114 }
115
116 #[inline(always)]
117 pub(crate) fn set_mode(&mut self, mode: Mode) {
118 self.mode = mode;
119 }
120
121 #[inline(always)]
123 pub(crate) fn plus(&self) -> bool {
124 self.plus
125 }
126
127 #[inline(always)]
128 pub(crate) fn set_plus(&mut self, plus: bool) {
129 self.plus = plus;
130 }
131
132 #[inline(always)]
134 pub fn offset(&self) -> usize {
135 (self.total - self.remaining) as usize
136 }
137
138 #[inline(always)]
140 pub fn front(&self) -> &'i [u8] {
141 match self.words.first() {
142 Some(word) => &word[self.skip as usize..],
143 None => &[],
144 }
145 }
146
147 #[inline(always)]
149 pub(crate) fn take_bytes(&mut self, n: usize) {
150 debug_assert!(n < self.front().len() + 1);
151 self.skip += n as u32;
152 self.remaining -= n as u32;
153 }
154
155 #[inline(always)]
157 pub(crate) fn take_word(&mut self) -> &'i BStr {
158 let rest = self.front();
159 self.remaining -= rest.len() as u32 + 1;
160 self.words = &self.words[1..];
161 self.skip = 0;
162 if self.mode == Mode::Bundle {
163 self.mode = Mode::Word;
164 }
165 BStr::new(rest)
166 }
167
168 fn words_in(&self, offset: usize) -> usize {
170 let mut end = 0;
171 for (n, (at, _)) in self.iter_offsets().enumerate() {
172 if at == offset {
173 return n;
174 }
175 end = at;
176 }
177 assert_eq!(
178 offset, self.remaining as usize,
179 "offset {offset} is inside a word (last start {end})"
180 );
181 self.words.len()
182 }
183}
184
185impl std::fmt::Debug for Argv<'_> {
186 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
187 let mut words: Vec<&BStr> = self.words.to_vec();
188 if let Some(first) = words.first_mut() {
189 *first = BStr::new(&first[self.skip as usize..]);
190 }
191 f.debug_struct("Argv")
192 .field("mode", &self.mode)
193 .field("words", &words)
194 .finish()
195 }
196}
197
198#[derive(Clone, Debug)]
200pub struct IterOffsets<'i> {
201 words: std::slice::Iter<'i, &'i BStr>,
202 skip: usize,
203 offset: usize,
204}
205
206impl<'i> Iterator for IterOffsets<'i> {
207 type Item = (usize, &'i BStr);
208
209 fn next(&mut self) -> Option<Self::Item> {
210 let word = BStr::new(&self.words.next()?[self.skip..]);
211 self.skip = 0;
212 let at = self.offset;
213 self.offset += word.len() + 1;
214 Some((at, word))
215 }
216}
217
218impl Offset for Argv<'_> {
219 #[inline(always)]
220 fn offset_from(&self, start: &Self) -> usize {
221 (start.remaining - self.remaining) as usize
222 }
223}
224
225impl SliceLen for Argv<'_> {
226 #[inline(always)]
227 fn slice_len(&self) -> usize {
228 self.remaining as usize
229 }
230}
231
232impl<'i> Stream for Argv<'i> {
235 type Token = &'i BStr;
236 type Slice = &'i [&'i BStr];
237 type IterOffsets = IterOffsets<'i>;
238 type Checkpoint = Self;
239
240 #[inline(always)]
241 fn iter_offsets(&self) -> Self::IterOffsets {
242 IterOffsets {
243 words: self.words.iter(),
244 skip: self.skip as usize,
245 offset: 0,
246 }
247 }
248 #[inline(always)]
249 fn eof_offset(&self) -> usize {
250 self.remaining as usize
251 }
252 #[inline(always)]
253 fn next_token(&mut self) -> Option<Self::Token> {
254 if self.is_empty() {
255 None
256 } else {
257 Some(self.take_word())
258 }
259 }
260 #[inline(always)]
261 fn peek_token(&self) -> Option<Self::Token> {
262 (!self.is_empty()).then(|| BStr::new(self.front()))
263 }
264 fn offset_for<P>(&self, predicate: P) -> Option<usize>
265 where
266 P: Fn(Self::Token) -> bool,
267 {
268 self.iter_offsets()
269 .find(|&(_, word)| predicate(word))
270 .map(|(at, _)| at)
271 }
272 fn offset_at(&self, tokens: usize) -> Result<usize, Needed> {
273 let mut iter = self.iter_offsets();
274 match iter.nth(tokens) {
275 Some((at, _)) => Ok(at),
276 None if tokens == self.words.len() => Ok(self.remaining as usize),
277 None => Err(Needed::new(tokens - self.words.len())),
278 }
279 }
280 fn next_slice(&mut self, offset: usize) -> Self::Slice {
281 let slice = self.peek_slice(offset);
282 for _ in 0..slice.len() {
283 self.take_word();
284 }
285 slice
286 }
287 fn peek_slice(&self, offset: usize) -> Self::Slice {
288 assert!(
289 self.skip == 0 || offset == 0,
290 "cannot slice whole words inside a bundle"
291 );
292 &self.words[..self.words_in(offset)]
293 }
294 #[inline(always)]
295 fn checkpoint(&self) -> Self::Checkpoint {
296 *self
297 }
298 #[inline(always)]
299 fn reset(&mut self, checkpoint: &Self::Checkpoint) {
300 *self = *checkpoint;
301 }
302 fn trace(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
303 write!(f, "{self:?}")
304 }
305}
306
307impl StreamIsPartial for Argv<'_> {
308 type PartialState = ();
309
310 #[inline(always)]
311 fn complete(&mut self) -> Self::PartialState {}
312 #[inline(always)]
313 fn restore_partial(&mut self, _state: Self::PartialState) {}
314 #[inline(always)]
315 fn is_partial_supported() -> bool {
316 false
317 }
318}
319
320impl Location for Argv<'_> {
321 #[inline(always)]
322 fn previous_token_end(&self) -> usize {
323 self.offset()
324 }
325 #[inline(always)]
326 fn current_token_start(&self) -> usize {
327 self.offset()
328 }
329}
330
331#[cfg(test)]
332mod tests {
333 use winnow::combinator::repeat;
334 use winnow::prelude::*;
335 use winnow::token::{any, take};
336
337 use super::*;
338
339 fn line<'a>(words: &'a [&'a str]) -> Vec<&'a BStr> {
340 words.iter().map(BStr::new).collect()
341 }
342
343 #[test]
344 fn generic_token_parsers_see_whole_words() {
345 let words = line(&["a b", "", "c"]);
346 let mut input = Argv::new(&words);
347 assert_eq!(
348 any::<_, crate::Error>.parse_next(&mut input).unwrap(),
349 "a b"
350 );
351 assert_eq!(input.offset(), 4);
352 let rest: &[&BStr] = take::<_, _, crate::Error>(2usize)
353 .parse_next(&mut input)
354 .unwrap();
355 assert_eq!(rest, &words[1..]);
356 assert!(input.is_empty());
357 }
358
359 #[test]
360 fn a_flag_matches_beside_a_word_holding_any_byte() {
361 let words = [BStr::new(b"--verbose"), BStr::new(b"a\0\xff")];
362 let mut input = Argv::new(&words);
363 let flag = crate::token::arg(&mut input).unwrap();
364 assert!(matches!(flag, crate::token::Arg::Long(f) if f.name == b"verbose"));
365 let word = crate::token::arg(&mut input).unwrap();
366 assert!(matches!(word, crate::token::Arg::Word(w) if w.value == &b"a\0\xff"[..]));
367 }
368
369 #[test]
370 fn a_multibyte_short_is_one_letter() {
371 let words = [BStr::new("-β".as_bytes())];
372 let mut input = Argv::new(&words);
373 let flag = crate::token::arg(&mut input).unwrap();
374 assert!(matches!(flag, crate::token::Arg::Short(f) if f.letter == 'β'));
375 assert!(input.is_empty());
376 }
377
378 #[test]
379 fn reading_a_letter_counts_as_progress() {
380 let words = line(&["-abc"]);
381 let mut input = Argv::new(&words);
382 let letters: Vec<_> = repeat(0.., crate::token::arg)
383 .parse_next(&mut input)
384 .unwrap();
385 assert_eq!(letters.len(), 3);
386 assert!(input.is_empty());
387 }
388
389 #[test]
390 fn checkpoint_restores_the_position_inside_a_bundle() {
391 let words = line(&["-ab", "x"]);
392 let mut input = Argv::new(&words);
393 crate::token::arg(&mut input).unwrap();
394 let saved = input.checkpoint();
395 crate::token::arg(&mut input).unwrap();
396 crate::token::arg(&mut input).unwrap();
397 input.reset(&saved);
398 assert_eq!((input.mode(), input.front()), (Mode::Bundle, &b"b"[..]));
399 }
400}