flaggy/spec.rs
1// Copyright 2015 Axel Rasmussen
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7// http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15use crate::error::*;
16
17/// Type denotes the particular type of flag a Spec structure describes. It
18/// also contains extra metadata about the flag, if applicable for that type.
19#[derive(Clone, Debug)]
20pub(crate) enum Type {
21 /// A required flag, meaning it must have some string value (possibly its
22 /// default value) after command-line flags have been parsed, or an error
23 /// has occurred.
24 Required {
25 /// The default value to give this flag if the user doesn't specify one.
26 /// This is optional, in which case it is an error for the user not to
27 /// specify a value explicitly.
28 default_value: Option<String>,
29 },
30 /// An optional flag - the value is treated the same way as a Required flag,
31 /// except it is *not* considered an error if there is no value associated
32 /// with it after command-line flags have been parsed.
33 Optional,
34 /// A boolean flag, which may either be on or off.
35 Boolean,
36 /// A positional flag, which, unlike all of the other flag types, must not
37 /// have its name specified explicitly (e.g. "--flag"), but which is
38 /// identified purely by its position in the list of command-line arguments.
39 Positional {
40 /// The default value(s) for this flag, if any.
41 default_value: Option<Vec<String>>,
42 /// Whether or not this flag is variadic. Variadic flags greedily scoop
43 /// up *all* of the remaining positional values at the end of the list
44 /// of command-line arguments.
45 ///
46 /// Note that because of this, it only makes sense for *one* flag to be
47 /// variadic, and it must always be the *last* Positional flag.
48 is_variadic: bool,
49 },
50}
51
52/// Spec describes a flag, in such a way that the parser can correctly identify
53/// it in the set of arguments given on the command-line.
54#[derive(Clone, Debug)]
55pub struct Spec {
56 /// The name of this flag. For non-positional flags, this must be used to
57 /// identify its value, e.g. like "--flag=value".
58 name: String,
59 /// The help string to print out for this flag when applicable.
60 help: String,
61 /// The optional short name for this flag - e.g., for a flag named "flag",
62 /// it may be convenient to let users alternatively identify it by "--f".
63 short_name: Option<char>,
64 /// The Type of flag, which identifies how its value should be identified,
65 /// how it should be parsed, etc.
66 flag_type: Type,
67}
68
69impl Spec {
70 /// Constructs a Spec which describes a required named flag. This flag may
71 /// have a default value, but the key point is that it must have some value
72 /// after parsing is complete.
73 pub fn required(
74 name: &str,
75 help: &str,
76 short_name: Option<char>,
77 default_value: Option<&str>,
78 ) -> Spec {
79 Spec {
80 name: name.to_owned(),
81 help: help.to_owned(),
82 short_name: short_name,
83 flag_type: Type::Required {
84 default_value: default_value.map(|dv| dv.to_owned()),
85 },
86 }
87 }
88
89 /// Constructs a Spec which describes an optional named flag. This flag
90 /// may not have a value after we are finished parsing.
91 pub fn optional(name: &str, help: &str, short_name: Option<char>) -> Spec {
92 Spec {
93 name: name.to_owned(),
94 help: help.to_owned(),
95 short_name: short_name,
96 flag_type: Type::Optional,
97 }
98 }
99
100 /// Constructs a Spec which describes a boolean named flag. Flags of this
101 /// type always have a value, and that value is either true or false,
102 /// instead of being a freeform string like other flag types.
103 ///
104 /// If this flag is not specified at all on the command line, its "default
105 /// value" is false.
106 pub fn boolean(name: &str, help: &str, short_name: Option<char>) -> Spec {
107 Spec {
108 name: name.to_owned(),
109 help: help.to_owned(),
110 short_name: short_name,
111 flag_type: Type::Boolean,
112 }
113 }
114
115 /// Constructs a Spec which describes a positional flag. Flags of this type
116 /// are not looked up by name after a "-" or "--" character, but instead
117 /// are parsed purely by their position in the list of command-line
118 /// arguments.
119 ///
120 /// This also means that the order in which positional flags are added to a
121 /// Specs structure matters for parsing.
122 ///
123 /// A positional flag is variadic if it should be able to collect more than
124 /// one value (e.g., for a command which takes a list of files to process
125 /// of unspecified length).
126 pub fn positional(
127 name: &str,
128 help: &str,
129 mut default_value: Option<&[&str]>,
130 is_variadic: bool,
131 ) -> Result<Spec> {
132 if let Some(dvs) = default_value {
133 if dvs.len() > 1 && !is_variadic {
134 return Err(Error::InvalidArgument(format!(
135 "Only variadic positional arguments can have multiple default values"
136 )));
137 }
138
139 if dvs.is_empty() {
140 default_value = None;
141 }
142 }
143
144 Ok(Spec {
145 name: name.to_owned(),
146 help: help.to_owned(),
147 short_name: None,
148 flag_type: Type::Positional {
149 default_value: default_value.map(|vs| vs.iter().map(|&v| v.to_owned()).collect()),
150 is_variadic: is_variadic,
151 },
152 })
153 }
154
155 /// Returns true if this Spec describes a boolean flag. This function is
156 /// needed because boolean flags are treated differently in some ways
157 /// during parsing.
158 pub(crate) fn is_boolean(&self) -> bool {
159 match self.flag_type {
160 Type::Boolean => true,
161 _ => false,
162 }
163 }
164
165 /// Returns true if this Spec describes a positional flag (that is, a flag
166 /// which is interpreted by its position in the command-line arguments, not
167 /// by the name it is given in the command-line arguments.
168 pub(crate) fn is_positional(&self) -> bool {
169 match self.flag_type {
170 Type::Positional { .. } => true,
171 _ => false,
172 }
173 }
174
175 /// Returns true if this Spec describes a named flag. This is equivalent to
176 /// !is_positional().
177 pub(crate) fn is_named(&self) -> bool {
178 !self.is_positional()
179 }
180
181 /// Returns true if this Spec describes a flag which has a default value
182 /// (that is, one which we will still store a value for even if it does not
183 /// appear in the command-line arguments).
184 pub(crate) fn has_default_value(&self) -> bool {
185 match self.flag_type {
186 Type::Required { ref default_value } => default_value.is_some(),
187 Type::Boolean => true,
188 Type::Positional {
189 ref default_value, ..
190 } => default_value.is_some(),
191 _ => false,
192 }
193 }
194
195 /// Returns the number of default values this flag has. For most flag types,
196 /// this will be either 0 or 1, but some flag types may support multiple
197 /// default values.
198 fn default_value_len(&self) -> usize {
199 match self.flag_type {
200 Type::Positional {
201 ref default_value, ..
202 } => match *default_value {
203 None => 0,
204 Some(ref dvs) => dvs.len(),
205 },
206 _ => match self.has_default_value() {
207 false => 0,
208 true => 1,
209 },
210 }
211 }
212
213 /// A flag is variadic if it can collect more than one value during parsing.
214 pub(crate) fn is_variadic(&self) -> bool {
215 match self.flag_type {
216 Type::Positional { is_variadic, .. } => is_variadic,
217 _ => false,
218 }
219 }
220
221 /// Returns true if this Spec must have an associated value (either one
222 /// specified or a default) after parsing command line arguments.
223 pub(crate) fn is_required(&self) -> bool {
224 if self.has_default_value() {
225 return true;
226 }
227 match self.flag_type {
228 Type::Required { .. } => true,
229 _ => false,
230 }
231 }
232
233 /// Returns this flag's full name (i.e., not the short name).
234 pub fn get_name(&self) -> &str {
235 self.name.as_str()
236 }
237
238 /// Returns the human-readable help text for this flag.
239 pub fn get_help(&self) -> &str {
240 self.help.as_str()
241 }
242
243 /// Returns this flag's short name, if it has one.
244 pub fn get_short_name(&self) -> Option<char> {
245 self.short_name.clone()
246 }
247
248 /// Returns this flag's type.
249 pub(crate) fn get_flag_type(&self) -> &Type {
250 &self.flag_type
251 }
252
253 /// Returns the default value for this Type::Required Spec, or None if it
254 /// either is some other type of Spec or does not have a default value.
255 pub(crate) fn get_required_default_value(&self) -> Option<&str> {
256 match self.flag_type {
257 Type::Required { ref default_value } => default_value.as_ref().map(|dv| dv.as_str()),
258 _ => None,
259 }
260 }
261}
262
263/// Specs is simply a list of flag Spec structures. Whereas each Spec defines
264/// the properties of a single flag, Specs defines the properties of multiple
265/// flags - generally, all of the flags assocaited with a single Command.
266#[derive(Clone, Debug)]
267pub struct Specs {
268 specs: Vec<Spec>,
269}
270
271impl Specs {
272 /// Construct a new Specs structure from the given complete list of flag
273 /// Spec structures.
274 ///
275 /// This may return an error if the Spec structures, taken together, are
276 /// invalid (for example, if *multiple* variadic flags are defined).
277 pub fn new(specs: Vec<Spec>) -> Result<Specs> {
278 if !specs
279 .iter()
280 .filter(|s| s.is_positional())
281 .skip_while(|s| !s.has_default_value())
282 .all(|s| s.has_default_value())
283 {
284 return Err(Error::InvalidArgument(format!(
285 "Positional flags after the first one with a default must also have defaults"
286 )));
287 }
288
289 if !specs
290 .iter()
291 .rev()
292 .skip_while(|s| !s.is_positional())
293 .next()
294 .map_or(true, |s| s.is_variadic() || s.default_value_len() <= 1)
295 {
296 return Err(Error::InvalidArgument(format!(
297 "The last positional flag can only have multiple default values if it is variadic"
298 )));
299 }
300
301 Ok(Specs { specs: specs })
302 }
303
304 /// Returns an Iterator over the Spec structures this Specs contains.
305 pub fn iter(&self) -> impl DoubleEndedIterator<Item = &Spec> {
306 self.specs.iter()
307 }
308
309 /// Given an iterator over a collection of Specs, locate the first Spec
310 /// which matches the given name. The given name might either be a short
311 /// name or a long name, depending on how it was specified on the command
312 /// line.
313 pub(crate) fn find_named_spec(&self, name: &str) -> Option<&Spec> {
314 let mut result: Option<&Spec> = None;
315 for s in &self.specs {
316 if s.is_named() {
317 if s.name == name {
318 result = Some(s);
319 break;
320 } else if let Some(sn) = s.short_name {
321 if name.len() == 1 && name.starts_with(sn) {
322 result = result.or_else(|| Some(s));
323 }
324 }
325 }
326 }
327 result
328 }
329}