1use crate::{
2 args::{Metadata, RawArgs},
3 error::Error,
4};
5
6#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
8pub struct ArgSpec {
9 pub name: &'static str,
11
12 pub doc: &'static str,
14
15 pub default: Option<&'static str>,
17
18 pub example: Option<&'static str>,
22}
23
24impl ArgSpec {
25 pub const DEFAULT: Self = Self {
27 name: "<ARGUMENT>",
28 doc: "",
29 default: None,
30 example: None,
31 };
32
33 pub const fn new(name: &'static str) -> Self {
35 Self {
36 name,
37 ..Self::DEFAULT
38 }
39 }
40
41 pub const fn doc(mut self, doc: &'static str) -> Self {
43 self.doc = doc;
44 self
45 }
46
47 pub const fn default(mut self, default: &'static str) -> Self {
49 self.default = Some(default);
50 self
51 }
52
53 pub const fn example(mut self, example: &'static str) -> Self {
55 self.example = Some(example);
56 self
57 }
58
59 pub fn take(self, args: &mut RawArgs) -> Arg {
61 let metadata = args.metadata();
62 args.with_record_arg(|args| {
63 if args.metadata().help_mode {
64 return if self.default.is_some() {
65 Arg::Default {
66 spec: self,
67 metadata,
68 }
69 } else if self.example.is_some() {
70 Arg::Example {
71 spec: self,
72 metadata,
73 }
74 } else {
75 Arg::None { spec: self }
76 };
77 }
78
79 for (index, raw_arg) in args.raw_args_mut().iter_mut().enumerate() {
80 if let Some(value) = raw_arg.value.take() {
81 return Arg::Positional {
82 spec: self,
83 metadata,
84 index,
85 value,
86 };
87 };
88 }
89
90 if self.default.is_some() {
91 Arg::Default {
92 spec: self,
93 metadata,
94 }
95 } else {
96 Arg::None { spec: self }
97 }
98 })
99 }
100}
101
102impl Default for ArgSpec {
103 fn default() -> Self {
104 Self::DEFAULT
105 }
106}
107
108#[derive(Debug, Clone, PartialEq, Eq, Hash)]
110#[allow(missing_docs)]
111pub enum Arg {
112 Positional {
113 spec: ArgSpec,
114 metadata: Metadata,
115 index: usize,
116 value: String,
117 },
118 Default {
119 spec: ArgSpec,
120 metadata: Metadata,
121 },
122 Example {
123 spec: ArgSpec,
124 metadata: Metadata,
125 },
126 None {
127 spec: ArgSpec,
128 },
129}
130
131impl Arg {
132 pub fn spec(&self) -> ArgSpec {
134 match self {
135 Arg::Positional { spec, .. }
136 | Arg::Default { spec, .. }
137 | Arg::Example { spec, .. }
138 | Arg::None { spec } => *spec,
139 }
140 }
141
142 pub fn is_present(&self) -> bool {
144 !matches!(self, Self::None { .. })
145 }
146
147 pub fn present(self) -> Option<Self> {
149 self.is_present().then_some(self)
150 }
151
152 pub fn then<F, T, E>(self, f: F) -> Result<T, Error>
179 where
180 F: FnOnce(Self) -> Result<T, E>,
181 E: std::fmt::Display,
182 {
183 if !self.is_present() {
184 return Err(Error::MissingArg {
185 arg: Box::new(self),
186 });
187 }
188 f(self.clone()).map_err(|e| Error::InvalidArg {
189 arg: Box::new(self),
190 reason: e.to_string(),
191 })
192 }
193
194 pub fn present_and_then<F, T, E>(self, f: F) -> Result<Option<T>, Error>
196 where
197 F: FnOnce(Self) -> Result<T, E>,
198 E: std::fmt::Display,
199 {
200 self.present().map(|arg| arg.then(f)).transpose()
201 }
202
203 pub fn value(&self) -> &str {
205 match self {
206 Arg::Positional { value, .. } => value.as_str(),
207 Arg::Default { spec, .. } => spec.default.unwrap_or(""),
208 Arg::Example { spec, .. } => spec.example.unwrap_or(""),
209 Arg::None { .. } => "",
210 }
211 }
212
213 pub fn index(&self) -> Option<usize> {
215 if let Arg::Positional { index, .. } = self {
216 Some(*index)
217 } else {
218 None
219 }
220 }
221
222 pub(crate) fn metadata(&self) -> Option<Metadata> {
223 match self {
224 Arg::Positional { metadata, .. }
225 | Arg::Default { metadata, .. }
226 | Arg::Example { metadata, .. } => Some(*metadata),
227 Arg::None { .. } => None,
228 }
229 }
230}
231
232#[cfg(test)]
233mod tests {
234 use super::*;
235
236 #[test]
237 fn required_arg() {
238 let mut args = test_args(&["test", "foo", "bar"]);
239 let arg = crate::arg("ARG");
240 assert!(matches!(
241 arg.take(&mut args),
242 Arg::Positional { index: 1, .. }
243 ));
244 assert!(matches!(
245 arg.take(&mut args),
246 Arg::Positional { index: 2, .. }
247 ));
248 assert!(matches!(arg.take(&mut args), Arg::None { .. }));
249 }
250
251 #[test]
252 fn optional_arg() {
253 let mut args = test_args(&["test", "foo"]);
254 let arg = crate::arg("ARG").default("bar");
255 assert!(matches!(
256 arg.take(&mut args),
257 Arg::Positional { index: 1, .. }
258 ));
259 assert!(matches!(arg.take(&mut args), Arg::Default { .. }));
260 assert!(matches!(arg.take(&mut args), Arg::Default { .. }));
261 }
262
263 #[test]
264 fn example_arg() {
265 let mut args = test_args(&["test", "foo"]);
266 args.metadata_mut().help_mode = true;
267
268 let arg = crate::arg("ARG").example("bar");
269 assert!(matches!(arg.take(&mut args), Arg::Example { .. }));
270 assert!(matches!(arg.take(&mut args), Arg::Example { .. }));
271 }
272
273 #[test]
274 fn parse_arg() {
275 let mut args = test_args(&["test", "1", "not a number"]);
276 let arg = crate::arg("ARG");
277 assert_eq!(
278 arg.take(&mut args)
279 .then(|a| a.value().parse::<usize>())
280 .ok(),
281 Some(1)
282 );
283 assert_eq!(
284 arg.take(&mut args)
285 .then(|a| a.value().parse::<usize>())
286 .ok(),
287 None
288 );
289 assert_eq!(
290 arg.take(&mut args)
291 .then(|a| a.value().parse::<usize>())
292 .ok(),
293 None
294 );
295 }
296
297 fn test_args(raw_args: &[&str]) -> RawArgs {
298 RawArgs::new(raw_args.iter().map(|a| a.to_string()))
299 }
300}