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}