1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
use crate::name::Name;
use crate::registry::Flag;
use crate::token::{Token, Tokenizer};
use std::collections::BTreeMap;
use std::ffi::OsStr;
use std::process;

/// Initialize the value of all flags based on arguments from the command line
/// at runtime.
///
/// This function must be called before accessing the values of any flags. After
/// this function has been called, the values of flags are available in the
/// `.flag` field of each flag.
///
/// The return vector contains everything on the command line which is not a
/// flag. These are sometimes called positional arguments.
///
/// # Examples
///
/// If we execute the following program with command line `./my_program a b c`
/// then nothing is printed because `--print-args` is not set. If we execute it
/// with `./my_program --print-args a b c` then the positional arguments `a` `b`
/// `c` are printed.
///
/// ```
/// gflags::define! {
///     --print-args = false
/// }
///
/// fn main() {
///     let args = gflags::parse();
///
///     if PRINT_ARGS.flag {
///         println!("args = {:?}", args);
///     }
/// }
/// ```
///
/// # Aborts
///
/// Aborts the process with an error message if the command line does not
/// conform to the flags defined by the application, or if any of the positional
/// arguments are non-UTF8. Use [`gflags::parse_os`] if you need to support
/// non-UTF8 positional arguments.
///
/// [`gflags::parse_os`]: crate::parse_os()
pub fn parse() -> Vec<&'static str> {
    fn to_str_or_abort(os_str: &OsStr) -> &str {
        os_str.to_str().unwrap_or_else(|| {
            eprintln!("Unsupported non-UTF8 command line argument");
            process::exit(1);
        })
    }

    parse_os().into_iter().map(to_str_or_abort).collect()
}

/// Initialize the value of all flags, accepting non-UTF8 positional arguments.
///
/// Equivalent to [`gflags::parse`] in all ways except that non-UTF8 positional
/// arguments are not an error. Note that non-UTF8 *flag values* are allowed
/// even by `gflags::parse`.
///
/// [`gflags::parse`]: crate::parse()
pub fn parse_os() -> Vec<&'static OsStr> {
    let mut shorts = BTreeMap::new();
    let mut longs = BTreeMap::new();
    for flag in inventory::iter::<Flag> {
        if let Some(short) = flag.short {
            shorts.insert(short, flag);
        }
        longs.insert(flag.name, flag);
    }

    let mut args = Vec::new();
    let mut tokens = Tokenizer::new();

    while let Some(token) = tokens.next() {
        match token {
            Token::Short(ch) => match shorts.get(&ch) {
                Some(flag) => {
                    let name = Name::short(ch);
                    flag.parser.parse(name, &mut tokens);
                }
                None => {
                    eprintln!("Unrecognized flag: -{}", ch);
                    process::exit(1);
                }
            },
            Token::Long(name) => match longs.get(name) {
                Some(flag) => {
                    let name = Name::long(flag.name);
                    flag.parser.parse(name, &mut tokens);
                }
                None => {
                    if name.starts_with("no") {
                        if let Some(flag) = longs.get(&name[2..]) {
                            if flag.parser.is_bool() {
                                flag.parser.unset_bool();
                                continue;
                            }
                        }
                    }
                    eprintln!("Unrecognized flag: --{}", name);
                    process::exit(1);
                }
            },
            Token::LongEq(name, arg) => {
                if let Some(flag) = longs.get(name) {
                    if flag.parser.is_bool() {
                        eprintln!("Unexpected argument {:?} for flag: --{}={0}", arg, name);
                        process::exit(1);
                    }

                    let name = Name::long(flag.name);

                    // Prepare an iterator and tokenizer for just this arg, then
                    // parse the arg for the flag
                    let arg = vec![OsStr::new(arg)];
                    let mut tokens = Tokenizer::iterate(arg);

                    flag.parser.parse(name, &mut tokens);
                } else {
                    eprintln!("Unrecognized flag: --{}", name);
                    process::exit(1);
                }
            }
            Token::Arg(arg) => args.push(arg),
        }
    }

    args
}