1use crate::{Error, Opt};
2use core::fmt;
3
4#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
17pub struct Lossy<'a>(pub &'a [u8]);
18
19impl fmt::Display for Lossy<'_> {
20 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
21 for chunk in self.0.utf8_chunks() {
22 f.write_str(chunk.valid())?;
23 for byte in chunk.invalid() {
24 write!(f, "\\x{byte:02X}")?;
25 }
26 }
27 Ok(())
28 }
29}
30
31pub trait Input<'a>: getargs::Argument + 'a {
33 fn bytes(self) -> &'a [u8];
35
36 fn text(self) -> Result<&'a str, &'a [u8]>;
38
39 fn short(short: Self::ShortOpt) -> char;
41
42 fn split_once(self, delimiter: char) -> Option<(Self, Self)>;
44}
45
46impl<'a, 'b: 'a> Input<'a> for &'b str {
47 fn bytes(self) -> &'a [u8] {
48 self.as_bytes()
49 }
50
51 fn text(self) -> Result<&'a str, &'a [u8]> {
52 Ok(self)
53 }
54
55 fn short(short: char) -> char {
56 short
57 }
58
59 fn split_once(self, delimiter: char) -> Option<(Self, Self)> {
60 str::split_once(self, delimiter)
61 }
62}
63
64impl<'a, 'b: 'a> Input<'a> for &'b [u8] {
65 fn bytes(self) -> &'a [u8] {
66 self
67 }
68
69 fn text(self) -> Result<&'a str, &'a [u8]> {
70 core::str::from_utf8(self).map_err(|_| self)
71 }
72
73 fn short(short: u8) -> char {
74 if short.is_ascii() {
75 char::from(short)
76 } else {
77 char::REPLACEMENT_CHARACTER
78 }
79 }
80
81 fn split_once(self, delimiter: char) -> Option<(Self, Self)> {
82 let mut buffer = [0; 4];
83 let delimiter = delimiter.encode_utf8(&mut buffer).as_bytes();
84 let index = self
85 .windows(delimiter.len())
86 .position(|window| window == delimiter)?;
87 Some((&self[..index], &self[index + delimiter.len()..]))
88 }
89}
90
91#[derive(Debug, Clone, Copy, PartialEq, Eq)]
93pub enum Arg<'a, A> {
94 Short(char),
96 Long(&'a str),
98 Positional(A),
100}
101
102pub fn text<'a, V: Input<'a>>(value: V, name: &'static str) -> Result<&'a str, Error<'a>> {
104 value.text().map_err(|value| Error::InvalidUtf8 {
105 name: Some(name),
106 value,
107 })
108}
109
110pub fn unnamed<'a, V: Input<'a>>(value: V) -> Result<&'a str, Error<'a>> {
112 value
113 .text()
114 .map_err(|value| Error::InvalidUtf8 { name: None, value })
115}
116
117pub fn bytes<'a, V: Input<'a>>(value: V) -> &'a [u8] {
119 value.bytes()
120}
121
122pub fn arg<'a, A: Input<'a>>(argument: getargs::Arg<A>) -> Result<Arg<'a, A>, Error<'a>> {
124 Ok(match argument {
125 getargs::Arg::Short(short) => Arg::Short(A::short(short)),
126 getargs::Arg::Long(long) => Arg::Long(unnamed(long)?),
127 getargs::Arg::Positional(value) => Arg::Positional(value),
128 })
129}
130
131pub fn opt<'a, A: Input<'a>>(option: getargs::Opt<A>) -> Result<Opt<'a>, Error<'a>> {
133 Ok(match option {
134 getargs::Opt::Short(short) => Opt::Short(A::short(short)),
135 getargs::Opt::Long(long) => Opt::Long(unnamed(long)?),
136 })
137}
138
139pub fn unexpected_argument<'a, V: Input<'a>>(value: V) -> Error<'a> {
141 match value.text() {
142 Ok(value) => Error::UnexpectedArgument(value),
143 Err(value) => Error::InvalidUtf8 { name: None, value },
144 }
145}
146
147pub fn unknown_subcommand<'a, V: Input<'a>>(value: V, names: &[&[&'static str]]) -> Error<'a> {
150 match value.text() {
151 Ok(name) => Error::UnknownSubcommand {
152 name,
153 suggestion: crate::suggest::suggest(name, names),
154 },
155 Err(value) => Error::InvalidUtf8 { name: None, value },
156 }
157}