1use crate::{Error, Result};
5use std::collections::BTreeMap;
6use std::fmt::Display;
7use std::str::FromStr;
8
9pub struct Parameter {
11 pub(crate) name: String,
12 pub(crate) type_name: &'static str,
13 pub(crate) positional: bool,
14 pub(crate) required: bool,
15 pub(crate) repeated: bool,
16 pub(crate) default: Option<String>,
17 pub(crate) description: String,
18 validate: fn(&str) -> Result<()>,
19}
20
21impl Parameter {
22 pub fn new<Value: FromStr>(name: &str) -> Self
24 where
25 Value::Err: Display,
26 {
27 Self {
28 name: name.into(),
29 type_name: std::any::type_name::<Value>(),
30 positional: true,
31 required: true,
32 repeated: false,
33 default: None,
34 description: String::new(),
35 validate: |value| parse::<Value>(value).map(|_| ()),
36 }
37 }
38 pub fn named(mut self) -> Self {
39 self.positional = false;
40 self
41 }
42 pub fn optional(mut self) -> Self {
43 self.required = false;
44 self
45 }
46 pub fn repeated(mut self) -> Self {
47 self.repeated = true;
48 self.positional = false;
49 self.required = false;
50 self
51 }
52 pub fn default(mut self, value: impl Into<String>) -> Self {
53 self.default = Some(value.into());
54 self.required = false;
55 self.positional = false;
56 self
57 }
58 pub fn help(mut self, description: impl Into<String>) -> Self {
59 self.description = description.into();
60 self
61 }
62 pub fn name(&self) -> &str {
63 &self.name
64 }
65}
66
67fn parse<Value: FromStr>(value: &str) -> Result<Value>
68where
69 Value::Err: Display,
70{
71 value.parse().map_err(|error| {
72 Error::new(format!(
73 "invalid {} value {value:?}: {error}",
74 std::any::type_name::<Value>()
75 ))
76 })
77}
78
79#[derive(Default, Debug)]
81pub struct Arguments(BTreeMap<String, Vec<String>>);
82
83impl Arguments {
84 pub fn required<Value: FromStr>(&self, name: &str) -> Result<Value>
85 where
86 Value::Err: Display,
87 {
88 self.optional(name)?
89 .ok_or_else(|| Error::new(format!("missing argument {name:?}")))
90 }
91 pub fn optional<Value: FromStr>(&self, name: &str) -> Result<Option<Value>>
92 where
93 Value::Err: Display,
94 {
95 self.0
96 .get(name)
97 .and_then(|values| values.first())
98 .map(|value| parse(value))
99 .transpose()
100 }
101 pub fn repeated<Value: FromStr>(&self, name: &str) -> Result<Vec<Value>>
102 where
103 Value::Err: Display,
104 {
105 self.0
106 .get(name)
107 .into_iter()
108 .flatten()
109 .map(|value| parse(value))
110 .collect()
111 }
112
113 fn insert(&mut self, parameter: &Parameter, value: &str) -> Result<()> {
114 if !parameter.repeated && self.0.contains_key(¶meter.name) {
115 return Err(Error::new(format!(
116 "argument {:?} was supplied more than once",
117 parameter.name
118 )));
119 }
120 (parameter.validate)(value)
121 .map_err(|error| Error::new(format!("{}: {error}", parameter.name)))?;
122 self.0
123 .entry(parameter.name.clone())
124 .or_default()
125 .push(value.into());
126 Ok(())
127 }
128
129 pub(crate) fn extract(parameters: &[Parameter], tokens: &[String]) -> Result<(Self, usize)> {
130 let mut arguments = Self::default();
131 let mut consumed = 0;
132 let mut options = true;
133 while let Some(token) = tokens.get(consumed) {
134 if token == "::" {
135 break;
136 }
137 if options && token == "--" {
138 options = false;
139 consumed += 1;
140 continue;
141 }
142 let named = if options {
143 token.strip_prefix("--")
144 } else {
145 None
146 };
147 if let Some(name) = named {
148 if name.contains('=') {
149 return Err(Error::new(
150 "named arguments use separate `--name value` tokens",
151 ));
152 }
153 let name = name.replace('-', "_");
154 let parameter = parameters
155 .iter()
156 .find(|parameter| parameter.name == name)
157 .ok_or_else(|| Error::new(format!("unknown argument {name:?}")))?;
158 consumed += 1;
159 let value = tokens
160 .get(consumed)
161 .filter(|value| value.as_str() != "::" && value.as_str() != "--")
162 .ok_or_else(|| Error::new(format!("argument {name:?} requires a value")))?;
163 consumed += 1;
164 arguments.insert(parameter, value)?;
165 } else if let Some(parameter) = parameters.iter().find(|parameter| {
166 parameter.positional && !arguments.0.contains_key(¶meter.name)
167 }) {
168 arguments.insert(parameter, token)?;
169 consumed += 1;
170 } else {
171 break;
172 }
173 }
174 for parameter in parameters {
175 if parameter.required && !arguments.0.contains_key(¶meter.name) {
176 return Err(Error::new(format!("missing argument {:?}", parameter.name)));
177 }
178 }
179 Ok((arguments, consumed))
180 }
181}