1use winnow::error::{ModalError as _, ParserError};
20use winnow::prelude::*;
21use winnow::stream::BStr;
22
23use crate::error::Error;
24use crate::stream::{Argv, Mode};
25use crate::value::FromArg;
26
27#[derive(Copy, Clone, Debug, PartialEq, Eq)]
55pub enum Arg<'i> {
56 Long(LongFlag<'i>),
58 Short(ShortFlag),
61 Word(Word<'i>),
64 Separator {
66 offset: usize,
68 },
69}
70
71#[derive(Copy, Clone, Debug, PartialEq, Eq)]
73pub struct LongFlag<'i> {
74 pub name: &'i [u8],
76 pub value: Option<&'i BStr>,
78 pub offset: usize,
80}
81
82#[derive(Copy, Clone, Debug, PartialEq, Eq)]
84pub struct ShortFlag {
85 pub letter: char,
87 pub plus: bool,
90 pub offset: usize,
92}
93
94#[derive(Copy, Clone, Debug, PartialEq, Eq)]
96pub struct Word<'i> {
97 pub value: &'i BStr,
99 pub offset: usize,
101 pub after_separator: bool,
103}
104
105impl<'i> Arg<'i> {
106 pub fn spelling(&self) -> String {
108 match self {
109 Arg::Long(f) => format!("--{}", BStr::new(f.name)),
110 Arg::Short(f) => format!("{}{}", if f.plus { '+' } else { '-' }, f.letter),
111 Arg::Word(w) => w.value.to_string(),
112 Arg::Separator { .. } => "--".to_owned(),
113 }
114 }
115
116 pub fn offset(&self) -> usize {
118 match *self {
119 Arg::Long(LongFlag { offset, .. })
120 | Arg::Short(ShortFlag { offset, .. })
121 | Arg::Word(Word { offset, .. })
122 | Arg::Separator { offset } => offset,
123 }
124 }
125
126 pub fn unexpected(&self) -> Error {
128 match self {
129 Arg::Long(_) | Arg::Short(_) => Error::unknown_flag(self.offset(), self.spelling()),
130 Arg::Word(_) | Arg::Separator { .. } => {
131 Error::unexpected_arg(self.offset(), self.spelling())
132 }
133 }
134 }
135
136 #[inline(always)]
139 pub fn check_switch(&self) -> Result<(), Error> {
140 match self {
141 Arg::Long(LongFlag { value: Some(v), .. }) => Err(self.unexpected_value(v)),
142 _ => Ok(()),
143 }
144 }
145
146 #[cold]
149 #[inline(never)]
150 fn unexpected_value(&self, value: &BStr) -> Error {
151 Error::unexpected_value(self.offset(), self.spelling(), value)
152 }
153
154 #[cold]
155 #[inline(never)]
156 fn missing_value(&self) -> Error {
157 Error::missing_value(self.offset(), self.spelling())
158 }
159
160 #[inline(always)]
168 pub fn read_value(&self, input: &mut Argv<'i>) -> Result<&'i BStr, Error> {
169 self.read_value_with(input, ValueOptions::DEFAULT)
170 }
171
172 #[inline]
175 pub fn read_next(&self, input: &mut Argv<'i>) -> Result<&'i BStr, Error> {
176 if input.is_empty() {
177 return Err(self.missing_value());
178 }
179 Ok(input.take_word())
180 }
181
182 #[inline]
187 pub fn read_more(
188 &self,
189 input: &mut Argv<'i>,
190 options: ValueOptions,
191 terminator: Option<&BStr>,
192 ) -> Option<&'i BStr> {
193 if input.is_empty() {
194 return None;
195 }
196 if terminator.is_some_and(|t| **t == *input.front()) {
197 input.take_word();
198 return None;
199 }
200 detached(input, options)
201 }
202
203 #[inline]
205 pub fn check_values(&self, got: usize, min: usize) -> Result<(), Error> {
206 if got < min {
207 return Err(self.missing_value());
208 }
209 Ok(())
210 }
211
212 #[inline(always)]
215 pub fn has_attached_value(&self, input: &Argv<'i>) -> bool {
216 match self {
217 Arg::Long(LongFlag { value, .. }) => value.is_some(),
218 Arg::Short(_) => input.mode() == Mode::Bundle,
219 _ => false,
220 }
221 }
222
223 #[inline(always)]
225 pub fn read_value_with(
226 &self,
227 input: &mut Argv<'i>,
228 options: ValueOptions,
229 ) -> Result<&'i BStr, Error> {
230 match self.next_value(input, options) {
231 Some(v) => Ok(v),
232 None => Err(self.missing_value()),
233 }
234 }
235
236 #[inline(always)]
240 pub fn read_value_or(&self, input: &mut Argv<'i>, missing: &'static BStr) -> &'i BStr {
241 self.read_value_or_with(input, ValueOptions::DEFAULT, missing)
242 }
243
244 #[inline(always)]
246 pub fn read_value_or_with(
247 &self,
248 input: &mut Argv<'i>,
249 options: ValueOptions,
250 missing: &'static BStr,
251 ) -> &'i BStr {
252 self.next_value(input, options).unwrap_or(missing)
253 }
254
255 #[inline(always)]
260 pub fn read_value_opt_with(
261 &self,
262 input: &mut Argv<'i>,
263 options: ValueOptions,
264 ) -> Option<&'i BStr> {
265 self.next_value(input, options)
266 }
267
268 #[inline(always)]
269 fn next_value(&self, input: &mut Argv<'i>, options: ValueOptions) -> Option<&'i BStr> {
270 match self {
271 Arg::Long(LongFlag { value: Some(v), .. }) => Some(v),
272 Arg::Short(_) if input.mode() == Mode::Bundle => {
273 let v = input.take_word();
274 if options.keep_equals {
275 return Some(v);
276 }
277 Some(BStr::new(v.strip_prefix(b"=").unwrap_or(v)))
278 }
279 _ => {
280 if input.is_empty() || options.require_equals {
281 return None;
282 }
283 detached(input, options)
284 }
285 }
286 }
287
288 #[inline]
290 pub fn read_value_as<T: FromArg>(&self, input: &mut Argv<'i>) -> Result<T, Error> {
291 let v = self.read_value(input)?;
292 self.convert(v)
293 }
294
295 #[inline]
297 pub fn convert<T: FromArg>(&self, value: &BStr) -> Result<T, Error> {
298 T::from_arg(value)
299 .map_err(|cause| Error::invalid_value(self.offset(), self.spelling(), value, cause))
300 }
301
302 pub fn values_as<T: FromArg>(&self, delimiter: u8) -> impl Parser<Argv<'i>, Vec<T>, Error> {
304 move |input: &mut Argv<'i>| {
305 let value = self.read_value(input)?;
306 split(value, delimiter).map(|v| self.convert(v)).collect()
307 }
308 }
309
310 pub fn switch(&self) -> impl Parser<Argv<'i>, (), Error> {
312 move |_: &mut Argv<'i>| self.check_switch()
313 }
314
315 pub fn value(&self) -> impl Parser<Argv<'i>, &'i BStr, Error> {
317 move |input: &mut Argv<'i>| self.read_value(input)
318 }
319
320 pub fn value_or<T: FromArg>(&self, missing: &'static BStr) -> impl Parser<Argv<'i>, T, Error> {
322 move |input: &mut Argv<'i>| {
323 let v = self.read_value_or(input, missing);
324 self.convert(v)
325 }
326 }
327
328 pub fn value_or_with<T: FromArg>(
330 &self,
331 options: ValueOptions,
332 missing: &'static BStr,
333 ) -> impl Parser<Argv<'i>, T, Error> {
334 move |input: &mut Argv<'i>| {
335 let v = self.read_value_or_with(input, options, missing);
336 self.convert(v)
337 }
338 }
339
340 pub fn value_as_with<T: FromArg>(
342 &self,
343 options: ValueOptions,
344 ) -> impl Parser<Argv<'i>, T, Error> {
345 move |input: &mut Argv<'i>| {
346 let v = self.read_value_with(input, options)?;
347 self.convert(v)
348 }
349 }
350
351 pub fn value_as<T: FromArg>(&self) -> impl Parser<Argv<'i>, T, Error> {
353 move |input: &mut Argv<'i>| self.read_value_as(input)
354 }
355}
356
357#[inline]
359pub fn split(value: &BStr, delimiter: u8) -> impl Iterator<Item = &BStr> {
360 value.split(move |&b| b == delimiter).map(BStr::new)
361}
362
363impl<'i> Word<'i> {
364 #[inline]
366 pub fn split(&self, delimiter: u8) -> impl Iterator<Item = Word<'i>> {
367 let (base, after_separator) = (self.offset, self.after_separator);
368 let start = self.value.as_ptr() as usize;
369 split(self.value, delimiter).map(move |value| Word {
370 value,
371 offset: base + (value.as_ptr() as usize - start),
372 after_separator,
373 })
374 }
375
376 #[inline]
378 pub fn convert<T: FromArg>(&self, name: &str) -> Result<T, Error> {
379 T::from_arg(self.value)
380 .map_err(|cause| Error::invalid_value(self.offset, name, self.value, cause))
381 }
382
383 pub fn value_as<T: FromArg>(&self, name: &'static str) -> impl Parser<Argv<'i>, T, Error> {
385 move |_: &mut Argv<'i>| self.convert(name)
386 }
387}
388
389#[inline(always)]
392fn detached<'i>(input: &mut Argv<'i>, options: ValueOptions) -> Option<&'i BStr> {
393 let next = input.front();
394 if input.mode() == Mode::Word
395 && is_flag_like(next)
396 && !options.hyphen_values
397 && !(options.negative_numbers && is_negative_number(next))
398 {
399 return None;
400 }
401 Some(input.take_word())
402}
403
404#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
410pub struct ValueOptions {
411 pub negative_numbers: bool,
413 pub hyphen_values: bool,
415 pub require_equals: bool,
417 pub keep_equals: bool,
420}
421
422impl ValueOptions {
423 pub const DEFAULT: Self = Self {
425 negative_numbers: false,
426 hyphen_values: false,
427 require_equals: false,
428 keep_equals: false,
429 };
430}
431
432#[inline]
436pub fn is_negative_number(word: &[u8]) -> bool {
437 let Some(rest) = word.strip_prefix(b"-") else {
438 return false;
439 };
440 let (mantissa, exponent) = match rest.iter().position(|b| matches!(b, b'e' | b'E')) {
441 Some(at) => (&rest[..at], Some(&rest[at + 1..])),
442 None => (rest, None),
443 };
444 let (mut digit, mut dot) = (false, false);
445 for &b in mantissa {
446 match b {
447 b'0'..=b'9' => digit = true,
448 b'.' if !dot => dot = true,
449 _ => return false,
450 }
451 }
452 let exponent_ok = match exponent {
453 None => true,
454 Some(e) => {
455 let digits = e
456 .strip_prefix(b"+")
457 .or_else(|| e.strip_prefix(b"-"))
458 .unwrap_or(e);
459 !digits.is_empty() && digits.iter().all(u8::is_ascii_digit)
460 }
461 };
462 digit && exponent_ok
463}
464
465pub fn number<'i>(input: &mut Argv<'i>) -> Result<Word<'i>, Error> {
468 if input.mode() != Mode::Word || !is_negative_number(input.front()) {
469 return Err(Error::from_input(input));
470 }
471 let offset = input.offset();
472 Ok(Word {
473 after_separator: false,
474 value: input.take_word(),
475 offset,
476 })
477}
478
479#[inline(always)]
481pub(crate) fn is_flag_like(word: &[u8]) -> bool {
482 word.len() > 1 && word[0] == b'-'
483}
484
485#[inline(always)]
487fn letter(input: &mut Argv<'_>) -> char {
488 let bytes = input.front();
489 let (c, width) = match bytes[0] {
490 b if b.is_ascii() => (b as char, 1),
491 b => {
492 let width = match b {
493 0xC2..=0xDF => 2,
494 0xE0..=0xEF => 3,
495 0xF0..=0xF4 => 4,
496 _ => 1,
497 };
498 match bytes.get(..width).and_then(|b| std::str::from_utf8(b).ok()) {
499 Some(s) => (
500 s.chars().next().unwrap_or(char::REPLACEMENT_CHARACTER),
501 width,
502 ),
503 None => (char::REPLACEMENT_CHARACTER, 1),
504 }
505 }
506 };
507 input.take_bytes(width);
508 if input.front().is_empty() {
509 input.take_word();
510 input.set_mode(Mode::Word);
511 } else {
512 input.set_mode(Mode::Bundle);
513 }
514 c
515}
516
517#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
519pub enum Kind {
520 Long,
522 Short,
524 Word,
526 Separator,
528}
529
530#[inline]
559pub fn kind(input: &mut Argv<'_>) -> Result<Kind, Error> {
560 if input.is_empty() {
561 return Err(Error::from_input(input));
562 }
563 Ok(match input.mode() {
564 Mode::Bundle => Kind::Short,
565 Mode::Stopped | Mode::Values => Kind::Word,
566 Mode::Word => match input.front() {
567 b"--" => Kind::Separator,
568 [b'-', b'-', ..] => Kind::Long,
569 [b'-', _, ..] => Kind::Short,
570 _ => Kind::Word,
571 },
572 })
573}
574
575#[inline(always)]
579pub fn arg<'i>(input: &mut Argv<'i>) -> Result<Arg<'i>, Error> {
580 let kind = kind(input)?;
581 let offset = input.offset();
582 Ok(match kind {
583 Kind::Separator => {
584 input.take_word();
585 input.set_mode(Mode::Stopped);
586 Arg::Separator { offset }
587 }
588 Kind::Long => {
589 let word = &input.take_word()[2..];
590 Arg::Long(match word.iter().position(|&b| b == b'=') {
591 Some(eq) => LongFlag {
592 name: &word[..eq],
593 value: Some(BStr::new(&word[eq + 1..])),
594 offset,
595 },
596 None => LongFlag {
597 name: word,
598 value: None,
599 offset,
600 },
601 })
602 }
603 Kind::Short => {
604 let plus = if input.mode() == Mode::Word {
606 input.take_bytes(1);
607 input.set_plus(false);
608 false
609 } else {
610 input.plus()
611 };
612 Arg::Short(ShortFlag {
613 letter: letter(input),
614 plus,
615 offset,
616 })
617 }
618 Kind::Word => Arg::Word(Word {
619 after_separator: input.mode() == Mode::Stopped,
620 value: input.take_word(),
621 offset,
622 }),
623 })
624}
625
626pub fn word<'i>(input: &mut Argv<'i>) -> Result<Word<'i>, Error> {
628 if kind(input)? != Kind::Word {
629 return Err(Error::from_input(input));
630 }
631 let offset = input.offset();
632 Ok(Word {
633 after_separator: input.mode() == Mode::Stopped,
634 value: input.take_word(),
635 offset,
636 })
637}
638
639pub fn flag_word<'i>(input: &mut Argv<'i>) -> Result<Word<'i>, Error> {
643 let front = input.front();
644 if input.is_empty() || input.mode() != Mode::Word || !is_flag_like(front) || front == b"--" {
645 return Err(Error::from_input(input));
646 }
647 let offset = input.offset();
648 Ok(Word {
649 after_separator: false,
650 value: input.take_word(),
651 offset,
652 })
653}
654
655#[inline(always)]
659pub fn arg_plus<'i>(input: &mut Argv<'i>) -> Result<Arg<'i>, Error> {
660 if input.mode() == Mode::Word && !input.is_empty() {
661 let front = input.front();
662 if front.len() > 1 && front[0] == b'+' {
663 let offset = input.offset();
664 input.take_bytes(1);
665 input.set_plus(true);
666 return Ok(Arg::Short(ShortFlag {
667 letter: letter(input),
668 plus: true,
669 offset,
670 }));
671 }
672 }
673 arg(input)
674}
675
676#[inline]
685pub fn long_only<'i>(input: &mut Argv<'i>, is_long: impl Fn(&[u8]) -> bool) -> Option<Arg<'i>> {
686 if input.is_empty() || input.mode() != Mode::Word {
687 return None;
688 }
689 let body = input.front().strip_prefix(b"-")?;
690 if body.len() < 2 || body[0] == b'-' {
691 return None;
692 }
693 let (name, value) = match body.iter().position(|&b| b == b'=') {
694 Some(eq) => (&body[..eq], Some(&body[eq + 1..])),
695 None => (body, None),
696 };
697 if !is_long(name) {
698 return None;
699 }
700 let offset = input.offset();
701 let word = &input.take_word()[1..];
702 let name = &word[..name.len()];
703 let value = value.map(|_| BStr::new(&word[name.len() + 1..]));
704 Some(Arg::Long(LongFlag {
705 name,
706 value,
707 offset,
708 }))
709}
710
711pub fn separator_word<'i>(input: &mut Argv<'i>) -> Result<Word<'i>, Error> {
714 if kind(input)? != Kind::Separator {
715 return Err(Error::from_input(input));
716 }
717 let offset = input.offset();
718 Ok(Word {
719 after_separator: false,
720 value: input.take_word(),
721 offset,
722 })
723}
724
725pub fn finish(input: &mut Argv<'_>) -> Result<(), Error> {
727 if input.is_empty() {
728 return Ok(());
729 }
730 Err(arg(input)?.unexpected().cut())
731}