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}