use winnow::error::{ModalError as _, ParserError};
use winnow::prelude::*;
use winnow::stream::BStr;
use crate::error::Error;
use crate::stream::{Argv, Mode};
use crate::value::FromArg;
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub enum Arg<'i> {
Long(LongFlag<'i>),
Short(ShortFlag),
Word(Word<'i>),
Separator {
offset: usize,
},
}
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub struct LongFlag<'i> {
pub name: &'i [u8],
pub value: Option<&'i BStr>,
pub offset: usize,
}
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub struct ShortFlag {
pub letter: char,
pub plus: bool,
pub offset: usize,
}
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub struct Word<'i> {
pub value: &'i BStr,
pub offset: usize,
pub after_separator: bool,
}
impl<'i> Arg<'i> {
pub fn spelling(&self) -> String {
match self {
Arg::Long(f) => format!("--{}", BStr::new(f.name)),
Arg::Short(f) => format!("{}{}", if f.plus { '+' } else { '-' }, f.letter),
Arg::Word(w) => w.value.to_string(),
Arg::Separator { .. } => "--".to_owned(),
}
}
pub fn offset(&self) -> usize {
match *self {
Arg::Long(LongFlag { offset, .. })
| Arg::Short(ShortFlag { offset, .. })
| Arg::Word(Word { offset, .. })
| Arg::Separator { offset } => offset,
}
}
pub fn unexpected(&self) -> Error {
match self {
Arg::Long(_) | Arg::Short(_) => Error::unknown_flag(self.offset(), self.spelling()),
Arg::Word(_) | Arg::Separator { .. } => {
Error::unexpected_arg(self.offset(), self.spelling())
}
}
}
#[inline(always)]
pub fn check_switch(&self) -> Result<(), Error> {
match self {
Arg::Long(LongFlag { value: Some(v), .. }) => Err(self.unexpected_value(v)),
_ => Ok(()),
}
}
#[cold]
#[inline(never)]
fn unexpected_value(&self, value: &BStr) -> Error {
Error::unexpected_value(self.offset(), self.spelling(), value)
}
#[cold]
#[inline(never)]
fn missing_value(&self) -> Error {
Error::missing_value(self.offset(), self.spelling())
}
#[inline(always)]
pub fn read_value(&self, input: &mut Argv<'i>) -> Result<&'i BStr, Error> {
self.read_value_with(input, ValueOptions::DEFAULT)
}
#[inline]
pub fn read_next(&self, input: &mut Argv<'i>) -> Result<&'i BStr, Error> {
if input.is_empty() {
return Err(self.missing_value());
}
Ok(input.take_word())
}
#[inline]
pub fn read_more(
&self,
input: &mut Argv<'i>,
options: ValueOptions,
terminator: Option<&BStr>,
) -> Option<&'i BStr> {
if input.is_empty() {
return None;
}
if terminator.is_some_and(|t| **t == *input.front()) {
input.take_word();
return None;
}
detached(input, options)
}
#[inline]
pub fn check_values(&self, got: usize, min: usize) -> Result<(), Error> {
if got < min {
return Err(self.missing_value());
}
Ok(())
}
#[inline(always)]
pub fn has_attached_value(&self, input: &Argv<'i>) -> bool {
match self {
Arg::Long(LongFlag { value, .. }) => value.is_some(),
Arg::Short(_) => input.mode() == Mode::Bundle,
_ => false,
}
}
#[inline(always)]
pub fn read_value_with(
&self,
input: &mut Argv<'i>,
options: ValueOptions,
) -> Result<&'i BStr, Error> {
match self.next_value(input, options) {
Some(v) => Ok(v),
None => Err(self.missing_value()),
}
}
#[inline(always)]
pub fn read_value_or(&self, input: &mut Argv<'i>, missing: &'static BStr) -> &'i BStr {
self.read_value_or_with(input, ValueOptions::DEFAULT, missing)
}
#[inline(always)]
pub fn read_value_or_with(
&self,
input: &mut Argv<'i>,
options: ValueOptions,
missing: &'static BStr,
) -> &'i BStr {
self.next_value(input, options).unwrap_or(missing)
}
#[inline(always)]
pub fn read_value_opt_with(
&self,
input: &mut Argv<'i>,
options: ValueOptions,
) -> Option<&'i BStr> {
self.next_value(input, options)
}
#[inline(always)]
fn next_value(&self, input: &mut Argv<'i>, options: ValueOptions) -> Option<&'i BStr> {
match self {
Arg::Long(LongFlag { value: Some(v), .. }) => Some(v),
Arg::Short(_) if input.mode() == Mode::Bundle => {
let v = input.take_word();
if options.keep_equals {
return Some(v);
}
Some(BStr::new(v.strip_prefix(b"=").unwrap_or(v)))
}
_ => {
if input.is_empty() || options.require_equals {
return None;
}
detached(input, options)
}
}
}
#[inline]
pub fn read_value_as<T: FromArg>(&self, input: &mut Argv<'i>) -> Result<T, Error> {
let v = self.read_value(input)?;
self.convert(v)
}
#[inline]
pub fn convert<T: FromArg>(&self, value: &BStr) -> Result<T, Error> {
T::from_arg(value)
.map_err(|cause| Error::invalid_value(self.offset(), self.spelling(), value, cause))
}
pub fn values_as<T: FromArg>(&self, delimiter: u8) -> impl Parser<Argv<'i>, Vec<T>, Error> {
move |input: &mut Argv<'i>| {
let value = self.read_value(input)?;
split(value, delimiter).map(|v| self.convert(v)).collect()
}
}
pub fn switch(&self) -> impl Parser<Argv<'i>, (), Error> {
move |_: &mut Argv<'i>| self.check_switch()
}
pub fn value(&self) -> impl Parser<Argv<'i>, &'i BStr, Error> {
move |input: &mut Argv<'i>| self.read_value(input)
}
pub fn value_or<T: FromArg>(&self, missing: &'static BStr) -> impl Parser<Argv<'i>, T, Error> {
move |input: &mut Argv<'i>| {
let v = self.read_value_or(input, missing);
self.convert(v)
}
}
pub fn value_or_with<T: FromArg>(
&self,
options: ValueOptions,
missing: &'static BStr,
) -> impl Parser<Argv<'i>, T, Error> {
move |input: &mut Argv<'i>| {
let v = self.read_value_or_with(input, options, missing);
self.convert(v)
}
}
pub fn value_as_with<T: FromArg>(
&self,
options: ValueOptions,
) -> impl Parser<Argv<'i>, T, Error> {
move |input: &mut Argv<'i>| {
let v = self.read_value_with(input, options)?;
self.convert(v)
}
}
pub fn value_as<T: FromArg>(&self) -> impl Parser<Argv<'i>, T, Error> {
move |input: &mut Argv<'i>| self.read_value_as(input)
}
}
#[inline]
pub fn split(value: &BStr, delimiter: u8) -> impl Iterator<Item = &BStr> {
value.split(move |&b| b == delimiter).map(BStr::new)
}
impl<'i> Word<'i> {
#[inline]
pub fn split(&self, delimiter: u8) -> impl Iterator<Item = Word<'i>> {
let (base, after_separator) = (self.offset, self.after_separator);
let start = self.value.as_ptr() as usize;
split(self.value, delimiter).map(move |value| Word {
value,
offset: base + (value.as_ptr() as usize - start),
after_separator,
})
}
#[inline]
pub fn convert<T: FromArg>(&self, name: &str) -> Result<T, Error> {
T::from_arg(self.value)
.map_err(|cause| Error::invalid_value(self.offset, name, self.value, cause))
}
pub fn value_as<T: FromArg>(&self, name: &'static str) -> impl Parser<Argv<'i>, T, Error> {
move |_: &mut Argv<'i>| self.convert(name)
}
}
#[inline(always)]
fn detached<'i>(input: &mut Argv<'i>, options: ValueOptions) -> Option<&'i BStr> {
let next = input.front();
if input.mode() == Mode::Word
&& is_flag_like(next)
&& !options.hyphen_values
&& !(options.negative_numbers && is_negative_number(next))
{
return None;
}
Some(input.take_word())
}
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
pub struct ValueOptions {
pub negative_numbers: bool,
pub hyphen_values: bool,
pub require_equals: bool,
pub keep_equals: bool,
}
impl ValueOptions {
pub const DEFAULT: Self = Self {
negative_numbers: false,
hyphen_values: false,
require_equals: false,
keep_equals: false,
};
}
#[inline]
pub fn is_negative_number(word: &[u8]) -> bool {
let Some(rest) = word.strip_prefix(b"-") else {
return false;
};
let (mantissa, exponent) = match rest.iter().position(|b| matches!(b, b'e' | b'E')) {
Some(at) => (&rest[..at], Some(&rest[at + 1..])),
None => (rest, None),
};
let (mut digit, mut dot) = (false, false);
for &b in mantissa {
match b {
b'0'..=b'9' => digit = true,
b'.' if !dot => dot = true,
_ => return false,
}
}
let exponent_ok = match exponent {
None => true,
Some(e) => {
let digits = e
.strip_prefix(b"+")
.or_else(|| e.strip_prefix(b"-"))
.unwrap_or(e);
!digits.is_empty() && digits.iter().all(u8::is_ascii_digit)
}
};
digit && exponent_ok
}
pub fn number<'i>(input: &mut Argv<'i>) -> Result<Word<'i>, Error> {
if input.mode() != Mode::Word || !is_negative_number(input.front()) {
return Err(Error::from_input(input));
}
let offset = input.offset();
Ok(Word {
after_separator: false,
value: input.take_word(),
offset,
})
}
#[inline(always)]
pub(crate) fn is_flag_like(word: &[u8]) -> bool {
word.len() > 1 && word[0] == b'-'
}
#[inline(always)]
fn letter(input: &mut Argv<'_>) -> char {
let bytes = input.front();
let (c, width) = match bytes[0] {
b if b.is_ascii() => (b as char, 1),
b => {
let width = match b {
0xC2..=0xDF => 2,
0xE0..=0xEF => 3,
0xF0..=0xF4 => 4,
_ => 1,
};
match bytes.get(..width).and_then(|b| std::str::from_utf8(b).ok()) {
Some(s) => (
s.chars().next().unwrap_or(char::REPLACEMENT_CHARACTER),
width,
),
None => (char::REPLACEMENT_CHARACTER, 1),
}
}
};
input.take_bytes(width);
if input.front().is_empty() {
input.take_word();
input.set_mode(Mode::Word);
} else {
input.set_mode(Mode::Bundle);
}
c
}
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
pub enum Kind {
Long,
Short,
Word,
Separator,
}
#[inline]
pub fn kind(input: &mut Argv<'_>) -> Result<Kind, Error> {
if input.is_empty() {
return Err(Error::from_input(input));
}
Ok(match input.mode() {
Mode::Bundle => Kind::Short,
Mode::Stopped | Mode::Values => Kind::Word,
Mode::Word => match input.front() {
b"--" => Kind::Separator,
[b'-', b'-', ..] => Kind::Long,
[b'-', _, ..] => Kind::Short,
_ => Kind::Word,
},
})
}
#[inline(always)]
pub fn arg<'i>(input: &mut Argv<'i>) -> Result<Arg<'i>, Error> {
let kind = kind(input)?;
let offset = input.offset();
Ok(match kind {
Kind::Separator => {
input.take_word();
input.set_mode(Mode::Stopped);
Arg::Separator { offset }
}
Kind::Long => {
let word = &input.take_word()[2..];
Arg::Long(match word.iter().position(|&b| b == b'=') {
Some(eq) => LongFlag {
name: &word[..eq],
value: Some(BStr::new(&word[eq + 1..])),
offset,
},
None => LongFlag {
name: word,
value: None,
offset,
},
})
}
Kind::Short => {
let plus = if input.mode() == Mode::Word {
input.take_bytes(1);
input.set_plus(false);
false
} else {
input.plus()
};
Arg::Short(ShortFlag {
letter: letter(input),
plus,
offset,
})
}
Kind::Word => Arg::Word(Word {
after_separator: input.mode() == Mode::Stopped,
value: input.take_word(),
offset,
}),
})
}
pub fn word<'i>(input: &mut Argv<'i>) -> Result<Word<'i>, Error> {
if kind(input)? != Kind::Word {
return Err(Error::from_input(input));
}
let offset = input.offset();
Ok(Word {
after_separator: input.mode() == Mode::Stopped,
value: input.take_word(),
offset,
})
}
pub fn flag_word<'i>(input: &mut Argv<'i>) -> Result<Word<'i>, Error> {
let front = input.front();
if input.is_empty() || input.mode() != Mode::Word || !is_flag_like(front) || front == b"--" {
return Err(Error::from_input(input));
}
let offset = input.offset();
Ok(Word {
after_separator: false,
value: input.take_word(),
offset,
})
}
#[inline(always)]
pub fn arg_plus<'i>(input: &mut Argv<'i>) -> Result<Arg<'i>, Error> {
if input.mode() == Mode::Word && !input.is_empty() {
let front = input.front();
if front.len() > 1 && front[0] == b'+' {
let offset = input.offset();
input.take_bytes(1);
input.set_plus(true);
return Ok(Arg::Short(ShortFlag {
letter: letter(input),
plus: true,
offset,
}));
}
}
arg(input)
}
#[inline]
pub fn long_only<'i>(input: &mut Argv<'i>, is_long: impl Fn(&[u8]) -> bool) -> Option<Arg<'i>> {
if input.is_empty() || input.mode() != Mode::Word {
return None;
}
let body = input.front().strip_prefix(b"-")?;
if body.len() < 2 || body[0] == b'-' {
return None;
}
let (name, value) = match body.iter().position(|&b| b == b'=') {
Some(eq) => (&body[..eq], Some(&body[eq + 1..])),
None => (body, None),
};
if !is_long(name) {
return None;
}
let offset = input.offset();
let word = &input.take_word()[1..];
let name = &word[..name.len()];
let value = value.map(|_| BStr::new(&word[name.len() + 1..]));
Some(Arg::Long(LongFlag {
name,
value,
offset,
}))
}
pub fn separator_word<'i>(input: &mut Argv<'i>) -> Result<Word<'i>, Error> {
if kind(input)? != Kind::Separator {
return Err(Error::from_input(input));
}
let offset = input.offset();
Ok(Word {
after_separator: false,
value: input.take_word(),
offset,
})
}
pub fn finish(input: &mut Argv<'_>) -> Result<(), Error> {
if input.is_empty() {
return Ok(());
}
Err(arg(input)?.unexpected().cut())
}