1use winnow::combinator::{alt, repeat, trace};
37use winnow::error::{ModalError as _, ParserError as _};
38use winnow::prelude::*;
39
40use crate::error::Error;
41use crate::stream::Argv;
42use crate::token::{self, Arg, ValueOptions};
43use crate::value::FromArg;
44use winnow::stream::BStr;
45
46#[derive(Copy, Clone, Debug, PartialEq, Eq)]
53pub struct Named<const N: usize = 1> {
54 short: Option<char>,
55 longs: [Option<&'static str>; N],
56 options: ValueOptions,
58}
59
60pub const fn short(c: char) -> Named {
62 Named {
63 short: Some(c),
64 longs: [None],
65 options: ValueOptions::DEFAULT,
66 }
67}
68
69pub const fn long(name: &'static str) -> Named {
71 Named {
72 short: None,
73 longs: [Some(name)],
74 options: ValueOptions::DEFAULT,
75 }
76}
77
78impl<const N: usize> Named<N> {
79 pub const fn short(mut self, c: char) -> Self {
81 self.short = Some(c);
82 self
83 }
84
85 pub const fn long(self, name: &'static str) -> Named {
87 Named {
88 short: self.short,
89 longs: [Some(name)],
90 options: self.options,
91 }
92 }
93
94 pub const fn longs<const M: usize>(self, names: [&'static str; M]) -> Named<M> {
97 let mut longs = [None; M];
98 let mut i = 0;
99 while i < M {
100 longs[i] = Some(names[i]);
101 i += 1;
102 }
103 Named {
104 short: self.short,
105 longs,
106 options: self.options,
107 }
108 }
109
110 pub const fn allow_hyphen_values(mut self) -> Self {
112 self.options.hyphen_values = true;
113 self
114 }
115
116 pub fn negated_by<'i, const M: usize>(
119 self,
120 no: Named<M>,
121 ) -> impl Parser<Argv<'i>, bool, Error> {
122 trace(
123 "negated_by",
124 alt((self.switch().value(true), no.switch().value(false))),
125 )
126 }
127
128 pub const fn require_equals(mut self) -> Self {
130 self.options.require_equals = true;
131 self
132 }
133
134 pub const fn allow_negative_numbers(mut self) -> Self {
136 self.options.negative_numbers = true;
137 self
138 }
139
140 #[inline]
142 pub fn matches(&self, arg: &Arg<'_>) -> bool {
143 match arg {
144 Arg::Long(f) => self
145 .longs
146 .iter()
147 .any(|l| l.is_some_and(|l| f.name == l.as_bytes())),
148 Arg::Short(f) => self.short == Some(f.letter),
149 Arg::Word(_) | Arg::Separator { .. } => false,
150 }
151 }
152
153 pub fn display(&self) -> String {
155 match (self.longs.first().copied().flatten(), self.short) {
156 (Some(l), _) => format!("--{l}"),
157 (None, Some(c)) => format!("-{c}"),
158 (None, None) => String::new(),
159 }
160 }
161
162 pub fn missing(&self, input: &Argv<'_>) -> Error {
164 Error::missing_required(input.offset(), self.display())
165 }
166
167 pub fn switch<'i>(mut self) -> impl Parser<Argv<'i>, (), Error> {
169 trace("switch", move |input: &mut Argv<'i>| {
170 self.parse_next(input)?.switch().parse_next(input)
171 })
172 }
173
174 pub fn argument<'i>(mut self) -> impl Parser<Argv<'i>, &'i BStr, Error> {
176 trace("argument", move |input: &mut Argv<'i>| {
177 let arg = self.parse_next(input)?;
178 arg.read_value_with(input, self.options)
179 })
180 }
181
182 pub fn argument_as<'i, T: FromArg>(mut self) -> impl Parser<Argv<'i>, T, Error> {
184 trace("argument_as", move |input: &mut Argv<'i>| {
185 self.parse_next(input)?
186 .value_as_with(self.options)
187 .parse_next(input)
188 })
189 }
190
191 pub fn argument_or<'i, T: FromArg>(
194 mut self,
195 missing: &'static str,
196 ) -> impl Parser<Argv<'i>, T, Error> {
197 trace("argument_or", move |input: &mut Argv<'i>| {
198 let arg = self.parse_next(input)?;
199 let value = arg.read_value_or_with(input, self.options, BStr::new(missing));
200 arg.convert(value)
201 })
202 }
203
204 pub fn arguments_as<'i, T: FromArg>(
207 mut self,
208 delimiter: u8,
209 ) -> impl Parser<Argv<'i>, Vec<T>, Error> {
210 trace("arguments_as", move |input: &mut Argv<'i>| {
211 let arg = self.parse_next(input)?;
212 let value = arg.read_value_with(input, self.options)?;
213 token::split(value, delimiter)
214 .map(|v| arg.convert(v))
215 .collect()
216 })
217 }
218}
219
220impl<'i, const N: usize> Parser<Argv<'i>, Arg<'i>, Error> for Named<N> {
222 #[inline]
223 fn parse_next(&mut self, input: &mut Argv<'i>) -> Result<Arg<'i>, Error> {
224 token::arg
225 .verify(|arg: &Arg<'i>| self.matches(arg))
226 .parse_next(input)
227 }
228}
229
230pub fn positional<'i, T: FromArg>(name: &'static str) -> impl Parser<Argv<'i>, T, Error> {
236 trace("positional", move |input: &mut Argv<'i>| {
237 token::word(input)?.value_as(name).parse_next(input)
238 })
239}
240
241pub trait Names {
243 fn contains(&self, word: &[u8]) -> bool;
245}
246
247impl Names for &str {
248 #[inline]
249 fn contains(&self, word: &[u8]) -> bool {
250 self.as_bytes() == word
251 }
252}
253
254impl<const N: usize> Names for [&str; N] {
255 #[inline]
256 fn contains(&self, word: &[u8]) -> bool {
257 self.iter().any(|name| name.as_bytes() == word)
258 }
259}
260
261pub fn command<'i, O, P>(name: impl Names, mut inner: P) -> impl Parser<Argv<'i>, O, Error>
267where
268 P: Parser<Argv<'i>, O, Error>,
269{
270 trace("command", move |input: &mut Argv<'i>| {
271 let start = input.checkpoint();
272 match token::word(input) {
273 Ok(word) if name.contains(word.value) && !word.after_separator => {
274 inner.parse_next(input).map_err(|e| e.cut())
275 }
276 Ok(_) => {
277 input.reset(&start);
278 Err(Error::from_input(input))
279 }
280 Err(e) => Err(e),
281 }
282 })
283}
284
285pub fn args<'i, P>(mut item: P) -> impl Parser<Argv<'i>, (), Error>
292where
293 P: Parser<Argv<'i>, (), Error>,
294{
295 trace("args", move |input: &mut Argv<'i>| {
296 let separator = token::arg
297 .verify(|a: &Arg<'i>| matches!(a, Arg::Separator { .. }))
298 .void();
299 repeat::<_, _, (), _, _>(0.., winnow::combinator::alt((item.by_ref(), separator)))
300 .parse_next(input)?;
301 token::finish(input).map_err(|e| e.cut())
302 })
303}