go_flag/
lib.rs

1//! A command-line parser with compatibility of Go's `flag` in its main focus.
2//!
3//! ## Design Goals
4//!
5//! Go comes with a built-in support for command-line parsing: the `flag` library.
6//! This is known to be incompatible with GNU convention, such as:
7//!
8//! - Short/long flags. POSIX/GNU flags sometimes have a pair of short and long
9//!   flags like `-f`/`--force` or `-n`/`--lines`. `flag` doesn't have such
10//!   distinction.
11//! - Combined short flags. In POSIX/GNU convention, `-fd` means `-f` plus `-d`.
12//!   `flag` parses it as a single flag named `fd`.
13//! - Flags after arguments. POSIX/GNU allows flags to appear after positional
14//!   arguments like `./command arg1 --flag arg2` unless explicitly separated
15//!   by `--`. `flag` parses it as a consecutive list of positional arguments.
16//!
17//! The `go-flag` crate is designed to allow Rust programmers to easily port
18//! Go CLI programs written using `flag` without breaking compatibility.
19//!
20//! Therefore, our priority is the following:
21//!
22//! 1. **Behavioral compatibility**. It's meant to be compatible with the Go's
23//!    built-in `flag` library in its command-line behavior.
24//!    Note that API compatibility (similarity) is a different matter.
25//! 2. **Migration**. Being unable to use more sophisticated parsers like
26//!    `structopt` is painful. Therefore, this library comes with an ability to
27//!    check typical incompatible usages to allow gradual migration.
28//! 3. **Simplicity**. This library isn't meant to provide full parser
29//!    functionality. For example, subcommand parsing is out of scope for
30//!    this library. Try to migrate to e.g. `structopt` if you want to extend
31//!    your program to accept more complex flags.
32//!
33//! ## Example
34//!
35//! Typically you can use the `parse` function.
36//!
37//! ```rust
38//! # if true {
39//! #     return;
40//! # }
41//! let mut force = false;
42//! let mut lines = 10_i32;
43//! let args: Vec<String> = go_flag::parse(|flags| {
44//!     flags.add_flag("f", &mut force);
45//!     flags.add_flag("lines", &mut lines);
46//! });
47//! # drop(args);
48//! ```
49//!
50//! If you want a list of file paths, use `PathBuf` or `OsString` to allow non-UTF8 strings.
51//!
52//! ```rust
53//! # if true {
54//! #     return;
55//! # }
56//! use std::path::PathBuf;
57//! let args: Vec<PathBuf> = go_flag::parse(|_| {});
58//! # drop(args);
59//! ```
60//!
61//! If an incompatible usage is detected, `parse` issues warnings and continues processing.
62//! You can alter the behavior using `parse_with_warnings`.
63//!
64//! For example, when enough time passed since the first release of your Rust port,
65//! you can start to deny the incompatible usages by specifying `WarningMode::Error`:
66//!
67//! ```rust
68//! # if true {
69//! #     return;
70//! # }
71//! use go_flag::WarningMode;
72//! let mut force = false;
73//! let mut lines = 10_i32;
74//! let args: Vec<String> =
75//!     go_flag::parse_with_warnings(WarningMode::Error, |flags| {
76//!         flags.add_flag("f", &mut force);
77//!         flags.add_flag("lines", &mut lines);
78//!     });
79//! # drop(args);
80//! ```
81
82use std::collections::HashMap;
83use std::ffi::OsStr;
84use std::fmt;
85
86pub use error::{FlagError, FlagParseError, FlagWarning};
87pub use flag_value::{FlagSetter, FlagValue};
88use unit_parsing::{parse_one, FlagResult};
89
90mod error;
91mod flag_value;
92mod unit_parsing;
93
94/// A set of flags. Allows fine control over parse procedure.
95///
96/// Typically you can use the `parse` function.
97///
98/// ## Example
99///
100/// ```rust
101/// # fn main() -> Result<(), go_flag::FlagError> {
102/// # use go_flag::FlagSet;
103/// let mut force = false;
104/// let mut lines = 10_i32;
105/// let args: Vec<String>;
106/// {
107///     let mut flags = FlagSet::new();
108///     flags.add_flag("f", &mut force);
109///     flags.add_flag("lines", &mut lines);
110///     args = flags.parse(&["-f", "--lines", "20", "--", "foo"])?;
111/// }
112/// assert_eq!(force, true);
113/// assert_eq!(lines, 20);
114/// assert_eq!(args, vec![String::from("foo")]);
115/// # Ok(())
116/// # }
117/// ```
118#[derive(Debug)]
119pub struct FlagSet<'a> {
120    flag_specs: HashMap<&'a str, FlagSpec<'a>>,
121}
122
123impl<'a> FlagSet<'a> {
124    /// Creates a new set of flags.
125    ///
126    /// ## Example
127    ///
128    /// ```rust
129    /// # use go_flag::FlagSet;
130    /// let mut flags = FlagSet::new();
131    /// # flags.add_flag("f", &mut false);
132    /// ```
133    pub fn new() -> Self {
134        Self {
135            flag_specs: HashMap::new(),
136        }
137    }
138
139    /// Add a flag to be parsed.
140    ///
141    /// ## Panics
142    ///
143    /// Panics if the flag of the same name is already registered.
144    ///
145    /// ## Example
146    ///
147    /// ```rust
148    /// # use go_flag::FlagSet;
149    /// let mut force = false;
150    /// let mut flags = FlagSet::new();
151    /// flags.add_flag("f", &mut force);
152    /// ```
153    pub fn add_flag(&mut self, name: &'a str, value: &'a mut dyn FlagSetter) {
154        let value = FlagSpec { r: value };
155        let old = self.flag_specs.insert(name, value);
156        if old.is_some() {
157            panic!("multiple flags with same name: {}", name);
158        }
159    }
160
161    /// Parses the given arguments.
162    ///
163    /// ## Returns
164    ///
165    /// Returns the list of positional arguments (remaining arguments).
166    ///
167    /// Positional arguments can also be parsed. You'll typically need
168    /// `Vec<String>`, `Vec<OsString>` or `Vec<PathBuf>`.
169    ///
170    /// ## Errors
171    ///
172    /// It returns `Err` if the given arguments contains invalid flags.
173    ///
174    /// ## Example
175    ///
176    /// ```rust
177    /// # fn main() -> Result<(), go_flag::FlagError> {
178    /// # use go_flag::FlagSet;
179    /// let mut force = false;
180    /// let mut flags = FlagSet::new();
181    /// flags.add_flag("f", &mut force);
182    /// let args: Vec<String> = flags.parse(&["-f", "foo"])?;
183    /// assert_eq!(args, vec![String::from("foo")]);
184    /// # Ok(())
185    /// # }
186    /// ```
187    pub fn parse<'b, T: FlagValue, S: AsRef<OsStr>>(
188        &mut self,
189        args: &'b [S],
190    ) -> Result<Vec<T>, FlagError> {
191        self.parse_with_warnings(args, None)
192    }
193
194    /// Parses the given arguments, recording warnings issued.
195    ///
196    /// ## Returns
197    ///
198    /// Returns the list of positional arguments (remaining arguments).
199    ///
200    /// Positional arguments can also be parsed. You'll typically need
201    /// `Vec<String>`, `Vec<OsString>` or `Vec<PathBuf>`.
202    ///
203    /// ## Errors
204    ///
205    /// It returns `Err` if the given arguments contains invalid flags.
206    ///
207    /// ## Example
208    ///
209    /// ```rust
210    /// # fn main() -> Result<(), go_flag::FlagError> {
211    /// # use go_flag::FlagSet;
212    /// let mut warnings = Vec::new();
213    /// let mut force = false;
214    /// let mut flags = FlagSet::new();
215    /// flags.add_flag("f", &mut force);
216    /// let args: Vec<String> = flags
217    ///     .parse_with_warnings(&["--f", "foo", "-non-flag"], Some(&mut warnings))?;
218    /// assert_eq!(args, vec![String::from("foo"), String::from("-non-flag")]);
219    /// assert_eq!(warnings[0].to_string(), "short flag with double minuses: --f");
220    /// assert_eq!(warnings[1].to_string(), "flag-like syntax appearing after argument: -non-flag");
221    /// # Ok(())
222    /// # }
223    /// ```
224    pub fn parse_with_warnings<'b, T: FlagValue, S: AsRef<OsStr>>(
225        &mut self,
226        mut args: &'b [S],
227        mut warnings: Option<&mut Vec<FlagWarning>>,
228    ) -> Result<Vec<T>, FlagError> {
229        loop {
230            let seen = self.process_one(&mut args, reborrow_option_mut(&mut warnings))?;
231            if !seen {
232                break;
233            }
234        }
235        let args = args
236            .iter()
237            .map(|x| {
238                T::parse(Some(x.as_ref()), reborrow_option_mut(&mut warnings))
239                    .map_err(|error| FlagError::ParseError { error })
240            })
241            .collect::<Result<Vec<_>, _>>()?;
242        Ok(args)
243    }
244
245    fn process_one<S: AsRef<OsStr>>(
246        &mut self,
247        args: &mut &[S],
248        mut warnings: Option<&mut Vec<FlagWarning>>,
249    ) -> Result<bool, FlagError> {
250        if args.is_empty() {
251            return Ok(false);
252        }
253        let arg0: &OsStr = args[0].as_ref();
254        let (num_minuses, name, value) = match parse_one(arg0) {
255            FlagResult::Argument => {
256                if let Some(warnings) = reborrow_option_mut(&mut warnings) {
257                    for arg in args.iter() {
258                        let arg = arg.as_ref();
259                        let flag_like = match parse_one(arg) {
260                            FlagResult::Argument | FlagResult::EndFlags => false,
261                            FlagResult::BadFlag | FlagResult::Flag { .. } => true,
262                        };
263                        if flag_like {
264                            warnings.push(FlagWarning::FlagAfterArg {
265                                flag: arg.to_string_lossy().into_owned(),
266                            });
267                        }
268                    }
269                }
270                return Ok(false);
271            }
272            FlagResult::EndFlags => {
273                *args = &args[1..];
274                return Ok(false);
275            }
276            FlagResult::BadFlag => {
277                return Err(FlagError::BadFlag {
278                    flag: arg0.to_string_lossy().into_owned(),
279                })
280            }
281            FlagResult::Flag {
282                num_minuses,
283                name,
284                value,
285            } => (num_minuses, name, value),
286        };
287        *args = &args[1..];
288        if let Some(warnings) = reborrow_option_mut(&mut warnings) {
289            if name.len() > 1 && num_minuses == 1 {
290                warnings.push(FlagWarning::ShortLong {
291                    flag: arg0.to_string_lossy().into_owned(),
292                });
293            }
294            if name.len() == 1 && num_minuses == 2 {
295                warnings.push(FlagWarning::LongShort {
296                    flag: arg0.to_string_lossy().into_owned(),
297                });
298            }
299        }
300        let name = name.to_str().ok_or_else(|| FlagError::UnknownFlag {
301            name: name.to_string_lossy().into_owned(),
302        })?;
303        let flag_spec = if let Some(flag_spec) = self.flag_specs.get_mut(name) {
304            flag_spec
305        } else {
306            return Err(FlagError::UnknownFlag {
307                name: name.to_owned(),
308            });
309        };
310        let value = if !flag_spec.r.is_bool_flag() && value.is_none() {
311            if args.is_empty() {
312                return Err(FlagError::ArgumentNeeded {
313                    name: name.to_owned(),
314                });
315            }
316            let arg1 = args[0].as_ref();
317            *args = &args[1..];
318            Some(arg1)
319        } else {
320            value.as_ref().map(|x| x.as_ref())
321        };
322        flag_spec
323            .r
324            .set(value, reborrow_option_mut(&mut warnings))
325            .map_err(|error| FlagError::ParseError { error })?;
326        Ok(true)
327    }
328}
329
330fn reborrow_option_mut<'a, T>(x: &'a mut Option<&mut T>) -> Option<&'a mut T> {
331    if let Some(x) = x {
332        Some(x)
333    } else {
334        None
335    }
336}
337
338struct FlagSpec<'a> {
339    r: &'a mut dyn FlagSetter,
340}
341
342impl<'a> fmt::Debug for FlagSpec<'a> {
343    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
344        struct FlagSetterPlaceholder<'a>(&'a dyn FlagSetter);
345        impl<'a> fmt::Debug for FlagSetterPlaceholder<'a> {
346            fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
347                write!(f, "<mutable reference {:p}>", self.0)
348            }
349        }
350
351        f.debug_struct("FlagSpec")
352            .field("r", &FlagSetterPlaceholder(self.r))
353            .finish()
354    }
355}
356
357/// Parses the given arguments into flags.
358///
359/// Flags are registered in the given closure.
360///
361/// ## Returns
362///
363/// Returns the list of positional arguments (remaining arguments).
364///
365/// Positional arguments can also be parsed. You'll typically need
366/// `Vec<String>`, `Vec<OsString>` or `Vec<PathBuf>`.
367///
368/// ## Errors
369///
370/// It returns `Err` if the given arguments contains invalid flags.
371///
372/// ## Example
373///
374/// ```rust
375/// # fn main() -> Result<(), go_flag::FlagError> {
376/// let mut force = false;
377/// let mut lines = 10_i32;
378/// let args = ["-f", "--", "foo"];
379/// let args: Vec<String> = go_flag::parse_args(&args, |flags| {
380///     flags.add_flag("f", &mut force);
381///     flags.add_flag("lines", &mut lines);
382/// })?;
383/// assert_eq!(force, true);
384/// assert_eq!(lines, 10);
385/// assert_eq!(args, vec![String::from("foo")]);
386/// # Ok(())
387/// # }
388/// ```
389pub fn parse_args<'a, T, S: AsRef<OsStr>, F>(args: &[S], f: F) -> Result<Vec<T>, FlagError>
390where
391    T: FlagValue,
392    F: FnOnce(&mut FlagSet<'a>),
393{
394    parse_args_with_warnings(args, None, f)
395}
396
397/// Parses the given arguments into flags, recording warnings issued.
398///
399/// Flags are registered in the given closure.
400///
401/// ## Returns
402///
403/// Returns the list of positional arguments (remaining arguments).
404///
405/// Positional arguments can also be parsed. You'll typically need
406/// `Vec<String>`, `Vec<OsString>` or `Vec<PathBuf>`.
407///
408/// ## Errors
409///
410/// It returns `Err` if the given arguments contains invalid flags.
411///
412/// ## Example
413///
414/// ```rust
415/// # fn main() -> Result<(), go_flag::FlagError> {
416/// let mut warnings = Vec::new();
417/// let mut force = false;
418/// let mut lines = 10_i32;
419/// let args = ["--f", "--", "foo"];
420/// let args: Vec<String> =
421///     go_flag::parse_args_with_warnings(&args, Some(&mut warnings), |flags| {
422///         flags.add_flag("f", &mut force);
423///         flags.add_flag("lines", &mut lines);
424///     })?;
425/// assert_eq!(force, true);
426/// assert_eq!(lines, 10);
427/// assert_eq!(args, vec![String::from("foo")]);
428/// assert_eq!(warnings[0].to_string(), "short flag with double minuses: --f");
429/// # Ok(())
430/// # }
431/// ```
432pub fn parse_args_with_warnings<'a, T, S: AsRef<OsStr>, F>(
433    args: &[S],
434    mut warnings: Option<&mut Vec<FlagWarning>>,
435    f: F,
436) -> Result<Vec<T>, FlagError>
437where
438    T: FlagValue,
439    F: FnOnce(&mut FlagSet<'a>),
440{
441    let mut flag_set = FlagSet::new();
442    f(&mut flag_set);
443    let remain = flag_set.parse_with_warnings(args, reborrow_option_mut(&mut warnings))?;
444    Ok(remain)
445}
446
447/// Parses the command-line arguments into flags.
448///
449/// Flags are registered in the given closure.
450///
451/// ## Returns
452///
453/// Returns the list of positional arguments (remaining arguments).
454///
455/// Positional arguments can also be parsed. You'll typically need
456/// `Vec<String>`, `Vec<OsString>` or `Vec<PathBuf>`.
457///
458/// ## Exits
459///
460/// It exits if the command-line arguments contain invalid flags.
461///
462/// ## Outputs
463///
464/// It prints errors and warnings to the standard error stream (stderr).
465///
466/// ## Example
467///
468/// ```rust
469/// # if true {
470/// #     return;
471/// # }
472/// let mut force = false;
473/// let mut lines = 10_i32;
474/// let args: Vec<String> = go_flag::parse(|flags| {
475///     flags.add_flag("f", &mut force);
476///     flags.add_flag("lines", &mut lines);
477/// });
478/// # drop(args);
479/// ```
480pub fn parse<'a, T, F>(f: F) -> Vec<T>
481where
482    T: FlagValue,
483    F: FnOnce(&mut FlagSet<'a>),
484{
485    parse_with_warnings(WarningMode::Report, f)
486}
487
488/// Parses the command-line arguments into flags, handling warnings as specified.
489///
490/// Flags are registered in the given closure.
491///
492/// ## Returns
493///
494/// Returns the list of positional arguments (remaining arguments).
495///
496/// Positional arguments can also be parsed. You'll typically need
497/// `Vec<String>`, `Vec<OsString>` or `Vec<PathBuf>`.
498///
499/// ## Exits
500///
501/// It exits if:
502///
503/// - the command-line arguments contain invalid flags, or
504/// - `mode` is `WarningMode::Error` and we have compatibility warnings.
505///
506/// ## Outputs
507///
508/// It prints errors and warnings to the standard error stream (stderr).
509///
510/// If `WarningMode::Ignore` is set, we'll throw warnings away.
511///
512/// ## Example
513///
514/// ```rust
515/// # if true {
516/// #     return;
517/// # }
518/// use go_flag::WarningMode;
519/// let mut force = false;
520/// let mut lines = 10_i32;
521/// let args: Vec<String> =
522///     go_flag::parse_with_warnings(WarningMode::Error, |flags| {
523///         flags.add_flag("f", &mut force);
524///         flags.add_flag("lines", &mut lines);
525///     });
526/// # drop(args);
527/// ```
528pub fn parse_with_warnings<'a, T, F>(mode: WarningMode, f: F) -> Vec<T>
529where
530    T: FlagValue,
531    F: FnOnce(&mut FlagSet<'a>),
532{
533    let mut warnings = if mode == WarningMode::Ignore {
534        None
535    } else {
536        Some(Vec::new())
537    };
538    let args = std::env::args_os().collect::<Vec<_>>();
539    match parse_args_with_warnings(&args[1..], warnings.as_mut(), f) {
540        Ok(x) => {
541            if let Some(warnings) = &warnings {
542                for w in warnings {
543                    eprintln!("{}", w);
544                }
545                if !warnings.is_empty() && mode == WarningMode::Error {
546                    std::process::exit(1);
547                }
548            }
549            x
550        }
551        Err(e) => {
552            eprintln!("{}", e);
553            std::process::exit(1);
554        }
555    }
556}
557
558/// How `parse_with_warnings` treats compatibility warnings.
559#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
560pub enum WarningMode {
561    /// Throw warnings away.
562    Ignore,
563    /// Report warnings to stderr and continue processing.
564    Report,
565    /// Report warnings to stderr and abort the program.
566    Error,
567}
568
569#[cfg(test)]
570mod tests {
571    use super::*;
572
573    #[test]
574    fn test_parse_args() {
575        let parse = |args: &[&str]| -> Result<(bool, i32, Vec<String>), FlagError> {
576            let mut force = false;
577            let mut lines = 10_i32;
578            let args = parse_args(args, |flags| {
579                flags.add_flag("f", &mut force);
580                flags.add_flag("lines", &mut lines);
581            })?;
582            Ok((force, lines, args))
583        };
584        assert_eq!(parse(&[]).unwrap(), (false, 10, vec![]));
585        assert_eq!(parse(&["-f"]).unwrap(), (true, 10, vec![]));
586        assert_eq!(parse(&["-f", "--lines=20"]).unwrap(), (true, 20, vec![]));
587    }
588}