1use std::ffi::OsString;
19use std::path::PathBuf;
20
21use winnow::stream::BStr;
22
23use crate::error::BoxError;
24
25pub trait FromArg: Sized {
55 const CHOICES: &'static [&'static str] = &[];
59
60 #[doc(hidden)]
63 const SPEC: &'static str = "";
64
65 const HINT: crate::complete::Hint = crate::complete::Hint::Nothing;
71
72 fn from_arg(value: &BStr) -> Result<Self, BoxError>;
74}
75
76#[derive(Debug, Clone, Copy, PartialEq, Eq)]
79pub struct ChoiceError {
80 pub choices: &'static [&'static str],
82}
83
84impl std::fmt::Display for ChoiceError {
85 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
86 write!(f, "expected one of {}", self.choices.join(", "))
87 }
88}
89
90impl std::error::Error for ChoiceError {}
91
92pub fn to_str(value: &BStr) -> Result<&str, std::str::Utf8Error> {
94 std::str::from_utf8(value)
95}
96
97impl FromArg for String {
98 const HINT: crate::complete::Hint = crate::complete::Hint::Files;
99
100 fn from_arg(value: &BStr) -> Result<Self, BoxError> {
101 Ok(to_str(value)?.to_owned())
102 }
103}
104
105impl FromArg for Vec<u8> {
106 fn from_arg(value: &BStr) -> Result<Self, BoxError> {
107 Ok(value.to_vec())
108 }
109}
110
111impl FromArg for OsString {
112 const HINT: crate::complete::Hint = crate::complete::Hint::Files;
113
114 #[cfg(unix)]
115 fn from_arg(value: &BStr) -> Result<Self, BoxError> {
116 use std::os::unix::ffi::OsStrExt as _;
117 Ok(std::ffi::OsStr::from_bytes(value).to_owned())
118 }
119
120 #[cfg(not(unix))]
121 fn from_arg(value: &BStr) -> Result<Self, BoxError> {
122 Ok(to_str(value)?.into())
123 }
124}
125
126impl FromArg for PathBuf {
127 const HINT: crate::complete::Hint = crate::complete::Hint::Files;
128
129 fn from_arg(value: &BStr) -> Result<Self, BoxError> {
130 OsString::from_arg(value).map(PathBuf::from)
131 }
132}
133
134macro_rules! from_str {
135 ($($ty:ty),* $(,)?) => {$(
136 impl FromArg for $ty {
137 fn from_arg(value: &BStr) -> Result<Self, BoxError> {
138 Ok(to_str(value)?.parse::<$ty>()?)
139 }
140 }
141 )*};
142}
143
144from_str!(
145 bool,
146 char,
147 u8,
148 u16,
149 u32,
150 u64,
151 u128,
152 usize,
153 i8,
154 i16,
155 i32,
156 i64,
157 i128,
158 isize,
159 f32,
160 f64,
161 std::net::IpAddr,
162 std::net::Ipv4Addr,
163 std::net::Ipv6Addr,
164 std::net::SocketAddr,
165);
166
167#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord, Default)]
183pub struct Parsed<T>(pub T);
184
185impl<T> FromArg for Parsed<T>
186where
187 T: std::str::FromStr,
188 T::Err: Into<BoxError>,
189{
190 fn from_arg(value: &BStr) -> Result<Self, BoxError> {
191 to_str(value)?.parse().map(Parsed).map_err(Into::into)
192 }
193}
194
195#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
202pub struct Spanned<T> {
203 pub value: T,
205 pub offset: usize,
208 pub attached: bool,
210}
211
212#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
216pub struct CInt<T>(pub T);
217
218#[derive(Debug, Clone, PartialEq, Eq)]
220pub struct CIntError(&'static str);
221
222impl std::fmt::Display for CIntError {
223 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
224 f.write_str(self.0)
225 }
226}
227
228impl std::error::Error for CIntError {}
229
230fn c_digits(text: &str) -> Result<(bool, u32, &str), CIntError> {
232 let (negative, rest) = match text.strip_prefix('-') {
233 Some(rest) => (true, rest),
234 None => (false, text.strip_prefix('+').unwrap_or(text)),
235 };
236 let (radix, digits) =
237 if let Some(hex) = rest.strip_prefix("0x").or_else(|| rest.strip_prefix("0X")) {
238 (16, hex)
239 } else if rest.len() > 1 && rest.starts_with('0') {
240 (8, &rest[1..])
241 } else {
242 (10, rest)
243 };
244 if digits.is_empty() || !digits.chars().all(|c| c.is_digit(radix)) {
245 return Err(CIntError("expected a number: decimal, 0x hex or 0 octal"));
246 }
247 Ok((negative, radix, digits))
248}
249
250macro_rules! c_int {
251 ($($ty:ty),* $(,)?) => {$(
252 impl FromArg for CInt<$ty> {
253 fn from_arg(value: &BStr) -> Result<Self, BoxError> {
254 let (negative, radix, digits) = c_digits(to_str(value)?)?;
255 let magnitude = u128::from_str_radix(digits, radix)?;
256 let n = if negative {
257 0i128.checked_sub_unsigned(magnitude).and_then(|n| <$ty>::try_from(n).ok())
258 } else {
259 <$ty>::try_from(magnitude).ok()
260 };
261 n.map(CInt).ok_or_else(|| CIntError("number out of range").into())
262 }
263 }
264 )*};
265}
266
267c_int!(u8, u16, u32, u64, usize, i8, i16, i32, i64, isize);
268
269#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
273pub struct KeyValue<K, V> {
274 pub key: K,
276 pub value: V,
278}
279
280#[derive(Debug, Clone, Copy, PartialEq, Eq)]
282pub struct MissingEquals;
283
284impl std::fmt::Display for MissingEquals {
285 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
286 f.write_str("expected `key=value`")
287 }
288}
289
290impl std::error::Error for MissingEquals {}
291
292impl<K: FromArg, V: FromArg> FromArg for KeyValue<K, V> {
293 fn from_arg(value: &BStr) -> Result<Self, BoxError> {
294 let eq = value.iter().position(|&b| b == b'=').ok_or(MissingEquals)?;
295 Ok(KeyValue {
296 key: K::from_arg(BStr::new(&value[..eq]))?,
297 value: V::from_arg(BStr::new(&value[eq + 1..]))?,
298 })
299 }
300}
301
302#[doc(hidden)]
306pub const fn documented_choices<T: FromArg>(path: &str) -> bool {
307 if T::SPEC.is_empty() {
308 T::CHOICES.is_empty()
309 } else {
310 filed_as(T::SPEC, path)
311 }
312}
313
314#[doc(hidden)]
325pub const fn filed_as(spec: &str, path: &str) -> bool {
326 let (spec, path) = (spec.as_bytes(), path.as_bytes());
327 if spec.is_empty() {
328 return true;
329 }
330 let (mut first, mut last, mut at) = (spec.len(), 0, 0);
332 while at < spec.len() {
333 if spec[at] == b':' {
334 if first == spec.len() {
335 first = at;
336 }
337 last = at + 1;
338 }
339 at += 1;
340 }
341 let mut name = path.len();
343 while name > 0 && path[name - 1] != b':' {
344 name -= 1;
345 }
346 if !same_bytes(spec, last, spec.len(), path, name, path.len()) {
347 return false;
348 }
349 if first == spec.len() || name == 0 {
350 return true;
351 }
352 let to = name - 2;
354 if starts_with(path, b"crate::") || first + 2 >= last {
355 return true;
356 }
357 if among_modules(spec, first + 1, last - 1, path, 0, to) {
358 return true;
359 }
360 let mut end = 0;
361 while end < to && path[end] != b':' {
362 end += 1;
363 }
364 same_bytes(spec, 0, first, path, 0, end)
365}
366
367const fn among_modules(
370 spec: &[u8],
371 start: usize,
372 mut end: usize,
373 path: &[u8],
374 from: usize,
375 to: usize,
376) -> bool {
377 loop {
378 if ends_with_modules(spec, start, end, path, from, to) {
379 return true;
380 }
381 while end > start && spec[end - 1] != b'/' {
382 end -= 1;
383 }
384 if end == start {
385 return false;
386 }
387 end -= 1;
388 }
389}
390
391const fn ends_with_modules(
393 spec: &[u8],
394 start: usize,
395 mut end: usize,
396 path: &[u8],
397 from: usize,
398 mut to: usize,
399) -> bool {
400 loop {
401 let (mut module, mut written) = (end, to);
402 while module > start && spec[module - 1] != b'/' {
403 module -= 1;
404 }
405 while written > from && path[written - 1] != b':' {
406 written -= 1;
407 }
408 if !same_bytes(spec, module, end, path, written, to) {
409 return false;
410 }
411 if written == from {
412 return true;
413 }
414 if module == start {
415 return false;
416 }
417 (end, to) = (module - 1, written - 2);
418 }
419}
420
421const fn starts_with(text: &[u8], prefix: &[u8]) -> bool {
422 text.len() >= prefix.len() && same_bytes(text, 0, prefix.len(), prefix, 0, prefix.len())
423}
424
425const fn same_bytes(a: &[u8], from: usize, to: usize, b: &[u8], start: usize, end: usize) -> bool {
426 if to - from != end - start {
427 return false;
428 }
429 let mut at = 0;
430 while at < to - from {
431 if a[from + at] != b[start + at] {
432 return false;
433 }
434 at += 1;
435 }
436 true
437}
438
439#[doc(hidden)]
442pub const fn stated_choices<T: FromArg>(stated: &[&str]) -> bool {
443 if T::CHOICES.is_empty() {
444 return true;
445 }
446 if T::CHOICES.len() != stated.len() {
447 return false;
448 }
449 let mut at = 0;
450 while at < stated.len() {
451 if !same(T::CHOICES[at], stated[at]) {
452 return false;
453 }
454 at += 1;
455 }
456 true
457}
458
459const fn same(a: &str, b: &str) -> bool {
460 same_bytes(a.as_bytes(), 0, a.len(), b.as_bytes(), 0, b.len())
461}
462
463#[cfg(test)]
464mod tests {
465 use super::*;
466
467 #[test]
468 fn a_path_leads_to_a_type_by_its_modules_or_its_crate() {
469 let spec = "app:src/cli/add:Opts";
470 for path in [
471 "Opts",
472 "add::Opts",
473 "cli::add::Opts",
474 "cli::Opts",
475 "crate::Opts",
476 "crate::cli::add::Opts",
477 "app::Opts",
478 "app::cli::Opts",
479 "app::src::cli::add::Opts",
480 "app::prelude::Opts",
481 "crate::prelude::Opts",
482 ] {
483 assert!(filed_as(spec, path), "{path}");
484 }
485 for path in [
486 "AddOpts",
487 "remove::Opts",
488 "add::cli::Opts",
489 "src::add::Opts",
490 "dep::Opts",
491 "dep::cli::add::Opts",
492 "std::net::Opts",
493 ] {
494 assert!(!filed_as(spec, path), "{path}");
495 }
496 assert!(filed_as("Opts", "anywhere::Opts"));
498 assert!(!filed_as("Opts", "anywhere::Other"));
499 assert!(filed_as("", "anywhere::Opts"));
500 assert!(filed_as("app::Opts", "x::Opts"));
501 }
502
503 fn c<T>(text: &str) -> Result<T, String>
504 where
505 CInt<T>: FromArg,
506 {
507 CInt::<T>::from_arg(BStr::new(text))
508 .map(|CInt(n)| n)
509 .map_err(|e| e.to_string())
510 }
511
512 #[test]
513 fn c_integers() {
514 assert_eq!(c::<u64>("0x400000"), Ok(0x40_0000));
515 assert_eq!(c::<u64>("0X1f"), Ok(31));
516 assert_eq!(c::<u64>("010"), Ok(8));
517 assert_eq!(c::<u64>("0"), Ok(0));
518 assert_eq!(c::<u64>("4096"), Ok(4096));
519 assert_eq!(c::<i64>("-0x10"), Ok(-16));
520 assert_eq!(c::<i8>("-128"), Ok(-128));
521 assert!(c::<u8>("256").is_err());
522 assert!(c::<u64>("-1").is_err());
523 assert!(c::<u64>("08").is_err());
524 assert!(c::<u64>("0x").is_err());
525 assert!(c::<u64>("").is_err());
526 }
527
528 #[test]
529 fn key_values() {
530 let kv = KeyValue::<String, String>::from_arg(BStr::new("sym=a=b")).unwrap();
531 assert_eq!((kv.key.as_str(), kv.value.as_str()), ("sym", "a=b"));
532 let kv = KeyValue::<String, CInt<u64>>::from_arg(BStr::new(".text=0x1000")).unwrap();
533 assert_eq!((kv.key.as_str(), kv.value.0), (".text", 0x1000));
534 assert!(KeyValue::<String, String>::from_arg(BStr::new("nothing")).is_err());
535 }
536}