libargs-rs 1.0.0

Safe Rust bindings for libargs — Unix/GNU-style argument parsing
use libargs::{ArgsError, Opt, Parser};

fn args(extra: &[&str]) -> Vec<String> {
    let mut v = vec!["prog".to_string()];
    v.extend(extra.iter().map(|s| s.to_string()));
    v
}

#[test]
fn test_short_bool() {
    let opts = vec![Opt::bool('v', "verbose", "be verbose")];
    let r = Parser::new().parse(&args(&["-v"]), opts).unwrap();
    assert!(r.flag("verbose"));
    assert_eq!(r.count("verbose"), 1);
}

#[test]
fn test_long_bool() {
    let opts = vec![Opt::bool('v', "verbose", "be verbose")];
    let r = Parser::new().parse(&args(&["--verbose"]), opts).unwrap();
    assert!(r.flag("verbose"));
}

#[test]
fn test_short_value_space() {
    let opts = vec![Opt::string('f', "file", "FILE", "file", None)];
    let r = Parser::new().parse(&args(&["-f", "out.txt"]), opts).unwrap();
    assert_eq!(r.string("file"), Some("out.txt"));
}

#[test]
fn test_long_value_equals() {
    let opts = vec![Opt::string('f', "file", "FILE", "file", None)];
    let r = Parser::new().parse(&args(&["--file=data.csv"]), opts).unwrap();
    assert_eq!(r.string("file"), Some("data.csv"));
}

#[test]
fn test_long_value_space() {
    let opts = vec![Opt::string('f', "file", "FILE", "file", None)];
    let r = Parser::new().parse(&args(&["--file", "data.csv"]), opts).unwrap();
    assert_eq!(r.string("file"), Some("data.csv"));
}

#[test]
fn test_int_decimal() {
    let opts = vec![Opt::int('n', "count", "N", "count", None)];
    let r = Parser::new().parse(&args(&["--count=42"]), opts).unwrap();
    assert_eq!(r.int("count"), Some(42));
}

#[test]
fn test_int_hex() {
    let opts = vec![Opt::int('n', "count", "N", "count", None)];
    let r = Parser::new().parse(&args(&["--count=0xff"]), opts).unwrap();
    assert_eq!(r.int("count"), Some(255));
}

#[test]
fn test_float() {
    let opts = vec![Opt::float('r', "ratio", "F", "ratio", None)];
    let r = Parser::new().parse(&args(&["--ratio=3.14"]), opts).unwrap();
    let v = r.float("ratio").unwrap();
    assert!((v - 3.14).abs() < 1e-4);
}

#[test]
fn test_default_string() {
    let opts = vec![Opt::string('o', "output", "FILE", "output", Some("a.out"))];
    let r = Parser::new().parse(&args(&[]), opts).unwrap();
    assert_eq!(r.string("output"), Some("a.out"));
}

#[test]
fn test_default_int() {
    let opts = vec![Opt::int('n', "count", "N", "count", Some(7))];
    let r = Parser::new().parse(&args(&[]), opts).unwrap();
    assert_eq!(r.int("count"), Some(7));
}

#[test]
fn test_positionals() {
    let opts = vec![Opt::bool('v', "verbose", "verbose")];
    let r = Parser::new().parse(&args(&["-v", "file1.txt", "file2.txt"]), opts).unwrap();
    assert!(r.flag("verbose"));
    assert_eq!(r.positionals(), &["file1.txt", "file2.txt"]);
}

#[test]
fn test_dashdash() {
    let opts = vec![Opt::bool('v', "verbose", "verbose")];
    let r = Parser::new().parse(&args(&["-v", "--", "-f", "notopt"]), opts).unwrap();
    assert!(r.dashdash_seen());
    assert_eq!(r.positionals(), &["-f", "notopt"]);
}

#[test]
fn test_stdin_dash_positional() {
    let opts: Vec<Opt> = vec![];
    let r = Parser::new().parse(&args(&["-"]), opts).unwrap();
    assert_eq!(r.positionals(), &["-"]);
}

#[test]
fn test_combined_short() {
    let opts = vec![
        Opt::bool('x', "extract", "extract"),
        Opt::bool('v', "verbose", "verbose"),
        Opt::bool('z', "gzip",    "gzip"),
    ];
    let r = Parser::new().parse(&args(&["-xvz"]), opts).unwrap();
    assert!(r.flag("extract"));
    assert!(r.flag("verbose"));
    assert!(r.flag("gzip"));
}

#[test]
fn test_repeated_option_count_and_values() {
    let opts = vec![Opt::string('I', "include", "DIR", "include dir", None)];
    let r = Parser::new()
        .parse(&args(&["-I", "inc1", "-I", "inc2", "-I", "inc3"]), opts)
        .unwrap();
    assert_eq!(r.count("include"), 3);
    assert_eq!(r.values("include"), &["inc1", "inc2", "inc3"]);
}

#[test]
fn test_negation() {
    let opts = vec![Opt::bool('v', "verbose", "verbose")];
    let r = Parser::new().parse(&args(&["--verbose", "--no-verbose"]), opts).unwrap();
    assert!(!r.flag("verbose"));
}

#[test]
fn test_posix_order() {
    let opts = vec![Opt::bool('v', "verbose", "verbose")];
    let r = Parser::new()
        .posix_order(true)
        .parse(&args(&["file.txt", "-v"]), opts)
        .unwrap();
    assert!(!r.flag("verbose"));
    assert_eq!(r.positionals().len(), 2);
}

#[test]
fn test_error_unknown_opt() {
    let opts: Vec<Opt> = vec![];
    let err = Parser::new().parse(&args(&["--no-such"]), opts).unwrap_err();
    assert!(matches!(err, ArgsError::UnknownOption(_)));
}

#[test]
fn test_error_missing_value() {
    let opts = vec![Opt::string('f', "file", "FILE", "file", None)];
    let err = Parser::new().parse(&args(&["--file"]), opts).unwrap_err();
    assert!(matches!(err, ArgsError::MissingValue(_)));
}

#[test]
fn test_error_bad_int() {
    let opts = vec![Opt::int('n', "count", "N", "count", None)];
    let err = Parser::new().parse(&args(&["--count=abc"]), opts).unwrap_err();
    assert!(matches!(err, ArgsError::BadInt(_)));
}

#[test]
fn test_short_value_attached() {
    let opts = vec![Opt::string('o', "output", "FILE", "output", None)];
    let r = Parser::new().parse(&args(&["-ofile.txt"]), opts).unwrap();
    assert_eq!(r.string("output"), Some("file.txt"));
}

#[test]
fn test_no_args() {
    let opts: Vec<Opt> = vec![];
    let r = Parser::new().parse(&args(&[]), opts).unwrap();
    assert_eq!(r.positionals().len(), 0);
}