auto_args/
lib.rs

1#![deny(missing_docs)]
2
3//! This crate enables you to create a command-line interface by defining a
4//! struct to hold your options.
5//! 
6//! # Features
7//! 
8//! * `meval` - enables parsing of numbers using the
9//!   [meval](https://docs.rs/meval/latest/meval/) crate.  This crate isn't well
10//!   maintained, but does enable specifying numbers in a variety of cool ways.
11
12use std::ffi::OsString;
13use std::path::PathBuf;
14#[cfg(not(feature = "meval"))]
15use std::str::FromStr;
16
17pub mod guide;
18
19#[doc(hidden)]
20pub use auto_args_derive::*;
21
22fn align_tabs(inp: &str) -> String {
23    let mut out = String::with_capacity(inp.len());
24    let mut stop1 = 0;
25    let mut stop2 = 0;
26    for l in inp.lines() {
27        let v: Vec<_> = l.splitn(3, '\t').collect();
28        if v.len() > 2 {
29            stop1 = std::cmp::max(stop1, v[0].len() + 2);
30            stop2 = std::cmp::max(stop2, v[1].len() + 1);
31        }
32    }
33    // We now know where to align the columns.
34    for l in inp.lines() {
35        let v: Vec<_> = l.splitn(3, '\t').collect();
36        if v.len() > 2 {
37            out.push_str(&format!(
38                "{:a$}{:b$}{}\n",
39                v[0],
40                v[1],
41                v[2],
42                a = stop1,
43                b = stop2
44            ));
45        } else {
46            out.push_str(l);
47            out.push('\n');
48        }
49    }
50    out
51}
52
53/// The primary trait, which is implemented by any type which may be
54/// part of your command-line flags.
55pub trait AutoArgs: Sized {
56    /// Parse the command-line arguments, exiting in case of error.
57    ///
58    /// This is what users actually use.
59    fn from_args() -> Self {
60        let mut v: Vec<_> = std::env::args_os().collect();
61        v.remove(0);
62        if v.iter().any(|v| v == "--help") {
63            println!("{}", Self::help());
64            std::process::exit(0);
65        }
66        match Self::parse_vec(v) {
67            Ok(val) => val,
68            Err(e) => {
69                println!("error: {}\n", e);
70                println!("{}", Self::usage());
71                std::process::exit(1)
72            }
73        }
74    }
75    /// Parse a `Vec` of arguments as if they were command line flags
76    ///
77    /// This mimics what we would do if we were doing the real
78    /// parsing, except that we don't exit on error.
79    fn parse_vec(mut args: Vec<OsString>) -> Result<Self, Error> {
80        let v = Self::parse_internal("", &mut args)?;
81        if args.len() > 0 {
82            Err(Error::UnexpectedOption(format!("{:?}", args)))
83        } else {
84            Ok(v)
85        }
86    }
87    /// Parse arguments given through an iterable thing such as a `Vec` or a slice, ignoring first element.
88    fn from_iter<I, T>(args: I) -> Result<Self, Error>
89    where
90        I: IntoIterator<Item = T>,
91        T: Into<OsString> + Clone,
92    {
93        let mut v: Vec<_> = args.into_iter().map(|v| v.into()).collect();
94        v.remove(0);
95        Self::parse_vec(v)
96    }
97    /// For implementation, but not for using this library.
98    ///
99    /// Parse this flag from the arguments, and return the set of
100    /// remaining arguments if it was successful.  Otherwise return an
101    /// error message indicating what went wrong.  The `prefix` is
102    /// a string that should be inserted prior to a flag name.
103    fn parse_internal(key: &str, args: &mut Vec<OsString>) -> Result<Self, Error>;
104    /// Indicates whether this type requires any input.
105    ///
106    /// This is false if the data may be processed with no input, true
107    /// otherwise.
108    const REQUIRES_INPUT: bool;
109    /// Return a tiny  help message.
110    fn tiny_help_message(key: &str) -> String;
111    /// Return a help message.
112    fn help_message(key: &str, doc: &str) -> String {
113        format!("\t{}\t{}", Self::tiny_help_message(key), doc)
114    }
115    /// Usage text for the actual command
116    fn usage() -> String {
117        format!(
118            "USAGE:
119  {} {}
120
121For more information try --help",
122            std::env::args_os()
123                .next()
124                .unwrap()
125                .to_string_lossy()
126                .rsplit("/")
127                .next()
128                .unwrap()
129                .to_string(),
130            Self::tiny_help_message("")
131        )
132    }
133    /// Help text for the actual command
134    fn help() -> String {
135        format!(
136            "USAGE:
137  {} {}
138
139{}
140
141For more information try --help",
142            std::env::args_os()
143                .next()
144                .unwrap()
145                .to_string_lossy()
146                .rsplit("/")
147                .next()
148                .unwrap()
149                .to_string(),
150            Self::tiny_help_message(""),
151            align_tabs(&Self::help_message("", ""))
152        )
153    }
154}
155
156/// A list of possible errors.
157#[derive(Clone, Debug, PartialEq)]
158pub enum Error {
159    /// An error from pico-args.
160    OptionValueParsingFailed(String, String),
161
162    /// A missing value from an option.
163    InvalidUTF8(String),
164
165    /// A missing value from an option.
166    OptionWithoutAValue(String),
167
168    /// A missing required flag.
169    MissingOption(String),
170
171    /// An unexpected option.
172    UnexpectedOption(String),
173}
174
175impl std::fmt::Display for Error {
176    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
177        match self {
178            Error::OptionValueParsingFailed(key, e) => {
179                write!(f, "error parsing option '{}': {}", key, e)
180            }
181            Error::MissingOption(key) => {
182                write!(f, "the required '{}' option is missing", key)
183            }
184            Error::InvalidUTF8(e) => {
185                write!(f, "invalid UTF-8: '{}'", e)
186            }
187            Error::OptionWithoutAValue(key) => {
188                write!(f, "the option '{}' is missing a value", key)
189            }
190            Error::UnexpectedOption(o) => {
191                write!(f, "unexpected option: {}", o)
192            }
193        }
194    }
195}
196
197impl std::error::Error for Error {}
198
199macro_rules! impl_from_osstr {
200    ($t:ty, $tyname:expr, $conv:expr) => {
201        impl AutoArgs for $t {
202            const REQUIRES_INPUT: bool = true;
203            fn parse_internal(key: &str, args: &mut Vec<OsString>) -> Result<Self, Error> {
204                let convert = $conv;
205                if key == "" {
206                    if args.len() == 0 {
207                        Err(Error::MissingOption("".to_string()))
208                    } else {
209                        let arg = if args[0] == "--" {
210                            if args.len() > 1 {
211                                args.remove(1)
212                            } else {
213                                return Err(Error::OptionWithoutAValue("".to_string()));
214                            }
215                        } else {
216                            args.remove(0)
217                        };
218                        convert(arg)
219                    }
220                } else {
221                    let eqthing = format!("{}=", key);
222                    if let Some(i) = args
223                        .iter()
224                        .position(|v| v == key || v.to_string_lossy().starts_with(&eqthing))
225                    {
226                        let thing = args
227                            .remove(i)
228                            .into_string()
229                            .map_err(|e| Error::InvalidUTF8(format!("{:?}", e)))?;
230                        if thing == key {
231                            if args.len() > i {
232                                convert(args.remove(i))
233                            } else {
234                                Err(Error::OptionWithoutAValue(key.to_string()))
235                            }
236                        } else {
237                            convert(thing.split_at(eqthing.len()).1.into())
238                        }
239                    } else {
240                        Err(Error::MissingOption(key.to_string()))
241                    }
242                }
243            }
244            fn tiny_help_message(key: &str) -> String {
245                if key == "" {
246                    "STRING".to_string()
247                } else {
248                    format!("{} STRING", key)
249                }
250            }
251        }
252
253        impl AutoArgs for Vec<$t> {
254            const REQUIRES_INPUT: bool = false;
255            fn parse_internal(key: &str, args: &mut Vec<OsString>) -> Result<Self, Error> {
256                let mut res: Self = Vec::new();
257                loop {
258                    match <$t>::parse_internal(key, args) {
259                        Ok(the_arg) => {
260                            res.push(the_arg);
261                        }
262                        Err(Error::MissingOption(_)) => {
263                            return Ok(res);
264                        }
265                        Err(e) => {
266                            return Err(e);
267                        }
268                    }
269                }
270            }
271            fn tiny_help_message(key: &str) -> String {
272                if key == "" {
273                    format!("{}...", $tyname)
274                } else {
275                    format!("{} {} ...", key, $tyname)
276                }
277            }
278        }
279    };
280}
281
282impl_from_osstr!(String, "STRING", |osstring: OsString| {
283    osstring
284        .into_string()
285        .map_err(|e| Error::InvalidUTF8(format!("{:?}", e)))
286});
287impl_from_osstr!(PathBuf, "PATH", |osstring: OsString| {
288    Ok(osstring.into())
289});
290
291impl AutoArgs for bool {
292    const REQUIRES_INPUT: bool = false;
293    fn parse_internal(key: &str, args: &mut Vec<OsString>) -> Result<Self, Error> {
294        if key == "" {
295            if args.len() == 0 {
296                Err(Error::OptionWithoutAValue("".to_string()))
297            } else {
298                if args[0] == "--" {
299                    return Err(Error::OptionWithoutAValue("bool".to_string()));
300                }
301                let arg = args.remove(0);
302                if arg == "false" {
303                    Ok(false)
304                } else if arg == "true" {
305                    Ok(true)
306                } else {
307                    Err(Error::MissingOption("bool".to_string()))
308                }
309            }
310        } else {
311            if args
312                .iter()
313                .filter(|v| v.to_string_lossy() == key)
314                .next()
315                .is_some()
316            {
317                *args = args
318                    .iter()
319                    .filter(|v| v.to_string_lossy() != key)
320                    .cloned()
321                    .collect();
322                Ok(true)
323            } else {
324                Ok(false)
325            }
326        }
327    }
328    fn tiny_help_message(key: &str) -> String {
329        if key == "" {
330            "(true|false)".to_string()
331        } else {
332            format!("[{}]", key)
333        }
334    }
335}
336
337impl<T: AutoArgs> AutoArgs for Option<T> {
338    const REQUIRES_INPUT: bool = false;
339    fn parse_internal(key: &str, args: &mut Vec<OsString>) -> Result<Self, Error> {
340        Ok(T::parse_internal(key, args).ok())
341    }
342    fn tiny_help_message(key: &str) -> String {
343        format!("[{}]", T::tiny_help_message(key))
344    }
345}
346
347macro_rules! impl_from {
348    ($t:ty, $tyname:expr) => {
349        impl AutoArgs for $t {
350            const REQUIRES_INPUT: bool = true;
351            fn parse_internal(key: &str, args: &mut Vec<OsString>) -> Result<Self, Error> {
352                use std::str::FromStr;
353                let the_arg = String::parse_internal(key, args)?;
354                match Self::from_str(&the_arg) {
355                    Ok(val) => Ok(val),
356                    Err(e) => {
357                        let e = Err(Error::OptionValueParsingFailed(
358                            key.to_string(),
359                            e.to_string(),
360                        ));
361                        #[cfg(feature = "meval")]
362                        let out = if let Ok(x) = meval::eval_str(&the_arg) {
363                            if (x as $t) as f64 == x {
364                                Ok(x as $t)
365                            } else {
366                                e
367                            }
368                        } else {
369                            e
370                        };
371                        #[cfg(not(feature = "meval"))]
372                        let out = e;
373                        out
374                    }
375                }
376            }
377            fn tiny_help_message(key: &str) -> String {
378                if key == "" {
379                    $tyname.to_string()
380                } else {
381                    format!("{} {}", key, $tyname)
382                }
383            }
384        }
385
386        impl AutoArgs for Vec<$t> {
387            const REQUIRES_INPUT: bool = false;
388            fn parse_internal(key: &str, args: &mut Vec<OsString>) -> Result<Self, Error> {
389                let mut res: Self = Vec::new();
390                loop {
391                    match <$t>::parse_internal(key, args) {
392                        Ok(val) => {
393                            res.push(val);
394                        }
395                        Err(Error::MissingOption(_)) => {
396                            return Ok(res);
397                        }
398                        Err(e) => {
399                            return Err(e);
400                        }
401                    }
402                }
403            }
404            fn tiny_help_message(key: &str) -> String {
405                if key == "" {
406                    format!("{}...", $tyname.to_string())
407                } else {
408                    format!("{} {} ...", key, $tyname)
409                }
410            }
411        }
412    };
413}
414
415impl_from!(u8, "u8");
416impl_from!(u16, "u16");
417impl_from!(u32, "u32");
418impl_from!(u64, "u64");
419impl_from!(usize, "usize");
420
421impl_from!(i8, "i8");
422impl_from!(i16, "i16");
423impl_from!(i32, "i32");
424impl_from!(i64, "i64");
425impl_from!(isize, "isize");
426
427impl AutoArgs for f64 {
428    const REQUIRES_INPUT: bool = true;
429    fn parse_internal(key: &str, args: &mut Vec<OsString>) -> Result<Self, Error> {
430        let the_arg = String::parse_internal(key, args)?;
431        #[cfg(feature = "meval")]
432        let value = meval::eval_str(the_arg)
433            .map_err(|e| Error::OptionValueParsingFailed(key.to_string(), e.to_string()));
434        #[cfg(not(feature = "meval"))]
435        let value = f64::from_str(&the_arg)
436            .map_err(|e| Error::OptionValueParsingFailed(key.to_string(), e.to_string()));
437        value
438    }
439    fn tiny_help_message(key: &str) -> String {
440        if key == "" {
441            "FLOAT".to_string()
442        } else {
443            format!("{} FLOAT", key)
444        }
445    }
446}
447
448impl AutoArgs for Vec<f64> {
449    const REQUIRES_INPUT: bool = false;
450    fn parse_internal(key: &str, args: &mut Vec<OsString>) -> Result<Self, Error> {
451        let mut res: Self = Vec::new();
452        loop {
453            match <f64>::parse_internal(key, args) {
454                Ok(val) => {
455                    res.push(val);
456                }
457                Err(Error::MissingOption(_)) => {
458                    return Ok(res);
459                }
460                Err(e) => {
461                    return Err(e);
462                }
463            }
464        }
465    }
466    fn tiny_help_message(key: &str) -> String {
467        format!("{} ...", f64::tiny_help_message(key))
468    }
469}
470
471impl AutoArgs for f32 {
472    const REQUIRES_INPUT: bool = true;
473    fn parse_internal(key: &str, args: &mut Vec<OsString>) -> Result<Self, Error> {
474        f64::parse_internal(key, args).map(|v| v as f32)
475    }
476    fn tiny_help_message(key: &str) -> String {
477        f64::tiny_help_message(key)
478    }
479}
480
481impl AutoArgs for Vec<f32> {
482    const REQUIRES_INPUT: bool = false;
483    fn parse_internal(key: &str, args: &mut Vec<OsString>) -> Result<Self, Error> {
484        let mut res: Self = Vec::new();
485        loop {
486            match <f32>::parse_internal(key, args) {
487                Ok(val) => {
488                    res.push(val);
489                }
490                Err(Error::MissingOption(_)) => {
491                    return Ok(res);
492                }
493                Err(e) => {
494                    return Err(e);
495                }
496            }
497        }
498    }
499    fn tiny_help_message(key: &str) -> String {
500        Vec::<f64>::tiny_help_message(key)
501    }
502}
503
504impl<T> AutoArgs for std::marker::PhantomData<T> {
505    const REQUIRES_INPUT: bool = false;
506    fn parse_internal(_key: &str, _args: &mut Vec<OsString>) -> Result<Self, Error> {
507        Ok(std::marker::PhantomData)
508    }
509    fn tiny_help_message(_key: &str) -> String {
510        "".to_string()
511    }
512}
513
514#[cfg(test)]
515mod tests {
516    use super::*;
517    use crate as auto_args;
518    fn should_parse<T: PartialEq + AutoArgs + std::fmt::Debug>(
519        args: &'static [&'static str],
520        key: &'static str,
521        result: T,
522    ) {
523        let mut args: Vec<_> = args.iter().map(|s| OsString::from(s)).collect();
524        assert_eq!(T::parse_internal(key, &mut args).unwrap(), result);
525    }
526    fn should_parse_completely<T: PartialEq + AutoArgs + std::fmt::Debug>(
527        args: &'static [&'static str],
528        key: &'static str,
529        result: T,
530    ) {
531        let mut args: Vec<_> = args.iter().map(|s| OsString::from(s)).collect();
532        assert_eq!(T::parse_internal(key, &mut args).unwrap(), result);
533        if args.len() != 0 {
534            println!("args remaining: {:?}", args);
535            assert_eq!(args.len(), 0);
536        }
537    }
538
539    fn shouldnt_parse<T: PartialEq + AutoArgs + std::fmt::Debug>(
540        args: &'static [&'static str],
541        key: &'static str,
542    ) {
543        let mut args: Vec<_> = args.iter().map(|s| OsString::from(s)).collect();
544        assert!(T::parse_internal(key, &mut args).is_err());
545    }
546
547    #[test]
548    fn hello_world() {
549        let flags = &["--hello", "world", "--bad"];
550        should_parse(flags, "--hello", "world".to_string());
551        shouldnt_parse::<String>(flags, "--helloo");
552        shouldnt_parse::<u8>(flags, "--hello");
553    }
554    #[test]
555    fn hello_world_complete() {
556        let flags = &["--hello", "world"];
557        should_parse_completely(flags, "--hello", "world".to_string());
558    }
559    #[test]
560    fn hello_list() {
561        let flags = &["--hello", "big", "--hello", "bad", "--hello", "wolf"];
562        should_parse(
563            flags,
564            "--hello",
565            vec!["big".to_string(), "bad".to_string(), "wolf".to_string()],
566        );
567        shouldnt_parse::<String>(flags, "--helloo");
568        shouldnt_parse::<u8>(flags, "--hello");
569    }
570    #[test]
571    fn positional_arg() {
572        let flags = &["bad"];
573        should_parse(flags, "", "bad".to_string());
574    }
575    #[test]
576    fn arg_u8() {
577        let flags = &["--hello", "8", "--goodbye", "255", "--bad"];
578        should_parse(flags, "--hello", 8u8);
579        should_parse(flags, "--goodbye", 255u8);
580        shouldnt_parse::<String>(flags, "--helloo");
581    }
582    #[test]
583    fn arg_i32() {
584        let flags = &["--hello", "-100008", "--goodbye", "255", "--bad"];
585        should_parse(flags, "--hello", -100008i32);
586        should_parse(flags, "--hello", -100008i64);
587        should_parse(flags, "--goodbye", 255i32);
588        shouldnt_parse::<String>(flags, "--helloo");
589        shouldnt_parse::<u32>(flags, "--hello");
590    }
591    #[test]
592    fn arg_equal_i32() {
593        let flags = &["--hello=-100008", "--goodbye", "255", "--bad"];
594        should_parse(flags, "--hello", -100008i32);
595        should_parse(flags, "--hello", -100008i64);
596        should_parse(flags, "--goodbye", 255i32);
597        shouldnt_parse::<String>(flags, "--helloo");
598        shouldnt_parse::<u32>(flags, "--hello");
599    }
600    #[test]
601    fn arg_f64() {
602        let flags = &["--hello=3e13", "--goodbye", "2^10", "--bad"];
603        should_parse(flags, "--hello", 3e13);
604        #[cfg(feature = "meval")]
605        should_parse(flags, "--goodbye", 1024.0);
606        shouldnt_parse::<String>(flags, "--helloo");
607        shouldnt_parse::<u32>(flags, "--hello");
608    }
609    #[test]
610    fn arg_pathbuf() {
611        let flags = &["--hello=3e13", "--goodbye", "2^10", "--bad"];
612        should_parse(flags, "--hello", PathBuf::from("3e13"));
613        should_parse(flags, "--goodbye", PathBuf::from("2^10"));
614        shouldnt_parse::<String>(flags, "--helloo");
615        shouldnt_parse::<u32>(flags, "--hello");
616    }
617    #[derive(AutoArgs, PartialEq, Debug)]
618    struct Test {
619        a: String,
620        b: String,
621    }
622    #[test]
623    fn derive_test() {
624        println!("help:\n{}", Test::help_message("", "this is the help"));
625        println!(
626            "help prefix --foo:\n{}",
627            Test::help_message("--foo", "this is the help")
628        );
629        let flags = &["--a=foo", "--b", "bar"];
630        should_parse_completely(
631            flags,
632            "",
633            Test {
634                a: "foo".to_string(),
635                b: "bar".to_string(),
636            },
637        );
638        shouldnt_parse::<String>(flags, "--helloo");
639
640        let foo_flags = &["--foo-a=foo", "--foo-b", "bar"];
641        should_parse_completely(
642            foo_flags,
643            "--foo",
644            Test {
645                a: "foo".to_string(),
646                b: "bar".to_string(),
647            },
648        );
649        shouldnt_parse::<Test>(foo_flags, "");
650    }
651    #[derive(AutoArgs, PartialEq, Debug)]
652    struct Pair<T> {
653        first: T,
654        second: T,
655    }
656    #[test]
657    fn derive_test_pair() {
658        println!(
659            "help:\n{}",
660            Pair::<Test>::help_message("", "this is the help")
661        );
662        let flags = &[
663            "--first-a=a1",
664            "--first-b",
665            "b1",
666            "--second-a",
667            "a2",
668            "--second-b",
669            "b2",
670        ];
671        should_parse_completely(
672            flags,
673            "",
674            Pair {
675                first: Test {
676                    a: "a1".to_string(),
677                    b: "b1".to_string(),
678                },
679                second: Test {
680                    a: "a2".to_string(),
681                    b: "b2".to_string(),
682                },
683            },
684        );
685        shouldnt_parse::<String>(flags, "--helloo");
686        assert!(!Pair::<Option<String>>::REQUIRES_INPUT);
687        assert!(Pair::<String>::REQUIRES_INPUT);
688    }
689    #[derive(AutoArgs, PartialEq, Debug)]
690    enum Either<A, B> {
691        Left(A),
692        Right(B),
693    }
694    #[test]
695    fn derive_either() {
696        let flags = &["--left", "37"];
697        should_parse_completely(flags, "", Either::<u8, u16>::Left(37u8));
698    }
699    #[test]
700    fn derive_pair_either() {
701        let flags = &["--first-left", "37", "--second-right", "3"];
702        should_parse_completely(
703            flags,
704            "",
705            Pair {
706                first: Either::Left(37),
707                second: Either::Right(3),
708            },
709        );
710    }
711    #[test]
712    fn derive_either_either() {
713        let flags = &["--right-left", "37"];
714        should_parse_completely(
715            flags,
716            "",
717            Either::<u32, Either<u8, u16>>::Right(Either::Left(37)),
718        );
719    }
720    #[test]
721    fn derive_either_option() {
722        let flags = &["--right-left", "7"];
723        should_parse_completely(
724            flags,
725            "",
726            Either::<u32, Either<u8, Option<u32>>>::Right(Either::Left(7)),
727        );
728
729        let flags = &["--right-right"];
730        should_parse_completely(
731            flags,
732            "",
733            Either::<u32, Either<u8, Option<u32>>>::Right(Either::Right(None)),
734        );
735
736        let flags = &["--right-right", "5"];
737        should_parse_completely(
738            flags,
739            "",
740            Either::<u32, Either<u8, Option<u32>>>::Right(Either::Right(Some(5))),
741        );
742    }
743    #[derive(AutoArgs, PartialEq, Debug)]
744    enum MyEnum {
745        Hello { foo: String, bar: u8 },
746        _Goodbye { baz: String },
747    }
748    #[test]
749    fn derive_myenum() {
750        let flags = &["--hello-foo", "good", "--hello-bar", "7"];
751        should_parse(
752            flags,
753            "",
754            MyEnum::Hello {
755                foo: "good".to_string(),
756                bar: 7,
757            },
758        );
759    }
760    #[test]
761    fn option() {
762        let flags = &["--foo", "good"];
763        should_parse(flags, "--foo", Some("good".to_string()));
764        should_parse(flags, "--bar", Option::<String>::None);
765        assert!(String::REQUIRES_INPUT);
766        assert!(!Option::<String>::REQUIRES_INPUT);
767    }
768    #[derive(AutoArgs, PartialEq, Debug)]
769    struct TupleStruct(usize);
770    #[test]
771    fn tuple_struct() {
772        let flags = &["--foo", "5"];
773        should_parse_completely(flags, "--foo", TupleStruct(5));
774    }
775}