use std::ffi::OsString;
const BITS: i32 = 16;
const INIT_BITS: i32 = 9;
#[derive(Debug)]
pub(super) struct Settings {
pub(super) decomp: bool,
pub(super) quiet: bool,
pub(super) force: bool,
pub(super) keep: bool,
pub(super) zcat: bool,
pub(super) recursive: bool,
pub(super) maxbits: i32,
pub(super) files: Vec<Vec<u8>>,
}
#[derive(Debug)]
pub(super) enum Parsed {
Run(Settings),
Version,
Help,
Unknown(u8),
MissingMaxbits,
}
pub(super) fn parse(name: &str, args: &[OsString]) -> Parsed {
let args: Vec<&[u8]> = args.iter().map(|a| a.as_encoded_bytes()).collect();
let mut s = Settings {
decomp: name == "uncompress",
quiet: true,
force: false,
keep: false,
zcat: false,
recursive: false,
maxbits: BITS,
files: Vec::new(),
};
let mut seen_double_dash = false;
let mut i = 0;
while i < args.len() {
let a = args[i];
if a == b"--" {
seen_double_dash = true;
} else if !seen_double_dash && a.first() == Some(&b'-') {
let mut k = 1;
while k < a.len() {
match a[k] {
b'V' => return Parsed::Version,
b's' | b'q' => s.quiet = true,
b'v' => s.quiet = false,
b'd' => s.decomp = true,
b'k' => s.keep = true,
b'f' | b'F' => s.force = true,
b'n' => {}
b'b' => {
let value = if k + 1 < a.len() {
&a[k + 1..]
} else if i + 1 < args.len() {
i += 1;
args[i]
} else {
return Parsed::MissingMaxbits;
};
s.maxbits = atoi(value);
break;
}
b'c' => s.zcat = true,
b'r' | b'R' => s.recursive = true,
b'h' => return Parsed::Help,
other => return Parsed::Unknown(other),
}
k += 1;
}
} else {
s.files.push(a.to_vec());
}
i += 1;
}
s.maxbits = s.maxbits.clamp(INIT_BITS, BITS);
Parsed::Run(s)
}
fn atoi(s: &[u8]) -> i32 {
super::super::apple::args::strtol(s).0 as i32
}
#[cfg(test)]
mod tests {
use super::*;
fn run(name: &str, args: &[&str]) -> Parsed {
let args: Vec<OsString> = args.iter().map(OsString::from).collect();
parse(name, &args)
}
fn settings(name: &str, args: &[&str]) -> Settings {
match run(name, args) {
Parsed::Run(s) => s,
other => panic!("{args:?}: {other:?}"),
}
}
#[test]
fn flags_go_anywhere_and_double_dash_ends_them() {
let s = settings("compress", &["f", "-c", "--", "-d", "--", "g"]);
assert!(s.zcat && !s.decomp);
assert_eq!(s.files, [b"f".to_vec(), b"-d".to_vec(), b"g".to_vec()]);
let s = settings("compress", &["-"]);
assert!(s.files.is_empty());
let s = settings("uncompress", &["-vq"]);
assert!(s.decomp && s.quiet);
let s = settings("compress", &["-qv"]);
assert!(!s.quiet);
}
#[test]
fn maxbits_is_atoi_then_clamped() {
for (args, want) in [
(&["-b", "12"][..], 12),
(&["-b12x"], 12),
(&["-b", " +12"], 12),
(&["-b", "-1"], 9),
(&["-b", ""], 9),
(&["-b", "abc"], 9),
(&["-b", "17"], 16),
(&["-bv"], 9),
(&["-b", "-v"], 9),
(&["-b", "4294967308"], 12),
(&["-b", "99999999999999999999"], 9),
(&["-b", "9", "-b", "12"], 12),
(&[], 16),
] {
assert_eq!(settings("compress", args).maxbits, want, "{args:?}");
}
assert!(settings("compress", &["-b", "-v"]).quiet);
assert!(matches!(run("compress", &["-b"]), Parsed::MissingMaxbits));
assert!(matches!(
run("compress", &["f", "-b"]),
Parsed::MissingMaxbits
));
}
#[test]
fn version_help_and_unknown_end_the_walk_where_met() {
assert!(matches!(run("compress", &["-Vx"]), Parsed::Version));
assert!(matches!(run("compress", &["-xV"]), Parsed::Unknown(b'x')));
assert!(matches!(run("compress", &["-hx"]), Parsed::Help));
assert!(matches!(
run("compress", &["--help"]),
Parsed::Unknown(b'-')
));
assert!(matches!(run("compress", &["-C"]), Parsed::Unknown(b'C')));
}
}