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