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)]
256 fn next_value(&self, input: &mut Argv<'i>, options: ValueOptions) -> Option<&'i BStr> {
257 match self {
258 Arg::Long(LongFlag { value: Some(v), .. }) => Some(v),
259 Arg::Short(_) if input.mode() == Mode::Bundle => {
260 let v = input.take_word();
261 if options.keep_equals {
262 return Some(v);
263 }
264 Some(BStr::new(v.strip_prefix(b"=").unwrap_or(v)))
265 }
266 _ => {
267 if input.is_empty() || options.require_equals {
268 return None;
269 }
270 detached(input, options)
271 }
272 }
273 }
274
275 #[inline]
277 pub fn read_value_as<T: FromArg>(&self, input: &mut Argv<'i>) -> Result<T, Error> {
278 let v = self.read_value(input)?;
279 self.convert(v)
280 }
281
282 #[inline]
284 pub fn convert<T: FromArg>(&self, value: &BStr) -> Result<T, Error> {
285 T::from_arg(value)
286 .map_err(|cause| Error::invalid_value(self.offset(), self.spelling(), value, cause))
287 }
288
289 pub fn values_as<T: FromArg>(&self, delimiter: u8) -> impl Parser<Argv<'i>, Vec<T>, Error> {
291 move |input: &mut Argv<'i>| {
292 let value = self.read_value(input)?;
293 split(value, delimiter).map(|v| self.convert(v)).collect()
294 }
295 }
296
297 pub fn switch(&self) -> impl Parser<Argv<'i>, (), Error> {
299 move |_: &mut Argv<'i>| self.check_switch()
300 }
301
302 pub fn value(&self) -> impl Parser<Argv<'i>, &'i BStr, Error> {
304 move |input: &mut Argv<'i>| self.read_value(input)
305 }
306
307 pub fn value_or<T: FromArg>(&self, missing: &'static BStr) -> impl Parser<Argv<'i>, T, Error> {
309 move |input: &mut Argv<'i>| {
310 let v = self.read_value_or(input, missing);
311 self.convert(v)
312 }
313 }
314
315 pub fn value_or_with<T: FromArg>(
317 &self,
318 options: ValueOptions,
319 missing: &'static BStr,
320 ) -> impl Parser<Argv<'i>, T, Error> {
321 move |input: &mut Argv<'i>| {
322 let v = self.read_value_or_with(input, options, missing);
323 self.convert(v)
324 }
325 }
326
327 pub fn value_as_with<T: FromArg>(
329 &self,
330 options: ValueOptions,
331 ) -> impl Parser<Argv<'i>, T, Error> {
332 move |input: &mut Argv<'i>| {
333 let v = self.read_value_with(input, options)?;
334 self.convert(v)
335 }
336 }
337
338 pub fn value_as<T: FromArg>(&self) -> impl Parser<Argv<'i>, T, Error> {
340 move |input: &mut Argv<'i>| self.read_value_as(input)
341 }
342}
343
344#[inline]
346pub fn split(value: &BStr, delimiter: u8) -> impl Iterator<Item = &BStr> {
347 value.split(move |&b| b == delimiter).map(BStr::new)
348}
349
350impl<'i> Word<'i> {
351 #[inline]
353 pub fn split(&self, delimiter: u8) -> impl Iterator<Item = Word<'i>> {
354 let (base, after_separator) = (self.offset, self.after_separator);
355 let start = self.value.as_ptr() as usize;
356 split(self.value, delimiter).map(move |value| Word {
357 value,
358 offset: base + (value.as_ptr() as usize - start),
359 after_separator,
360 })
361 }
362
363 #[inline]
365 pub fn convert<T: FromArg>(&self, name: &str) -> Result<T, Error> {
366 T::from_arg(self.value)
367 .map_err(|cause| Error::invalid_value(self.offset, name, self.value, cause))
368 }
369
370 pub fn value_as<T: FromArg>(&self, name: &'static str) -> impl Parser<Argv<'i>, T, Error> {
372 move |_: &mut Argv<'i>| self.convert(name)
373 }
374}
375
376#[inline(always)]
379fn detached<'i>(input: &mut Argv<'i>, options: ValueOptions) -> Option<&'i BStr> {
380 let next = input.front();
381 if input.mode() == Mode::Word
382 && is_flag_like(next)
383 && !options.hyphen_values
384 && !(options.negative_numbers && is_negative_number(next))
385 {
386 return None;
387 }
388 Some(input.take_word())
389}
390
391#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
397pub struct ValueOptions {
398 pub negative_numbers: bool,
400 pub hyphen_values: bool,
402 pub require_equals: bool,
404 pub keep_equals: bool,
407}
408
409impl ValueOptions {
410 pub const DEFAULT: Self = Self {
412 negative_numbers: false,
413 hyphen_values: false,
414 require_equals: false,
415 keep_equals: false,
416 };
417}
418
419#[inline]
423pub fn is_negative_number(word: &[u8]) -> bool {
424 let Some(rest) = word.strip_prefix(b"-") else {
425 return false;
426 };
427 let (mantissa, exponent) = match rest.iter().position(|b| matches!(b, b'e' | b'E')) {
428 Some(at) => (&rest[..at], Some(&rest[at + 1..])),
429 None => (rest, None),
430 };
431 let (mut digit, mut dot) = (false, false);
432 for &b in mantissa {
433 match b {
434 b'0'..=b'9' => digit = true,
435 b'.' if !dot => dot = true,
436 _ => return false,
437 }
438 }
439 let exponent_ok = match exponent {
440 None => true,
441 Some(e) => {
442 let digits = e
443 .strip_prefix(b"+")
444 .or_else(|| e.strip_prefix(b"-"))
445 .unwrap_or(e);
446 !digits.is_empty() && digits.iter().all(u8::is_ascii_digit)
447 }
448 };
449 digit && exponent_ok
450}
451
452pub fn number<'i>(input: &mut Argv<'i>) -> Result<Word<'i>, Error> {
455 if input.mode() != Mode::Word || !is_negative_number(input.front()) {
456 return Err(Error::from_input(input));
457 }
458 let offset = input.offset();
459 Ok(Word {
460 after_separator: false,
461 value: input.take_word(),
462 offset,
463 })
464}
465
466#[inline(always)]
468pub(crate) fn is_flag_like(word: &[u8]) -> bool {
469 word.len() > 1 && word[0] == b'-'
470}
471
472#[inline(always)]
474fn letter(input: &mut Argv<'_>) -> char {
475 let bytes = input.front();
476 let (c, width) = match bytes[0] {
477 b if b.is_ascii() => (b as char, 1),
478 b => {
479 let width = match b {
480 0xC2..=0xDF => 2,
481 0xE0..=0xEF => 3,
482 0xF0..=0xF4 => 4,
483 _ => 1,
484 };
485 match bytes.get(..width).and_then(|b| std::str::from_utf8(b).ok()) {
486 Some(s) => (
487 s.chars().next().unwrap_or(char::REPLACEMENT_CHARACTER),
488 width,
489 ),
490 None => (char::REPLACEMENT_CHARACTER, 1),
491 }
492 }
493 };
494 input.take_bytes(width);
495 if input.front().is_empty() {
496 input.take_word();
497 input.set_mode(Mode::Word);
498 } else {
499 input.set_mode(Mode::Bundle);
500 }
501 c
502}
503
504#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
506pub enum Kind {
507 Long,
509 Short,
511 Word,
513 Separator,
515}
516
517#[inline]
546pub fn kind(input: &mut Argv<'_>) -> Result<Kind, Error> {
547 if input.is_empty() {
548 return Err(Error::from_input(input));
549 }
550 Ok(match input.mode() {
551 Mode::Bundle => Kind::Short,
552 Mode::Stopped | Mode::Values => Kind::Word,
553 Mode::Word => match input.front() {
554 b"--" => Kind::Separator,
555 [b'-', b'-', ..] => Kind::Long,
556 [b'-', _, ..] => Kind::Short,
557 _ => Kind::Word,
558 },
559 })
560}
561
562#[inline(always)]
566pub fn arg<'i>(input: &mut Argv<'i>) -> Result<Arg<'i>, Error> {
567 let kind = kind(input)?;
568 let offset = input.offset();
569 Ok(match kind {
570 Kind::Separator => {
571 input.take_word();
572 input.set_mode(Mode::Stopped);
573 Arg::Separator { offset }
574 }
575 Kind::Long => {
576 let word = &input.take_word()[2..];
577 Arg::Long(match word.iter().position(|&b| b == b'=') {
578 Some(eq) => LongFlag {
579 name: &word[..eq],
580 value: Some(BStr::new(&word[eq + 1..])),
581 offset,
582 },
583 None => LongFlag {
584 name: word,
585 value: None,
586 offset,
587 },
588 })
589 }
590 Kind::Short => {
591 let plus = if input.mode() == Mode::Word {
593 input.take_bytes(1);
594 input.set_plus(false);
595 false
596 } else {
597 input.plus()
598 };
599 Arg::Short(ShortFlag {
600 letter: letter(input),
601 plus,
602 offset,
603 })
604 }
605 Kind::Word => Arg::Word(Word {
606 after_separator: input.mode() == Mode::Stopped,
607 value: input.take_word(),
608 offset,
609 }),
610 })
611}
612
613pub fn word<'i>(input: &mut Argv<'i>) -> Result<Word<'i>, Error> {
615 if kind(input)? != Kind::Word {
616 return Err(Error::from_input(input));
617 }
618 let offset = input.offset();
619 Ok(Word {
620 after_separator: input.mode() == Mode::Stopped,
621 value: input.take_word(),
622 offset,
623 })
624}
625
626pub fn flag_word<'i>(input: &mut Argv<'i>) -> Result<Word<'i>, Error> {
630 let front = input.front();
631 if input.is_empty() || input.mode() != Mode::Word || !is_flag_like(front) || front == b"--" {
632 return Err(Error::from_input(input));
633 }
634 let offset = input.offset();
635 Ok(Word {
636 after_separator: false,
637 value: input.take_word(),
638 offset,
639 })
640}
641
642#[inline(always)]
646pub fn arg_plus<'i>(input: &mut Argv<'i>) -> Result<Arg<'i>, Error> {
647 if input.mode() == Mode::Word && !input.is_empty() {
648 let front = input.front();
649 if front.len() > 1 && front[0] == b'+' {
650 let offset = input.offset();
651 input.take_bytes(1);
652 input.set_plus(true);
653 return Ok(Arg::Short(ShortFlag {
654 letter: letter(input),
655 plus: true,
656 offset,
657 }));
658 }
659 }
660 arg(input)
661}
662
663#[inline]
672pub fn long_only<'i>(input: &mut Argv<'i>, is_long: impl Fn(&[u8]) -> bool) -> Option<Arg<'i>> {
673 if input.is_empty() || input.mode() != Mode::Word {
674 return None;
675 }
676 let body = input.front().strip_prefix(b"-")?;
677 if body.len() < 2 || body[0] == b'-' {
678 return None;
679 }
680 let (name, value) = match body.iter().position(|&b| b == b'=') {
681 Some(eq) => (&body[..eq], Some(&body[eq + 1..])),
682 None => (body, None),
683 };
684 if !is_long(name) {
685 return None;
686 }
687 let offset = input.offset();
688 let word = &input.take_word()[1..];
689 let name = &word[..name.len()];
690 let value = value.map(|_| BStr::new(&word[name.len() + 1..]));
691 Some(Arg::Long(LongFlag {
692 name,
693 value,
694 offset,
695 }))
696}
697
698pub fn separator_word<'i>(input: &mut Argv<'i>) -> Result<Word<'i>, Error> {
701 if kind(input)? != Kind::Separator {
702 return Err(Error::from_input(input));
703 }
704 let offset = input.offset();
705 Ok(Word {
706 after_separator: false,
707 value: input.take_word(),
708 offset,
709 })
710}
711
712pub fn finish(input: &mut Argv<'_>) -> Result<(), Error> {
714 if input.is_empty() {
715 return Ok(());
716 }
717 Err(arg(input)?.unexpected().cut())
718}