args/
lib.rs

1// Copyright 2016 Matthew Fornaciari <mattforni@gmail.com>
2//! A dead simple implementation of command line argument parsing and validation
3//! built on top of the [getopts](https://crates.io/crates/getopts) crate.
4//!
5//! In order to use the `args` crate simply create an `Args` object and begin
6//! registering possible command line options via the `flag(...)` and `option(...)`
7//! methods. Once all options have been registered, parse arguments directly from the
8//! command line, or provide a vector of your own arguments.
9//!
10//! Any errors encountered during parsing will be returned wrapped in an `ArgsError`.
11//! If there are no errors during parsing values may be retrieved from the `args`
12//! object by simply calling `value_of(...)` and `validated_value_of(...)`.
13//!
14//! That's it!
15//!
16//! # Usage
17//!
18//! This crate is [on crates.io](https://crates.io/crates/args) and can be
19//! used by adding `args` to the dependencies in your project's `Cargo.toml`.
20//!
21//! ```toml
22//! [dependencies]
23//! args = "2.0"
24//! ```
25//!
26//! and this to your crate root:
27//!
28//! ```rust
29//! extern crate args;
30//! ```
31//!
32//! # Example
33//!
34//! The following example shows simple command line parsing for an application that
35//! requires a number of iterations between zero *(0)* and ten *(10)* to be specified,
36//! accepts an optional log file name and responds to the help flag.
37//!
38//! ```rust
39//! extern crate args;
40//! extern crate getopts;
41//!
42//! use getopts::Occur;
43//! use std::process::exit;
44//!
45//! use args::{Args,ArgsError};
46//! use args::validations::{Order,OrderValidation};
47//!
48//! const PROGRAM_DESC: &'static str = "Run this program";
49//! const PROGRAM_NAME: &'static str = "program";
50//!
51//! fn main() {
52//!     match parse(&vec!("-i", "5")) {
53//!         Ok(_) => println!("Successfully parsed args"),
54//!         Err(error) => {
55//!             println!("{}", error);
56//!             exit(1);
57//!         }
58//!     };
59//! }
60//!
61//! fn parse(input: &Vec<&str>) -> Result<(), ArgsError> {
62//!     let mut args = Args::new(PROGRAM_NAME, PROGRAM_DESC);
63//!     args.flag("h", "help", "Print the usage menu");
64//!     args.option("i",
65//!         "iter",
66//!         "The number of times to run this program",
67//!         "TIMES",
68//!         Occur::Req,
69//!         None);
70//!     args.option("l",
71//!         "log_file",
72//!         "The name of the log file",
73//!         "NAME",
74//!         Occur::Optional,
75//!         Some(String::from("output.log")));
76//!
77//!     try!(args.parse(input));
78//!
79//!     let help = try!(args.value_of("help"));
80//!     if help {
81//!         args.full_usage();
82//!         return Ok(());
83//!     }
84//!
85//!     let gt_0 = Box::new(OrderValidation::new(Order::GreaterThan, 0u32));
86//!     let lt_10 = Box::new(OrderValidation::new(Order::LessThanOrEqual, 10u32));
87//!
88//!     let iters = try!(args.validated_value_of("iter", &[gt_0, lt_10]));
89//!     for iter in 0..iters {
90//!         println!("Working on iteration {}", iter);
91//!     }
92//!     println!("All done.");
93//!
94//!     Ok(())
95//! }
96//! ```
97//!
98
99#![doc(html_logo_url = "https://www.rust-lang.org/logos/rust-logo-128x128-blk-v2.png",
100    html_favicon_url = "https://www.rust-lang.org/favicon.ico",
101    html_root_url = "https://doc.rust-lang.org/")]
102#![deny(missing_docs)]
103#![cfg_attr(test, deny(warnings))]
104
105#[macro_use] extern crate log;
106extern crate getopts;
107
108use getopts::{Fail,HasArg,Occur,Options};
109use std::collections::BTreeMap;
110use std::collections::btree_map::Iter;
111use std::env;
112use std::ffi::OsStr;
113use std::fmt::{self,Display,Formatter};
114use std::iter::IntoIterator;
115use std::str::FromStr;
116
117pub use self::errors::ArgsError;
118
119use self::options::Opt;
120use self::validations::Validation;
121
122pub mod traits;
123pub mod validations;
124
125mod errors;
126mod options;
127#[cfg(test)] mod tst;
128
129const COLUMN_WIDTH: usize = 20;
130const SCOPE_PARSE: &'static str = "parse";
131const SEPARATOR: &'static str = ",";
132
133/// A dead simple implementation of command line argument parsing and validation.
134pub struct Args {
135    description: String,
136    options: Options,
137    opts: BTreeMap<String, Box<Opt>>,
138    opt_names: Vec<String>,
139    program_name: String,
140    values: BTreeMap<String, String>
141}
142
143impl Args {
144    // Public associated methods
145    /// Creates an empty set of command line options.
146    pub fn new(program_name: &str, description: &str) -> Args {
147        debug!("Creating new args object for '{}'", program_name);
148
149        Args {
150            description: description.to_string(),
151            options: Options::new(),
152            opts: BTreeMap::new(),
153            opt_names: Vec::new(),
154            program_name: program_name.to_string(),
155            values: BTreeMap::new()
156        }
157    }
158
159    // Public instance methods
160    /// Registers an optional flag argument that does not take an argument and defaults to false.
161    ///
162    /// * `short_name` - e.g. `"h"` for a `-h` option, or `""` for none
163    /// * `long_name` - e.g. `"help"` for a `--help` option, or `""` for none
164    /// * `desc` - A description of the flag for the usage message
165    pub fn flag(&mut self,
166            short_name: &str,
167            long_name: &str,
168            desc: &str) -> &mut Args {
169        self.register_opt(
170            options::new(short_name,
171                long_name,
172                desc,
173                "",
174                HasArg::No,
175                Occur::Optional,
176                None
177            )
178        );
179
180        self
181    }
182
183    /// Generates a combination of the short and verbose usage messages.
184    pub fn full_usage(&self) -> String {
185        format!("{}\n\n{}", self.short_usage(), self.usage())
186    }
187
188    /// Returns a `bool` indicating whether or not any options are registered.
189    pub fn has_options(&self) -> bool {
190        !self.opts.is_empty()
191    }
192
193    /// Returns a `bool` indicating whether or not a argument is present.
194    pub fn has_value(&self, opt_name: &str) -> bool {
195        self.values.get(opt_name).is_some()
196    }
197
198    /// Returns an iterator visiting all key-value pairs in alphabetical order.
199    pub fn iter(&self) -> Iter<String, String> {
200        self.values.iter()
201    }
202
203    /// Registers an option explicitly.
204    ///
205    /// * `short_name` - e.g. `"h"` for a `-h` option, or `""` for none
206    /// * `long_name` - e.g. `"help"` for a `--help` option, or `""` for none
207    /// * `desc` - A description of the flag for the usage message
208    /// * `hint` - A hint to be used in place of the argument in the usage message,
209    /// e.g. `"FILE"` for a `-o FILE` option
210    /// * `occur` - An enum representing whether the option is required or not
211    /// * `default` - The default value for this option if there should be one
212    pub fn option(&mut self,
213            short_name: &str,
214            long_name: &str,
215            desc: &str,
216            hint: &str,
217            occur: Occur,
218            default: Option<String>) -> &mut Args {
219        self.register_opt(
220            options::new(short_name,
221                long_name,
222                desc,
223                hint,
224                HasArg::Yes,
225                occur,
226                default
227            )
228        );
229
230        self
231    }
232
233    /// Parses arguments according to the registered options.
234    ///
235    /// # Failures
236    /// Fails if any errors are encountered during parsing.
237    pub fn parse<C: IntoIterator>(&mut self, raw_args: C) -> Result<(), ArgsError> where C::Item: AsRef<OsStr> {
238        debug!("Parsing args for '{}'", self.program_name);
239
240        // Get matches and return an error if there is a problem parsing
241        let matches = match self.options.parse(raw_args) {
242            Ok(matches) => { matches },
243            Err(error) => { return Err(ArgsError::new(SCOPE_PARSE, &error.to_string())) }
244        };
245
246        // Find matches and store the values (or a default)
247        for opt_name in &self.opt_names {
248            let option = self.opts.get(opt_name);
249            if option.is_none() {
250                return Err(ArgsError::new(SCOPE_PARSE, &Fail::UnrecognizedOption(opt_name.to_string()).to_string()));
251            }
252
253            let opt = option.unwrap();
254            let value = opt.parse(&matches).unwrap_or("".to_string());
255            if !value.is_empty() {
256                self.values.insert(opt_name.to_string(), value);
257            } else {
258                if opt.is_required() {
259                    return Err(ArgsError::new(SCOPE_PARSE, &Fail::ArgumentMissing(opt_name.to_string()).to_string()));
260                }
261            }
262        }
263
264        debug!("Args: {:?}", self.values);
265        Ok(())
266    }
267
268    /// Parses arguments directly from the command line according to the registered options.
269    ///
270    /// # Failures
271    /// Fails if any errors are encountered during parsing.
272    pub fn parse_from_cli(&mut self) -> Result<(), ArgsError> {
273        // Retrieve the cli args and throw out the program name
274        let mut raw_args: Vec<String> = env::args().collect();
275        if !raw_args.is_empty() { raw_args.remove(0); }
276
277        self.parse(&mut raw_args)
278    }
279
280    /// Generates a one-line usage summary from the registered options.
281    pub fn short_usage(&self) -> String {
282        self.options.short_usage(&self.program_name)
283    }
284
285    /// Generates a verbose usage summary from the registered options.
286    pub fn usage(&self) -> String {
287        if !self.has_options() { return format!("{}\n", self.description); }
288        self.options.usage(&self.description)
289    }
290
291    /// Retrieves the optional value of the `Opt` identified by `opt_name`, casts it to
292    /// the type specified by `T`, runs all provided `Validation`s, and wraps it in an Option<T>.
293    ///
294    /// # Failures
295    ///
296    /// See `validated_value_of`
297    pub fn optional_validated_value_of<T>(&self, opt_name: &str, validations: &[Box<Validation<T=T>>])
298                                          -> Result<Option<T>, ArgsError> where T: FromStr {
299        if self.has_value(opt_name) {
300            Ok(Some(try!(self.validated_value_of::<T>(opt_name, validations))))
301        } else {
302            Ok(None)
303        }
304    }
305
306    /// Retrieves the optional value of the `Opt` identified by `opt_name`, casts it to
307    /// the type specified by `T` and wraps it in an optional.
308    ///
309    /// # Failures
310    ///
311    /// See `value_of`
312    pub fn optional_value_of<T: FromStr>(&self, opt_name: &str) -> Result<Option<T>, ArgsError> {
313        if self.has_value(opt_name) {
314            Ok(Some(try!(self.value_of::<T>(opt_name))))
315        } else {
316            Ok(None)
317        }
318    }
319
320    /// Retrieves the value of the `Opt` identified by `opt_name`, casts it to
321    /// the type specified by `T` and then runs all provided `Validation`s.
322    ///
323    /// # Failures
324    ///
325    /// Returns `Err(ArgsError)` if no `Opt` correspond to `opt_name`, if the value cannot
326    /// be cast to type `T` or if any validation is considered invalid.
327    pub fn validated_value_of<T>(&self, opt_name: &str, validations: &[Box<Validation<T=T>>])
328        -> Result<T, ArgsError> where T: FromStr {
329        // If the value does not have an error, run validations
330        self.value_of::<T>(opt_name).and_then(|value| {
331            for validation in validations {
332                // If any validations fail, break the loop and return the error
333                if validation.is_invalid(&value) { return Err(validation.error(&value)); }
334            }
335
336            Ok(value)
337        })
338    }
339
340    /// Retrieves the value for the `Opt` identified by `opt_name` and casts it to
341    /// the type specified by `T`.
342    ///
343    /// # Failures
344    ///
345    /// Returns `Err(ArgsError)` if no `Opt` corresponds to `opt_name` or if the
346    /// value cannot be cast to type `T`.
347    pub fn value_of<T: FromStr>(&self, opt_name: &str) -> Result<T, ArgsError> {
348        self.values.get(opt_name).ok_or(
349            ArgsError::new(opt_name, "does not have a value")
350        ).and_then(|value_string| {
351            T::from_str(value_string).or(
352                Err(ArgsError::new(opt_name, &format!("unable to parse '{}'", value_string)))
353            )
354        })
355    }
356
357    /// Retrieves a vector of values for the `Opt` identified by `opt_name` and
358    /// casts each of them to the type specified by `T`.
359    ///
360    /// # Failures
361    ///
362    /// Returns `Err(ArgsError)` if no `Opt` corresponds to `opt_name` or if any
363    /// of the values cannot be cast to type `T`.
364    pub fn values_of<T: FromStr>(&self, opt_name: &str) -> Result<Vec<T>, ArgsError> {
365        self.values.get(opt_name).ok_or(
366            ArgsError::new(opt_name, "does not have a value")
367        ).and_then(|values_str| {
368            values_str.split(SEPARATOR).map(|value| {
369                T::from_str(value).or(
370                    Err(ArgsError::new(opt_name, &format!("unable to parse '{}'", value)))
371                )
372            }).collect()
373        })
374    }
375
376    /// Retreives an `Option<Opt>` for the `Opt` identified by `opt_name`
377    ///
378    /// # Failures
379    ///
380    /// Returns `None` if no `Opt` corresponds to `opt_name`.
381    pub fn get_option(&self, opt_name :&str) -> Option<Box<Opt>> {
382        self.opts.get(opt_name).map(|opt| opt.clone())
383    }
384
385    // Private instance methods
386    fn register_opt(&mut self, opt: Box<Opt>) {
387        if !self.opt_names.contains(&opt.name()) {
388            debug!("Registering {}", opt);
389            opt.register(&mut self.options);
390            self.opt_names.push(opt.name().to_string());
391            self.opts.insert(opt.name().to_string(), opt);
392        } else {
393            warn!("{} is already registered, ignoring", opt.name());
394        }
395    }
396}
397
398impl Display for Args {
399    fn fmt(&self, f: &mut Formatter) -> fmt::Result {
400        let mut display = String::new();
401        display.push_str(&format!("{}\n{}",
402            to_column("Args"), column_underline()));
403        for (key, value) in self.values.clone() {
404            display.push_str(&format!("\n{}\t{}",
405                to_column(&key), to_column(&value)));
406        }
407        write!(f, "{}", display)
408    }
409}
410
411// Private associated methods
412fn column_underline() -> String {
413    let mut underline = String::new();
414    for _ in 0..COLUMN_WIDTH { underline.push_str("="); }
415    underline
416}
417
418fn to_column(string: &str) -> String {
419    let mut string = string.to_string();
420    string = if string.len() > COLUMN_WIDTH {
421        string.truncate(COLUMN_WIDTH- 3);
422        format!("{}...", string)
423    } else { string };
424    let mut spaces = String::new();
425    for _ in 0..(COLUMN_WIDTH - string.len()) { spaces.push_str(" "); }
426    format!("{}{}", string, spaces)
427}
428