use winnow::combinator::{alt, repeat, trace};
use winnow::error::{ModalError as _, ParserError as _};
use winnow::prelude::*;
use crate::error::Error;
use crate::stream::Argv;
use crate::token::{self, Arg, ValueOptions};
use crate::value::FromArg;
use winnow::stream::BStr;
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub struct Named<const N: usize = 1> {
short: Option<char>,
longs: [Option<&'static str>; N],
options: ValueOptions,
}
pub const fn short(c: char) -> Named {
Named {
short: Some(c),
longs: [None],
options: ValueOptions::DEFAULT,
}
}
pub const fn long(name: &'static str) -> Named {
Named {
short: None,
longs: [Some(name)],
options: ValueOptions::DEFAULT,
}
}
impl<const N: usize> Named<N> {
pub const fn short(mut self, c: char) -> Self {
self.short = Some(c);
self
}
pub const fn long(self, name: &'static str) -> Named {
Named {
short: self.short,
longs: [Some(name)],
options: self.options,
}
}
pub const fn longs<const M: usize>(self, names: [&'static str; M]) -> Named<M> {
let mut longs = [None; M];
let mut i = 0;
while i < M {
longs[i] = Some(names[i]);
i += 1;
}
Named {
short: self.short,
longs,
options: self.options,
}
}
pub const fn allow_hyphen_values(mut self) -> Self {
self.options.hyphen_values = true;
self
}
pub fn negated_by<'i, const M: usize>(
self,
no: Named<M>,
) -> impl Parser<Argv<'i>, bool, Error> {
trace(
"negated_by",
alt((self.switch().value(true), no.switch().value(false))),
)
}
pub const fn require_equals(mut self) -> Self {
self.options.require_equals = true;
self
}
pub const fn allow_negative_numbers(mut self) -> Self {
self.options.negative_numbers = true;
self
}
#[inline]
pub fn matches(&self, arg: &Arg<'_>) -> bool {
match arg {
Arg::Long(f) => self
.longs
.iter()
.any(|l| l.is_some_and(|l| f.name == l.as_bytes())),
Arg::Short(f) => self.short == Some(f.letter),
Arg::Word(_) | Arg::Separator { .. } => false,
}
}
pub fn display(&self) -> String {
match (self.longs.first().copied().flatten(), self.short) {
(Some(l), _) => format!("--{l}"),
(None, Some(c)) => format!("-{c}"),
(None, None) => String::new(),
}
}
pub fn missing(&self, input: &Argv<'_>) -> Error {
Error::missing_required(input.offset(), self.display())
}
pub fn switch<'i>(mut self) -> impl Parser<Argv<'i>, (), Error> {
trace("switch", move |input: &mut Argv<'i>| {
self.parse_next(input)?.switch().parse_next(input)
})
}
pub fn argument<'i>(mut self) -> impl Parser<Argv<'i>, &'i BStr, Error> {
trace("argument", move |input: &mut Argv<'i>| {
let arg = self.parse_next(input)?;
arg.read_value_with(input, self.options)
})
}
pub fn argument_as<'i, T: FromArg>(mut self) -> impl Parser<Argv<'i>, T, Error> {
trace("argument_as", move |input: &mut Argv<'i>| {
self.parse_next(input)?
.value_as_with(self.options)
.parse_next(input)
})
}
pub fn argument_or<'i, T: FromArg>(
mut self,
missing: &'static str,
) -> impl Parser<Argv<'i>, T, Error> {
trace("argument_or", move |input: &mut Argv<'i>| {
let arg = self.parse_next(input)?;
let value = arg.read_value_or_with(input, self.options, BStr::new(missing));
arg.convert(value)
})
}
pub fn argument_opt<'i>(mut self) -> impl Parser<Argv<'i>, Option<&'i BStr>, Error> {
trace("argument_opt", move |input: &mut Argv<'i>| {
let arg = self.parse_next(input)?;
Ok(arg.read_value_opt_with(input, self.options))
})
}
pub fn argument_opt_as<'i, T: FromArg>(mut self) -> impl Parser<Argv<'i>, Option<T>, Error> {
trace("argument_opt_as", move |input: &mut Argv<'i>| {
let arg = self.parse_next(input)?;
arg.read_value_opt_with(input, self.options)
.map(|v| arg.convert(v))
.transpose()
})
}
pub fn arguments_as<'i, T: FromArg>(
mut self,
delimiter: u8,
) -> impl Parser<Argv<'i>, Vec<T>, Error> {
trace("arguments_as", move |input: &mut Argv<'i>| {
let arg = self.parse_next(input)?;
let value = arg.read_value_with(input, self.options)?;
token::split(value, delimiter)
.map(|v| arg.convert(v))
.collect()
})
}
}
impl<'i, const N: usize> Parser<Argv<'i>, Arg<'i>, Error> for Named<N> {
#[inline]
fn parse_next(&mut self, input: &mut Argv<'i>) -> Result<Arg<'i>, Error> {
token::arg
.verify(|arg: &Arg<'i>| self.matches(arg))
.parse_next(input)
}
}
pub fn positional<'i, T: FromArg>(name: &'static str) -> impl Parser<Argv<'i>, T, Error> {
trace("positional", move |input: &mut Argv<'i>| {
token::word(input)?.value_as(name).parse_next(input)
})
}
pub trait Names {
fn contains(&self, word: &[u8]) -> bool;
}
impl Names for &str {
#[inline]
fn contains(&self, word: &[u8]) -> bool {
self.as_bytes() == word
}
}
impl<const N: usize> Names for [&str; N] {
#[inline]
fn contains(&self, word: &[u8]) -> bool {
self.iter().any(|name| name.as_bytes() == word)
}
}
pub fn command<'i, O, P>(name: impl Names, mut inner: P) -> impl Parser<Argv<'i>, O, Error>
where
P: Parser<Argv<'i>, O, Error>,
{
trace("command", move |input: &mut Argv<'i>| {
let start = input.checkpoint();
match token::word(input) {
Ok(word) if name.contains(word.value) && !word.after_separator => {
inner.parse_next(input).map_err(|e| e.cut())
}
Ok(_) => {
input.reset(&start);
Err(Error::from_input(input))
}
Err(e) => Err(e),
}
})
}
pub fn args<'i, P>(mut item: P) -> impl Parser<Argv<'i>, (), Error>
where
P: Parser<Argv<'i>, (), Error>,
{
trace("args", move |input: &mut Argv<'i>| {
let separator = token::arg
.verify(|a: &Arg<'i>| matches!(a, Arg::Separator { .. }))
.void();
repeat::<_, _, (), _, _>(0.., winnow::combinator::alt((item.by_ref(), separator)))
.parse_next(input)?;
token::finish(input).map_err(|e| e.cut())
})
}