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 #[doc(hidden)]
110 #[inline]
111 pub fn separate(&mut self) {
112 if self.mode == Mode::Values {
113 self.mode = Mode::Stopped;
114 }
115 }
116
117 #[inline]
120 pub fn resume_flags(&mut self) {
121 if matches!(self.mode, Mode::Stopped | Mode::Values) {
122 self.mode = Mode::Word;
123 }
124 }
125
126 #[inline(always)]
127 pub(crate) fn set_mode(&mut self, mode: Mode) {
128 self.mode = mode;
129 }
130
131 #[inline(always)]
133 pub(crate) fn plus(&self) -> bool {
134 self.plus
135 }
136
137 #[inline(always)]
138 pub(crate) fn set_plus(&mut self, plus: bool) {
139 self.plus = plus;
140 }
141
142 #[inline(always)]
144 pub fn offset(&self) -> usize {
145 (self.total - self.remaining) as usize
146 }
147
148 #[inline(always)]
150 pub fn front(&self) -> &'i [u8] {
151 match self.words.first() {
152 Some(word) => &word[self.skip as usize..],
153 None => &[],
154 }
155 }
156
157 #[inline(always)]
159 pub(crate) fn take_bytes(&mut self, n: usize) {
160 debug_assert!(n < self.front().len() + 1);
161 self.skip += n as u32;
162 self.remaining -= n as u32;
163 }
164
165 #[inline(always)]
167 pub(crate) fn take_word(&mut self) -> &'i BStr {
168 let rest = self.front();
169 self.remaining -= rest.len() as u32 + 1;
170 self.words = &self.words[1..];
171 self.skip = 0;
172 if self.mode == Mode::Bundle {
173 self.mode = Mode::Word;
174 }
175 BStr::new(rest)
176 }
177
178 fn words_in(&self, offset: usize) -> usize {
180 let mut end = 0;
181 for (n, (at, _)) in self.iter_offsets().enumerate() {
182 if at == offset {
183 return n;
184 }
185 end = at;
186 }
187 assert_eq!(
188 offset, self.remaining as usize,
189 "offset {offset} is inside a word (last start {end})"
190 );
191 self.words.len()
192 }
193}
194
195impl std::fmt::Debug for Argv<'_> {
196 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
197 let mut words: Vec<&BStr> = self.words.to_vec();
198 if let Some(first) = words.first_mut() {
199 *first = BStr::new(&first[self.skip as usize..]);
200 }
201 f.debug_struct("Argv")
202 .field("mode", &self.mode)
203 .field("words", &words)
204 .finish()
205 }
206}
207
208#[derive(Clone, Debug)]
210pub struct IterOffsets<'i> {
211 words: std::slice::Iter<'i, &'i BStr>,
212 skip: usize,
213 offset: usize,
214}
215
216impl<'i> Iterator for IterOffsets<'i> {
217 type Item = (usize, &'i BStr);
218
219 fn next(&mut self) -> Option<Self::Item> {
220 let word = BStr::new(&self.words.next()?[self.skip..]);
221 self.skip = 0;
222 let at = self.offset;
223 self.offset += word.len() + 1;
224 Some((at, word))
225 }
226}
227
228impl Offset for Argv<'_> {
229 #[inline(always)]
230 fn offset_from(&self, start: &Self) -> usize {
231 (start.remaining - self.remaining) as usize
232 }
233}
234
235impl SliceLen for Argv<'_> {
236 #[inline(always)]
237 fn slice_len(&self) -> usize {
238 self.remaining as usize
239 }
240}
241
242impl<'i> Stream for Argv<'i> {
245 type Token = &'i BStr;
246 type Slice = &'i [&'i BStr];
247 type IterOffsets = IterOffsets<'i>;
248 type Checkpoint = Self;
249
250 #[inline(always)]
251 fn iter_offsets(&self) -> Self::IterOffsets {
252 IterOffsets {
253 words: self.words.iter(),
254 skip: self.skip as usize,
255 offset: 0,
256 }
257 }
258 #[inline(always)]
259 fn eof_offset(&self) -> usize {
260 self.remaining as usize
261 }
262 #[inline(always)]
263 fn next_token(&mut self) -> Option<Self::Token> {
264 if self.is_empty() {
265 None
266 } else {
267 Some(self.take_word())
268 }
269 }
270 #[inline(always)]
271 fn peek_token(&self) -> Option<Self::Token> {
272 (!self.is_empty()).then(|| BStr::new(self.front()))
273 }
274 fn offset_for<P>(&self, predicate: P) -> Option<usize>
275 where
276 P: Fn(Self::Token) -> bool,
277 {
278 self.iter_offsets()
279 .find(|&(_, word)| predicate(word))
280 .map(|(at, _)| at)
281 }
282 fn offset_at(&self, tokens: usize) -> Result<usize, Needed> {
283 let mut iter = self.iter_offsets();
284 match iter.nth(tokens) {
285 Some((at, _)) => Ok(at),
286 None if tokens == self.words.len() => Ok(self.remaining as usize),
287 None => Err(Needed::new(tokens - self.words.len())),
288 }
289 }
290 fn next_slice(&mut self, offset: usize) -> Self::Slice {
291 let slice = self.peek_slice(offset);
292 for _ in 0..slice.len() {
293 self.take_word();
294 }
295 slice
296 }
297 fn peek_slice(&self, offset: usize) -> Self::Slice {
298 assert!(
299 self.skip == 0 || offset == 0,
300 "cannot slice whole words inside a bundle"
301 );
302 &self.words[..self.words_in(offset)]
303 }
304 #[inline(always)]
305 fn checkpoint(&self) -> Self::Checkpoint {
306 *self
307 }
308 #[inline(always)]
309 fn reset(&mut self, checkpoint: &Self::Checkpoint) {
310 *self = *checkpoint;
311 }
312 fn trace(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
313 write!(f, "{self:?}")
314 }
315}
316
317impl StreamIsPartial for Argv<'_> {
318 type PartialState = ();
319
320 #[inline(always)]
321 fn complete(&mut self) -> Self::PartialState {}
322 #[inline(always)]
323 fn restore_partial(&mut self, _state: Self::PartialState) {}
324 #[inline(always)]
325 fn is_partial_supported() -> bool {
326 false
327 }
328}
329
330impl Location for Argv<'_> {
331 #[inline(always)]
332 fn previous_token_end(&self) -> usize {
333 self.offset()
334 }
335 #[inline(always)]
336 fn current_token_start(&self) -> usize {
337 self.offset()
338 }
339}
340
341#[cfg(test)]
342mod tests {
343 use winnow::combinator::repeat;
344 use winnow::prelude::*;
345 use winnow::token::{any, take};
346
347 use super::*;
348
349 fn line<'a>(words: &'a [&'a str]) -> Vec<&'a BStr> {
350 words.iter().map(BStr::new).collect()
351 }
352
353 #[test]
354 fn generic_token_parsers_see_whole_words() {
355 let words = line(&["a b", "", "c"]);
356 let mut input = Argv::new(&words);
357 assert_eq!(
358 any::<_, crate::Error>.parse_next(&mut input).unwrap(),
359 "a b"
360 );
361 assert_eq!(input.offset(), 4);
362 let rest: &[&BStr] = take::<_, _, crate::Error>(2usize)
363 .parse_next(&mut input)
364 .unwrap();
365 assert_eq!(rest, &words[1..]);
366 assert!(input.is_empty());
367 }
368
369 #[test]
370 fn a_flag_matches_beside_a_word_holding_any_byte() {
371 let words = [BStr::new(b"--verbose"), BStr::new(b"a\0\xff")];
372 let mut input = Argv::new(&words);
373 let flag = crate::token::arg(&mut input).unwrap();
374 assert!(matches!(flag, crate::token::Arg::Long(f) if f.name == b"verbose"));
375 let word = crate::token::arg(&mut input).unwrap();
376 assert!(matches!(word, crate::token::Arg::Word(w) if w.value == &b"a\0\xff"[..]));
377 }
378
379 #[test]
380 fn a_multibyte_short_is_one_letter() {
381 let words = [BStr::new("-β".as_bytes())];
382 let mut input = Argv::new(&words);
383 let flag = crate::token::arg(&mut input).unwrap();
384 assert!(matches!(flag, crate::token::Arg::Short(f) if f.letter == 'β'));
385 assert!(input.is_empty());
386 }
387
388 #[test]
389 fn reading_a_letter_counts_as_progress() {
390 let words = line(&["-abc"]);
391 let mut input = Argv::new(&words);
392 let letters: Vec<_> = repeat(0.., crate::token::arg)
393 .parse_next(&mut input)
394 .unwrap();
395 assert_eq!(letters.len(), 3);
396 assert!(input.is_empty());
397 }
398
399 #[test]
400 fn checkpoint_restores_the_position_inside_a_bundle() {
401 let words = line(&["-ab", "x"]);
402 let mut input = Argv::new(&words);
403 crate::token::arg(&mut input).unwrap();
404 let saved = input.checkpoint();
405 crate::token::arg(&mut input).unwrap();
406 crate::token::arg(&mut input).unwrap();
407 input.reset(&saved);
408 assert_eq!((input.mode(), input.front()), (Mode::Bundle, &b"b"[..]));
409 }
410}