From 8f990ba515ccdb548b8f4a858e128e204842fcbd Mon Sep 17 00:00:00 2001 From: emma Date: Sat, 22 Jun 2024 22:18:13 -0600 Subject: [PATCH] getopt.rs(3): adds testing --- Makefile | 6 +- src/getopt.rs | 151 +++++++++++++++++++++++++++++++++++--------------- 2 files changed, 111 insertions(+), 46 deletions(-) diff --git a/Makefile b/Makefile index 7736a78..734a62c 100644 --- a/Makefile +++ b/Makefile @@ -49,9 +49,13 @@ install: dist cp -r $(DESTDIR)/* / .PHONY: test -test: build +test: build /tmp/getopt tests/posix-compat.sh +/tmp/getopt: src/getopt.rs + $(RUSTC) --test -o /tmp/getopt src/getopt.rs + /tmp/getopt + .PHONY: rustlibs rustlibs: build/o/libsysexits.rlib build/o/libgetopt.rlib \ build/o/libstrerror.rlib diff --git a/src/getopt.rs b/src/getopt.rs index 5ce094c..dc88229 100644 --- a/src/getopt.rs +++ b/src/getopt.rs @@ -32,49 +32,71 @@ extern "C" { ) -> c_int; } -pub struct Opt { - pub arg: Option, - ind: *mut i32, - pub opt: String, -} - -impl Opt { - pub fn index(&self) -> usize { unsafe { *self.ind as usize } } - - pub fn set_index(&self, ind: i32) { unsafe { *self.ind = ind; } } -} - +#[derive(Clone, Debug)] pub enum OptError { MissingArg(String), UnknownOpt(String), } +#[derive(Clone, Debug)] +pub struct Opt { + arg: Option, + ind: *mut i32, + opt: Result, +} + +impl Opt { + pub fn arg(&self) -> Option { self.arg.clone() } + + /* sets optarg if default is desired */ + pub fn arg_or(&self, default: impl std::fmt::Display) -> String { + default.to_string() + } + + pub fn opt(&self) -> Result<&str, OptError> { + self.opt.as_ref().map(|o| o.as_str()).map_err(OptError::clone) + } + + /* From getopt(3p): + * + * The variable optind is the index of the next element of the argv[] + * vector to be processed. It shall be initialized to 1 by the system, and + * getopt() shall update it when it finishes with each element of argv[]. + * If the application sets optind to zero before calling getopt(), the + * behavior is unspecified. When an element of argv[] contains multiple + * option characters, it is unspecified how getopt() determines which + * options have already been processed. */ + pub fn ind(&self) -> usize { unsafe { *self.ind as usize } } + + pub fn set_ind(&self, ind: i32) { unsafe { *self.ind = ind; } } +} + /* function signature */ pub trait GetOpt { - fn getopt(&self, optstring: &str) -> Option>; + fn getopt(&self, optstring: &str) -> Option; } impl GetOpt for Vec { - fn getopt(&self, optstring: &str) -> Option> { + fn getopt(&self, optstring: &str) -> Option { let c_strings: Vec<_> = self .iter() .cloned() - .map(CString::new) - .map(Result::unwrap) + .map(|x| CString::new(x).unwrap().into_raw()) .collect(); + let boxed = Box::into_raw(c_strings.into_boxed_slice()); + let argv = boxed as *const *mut i8; + /* these operations must be separated out into separate operations so * the CStrings can live long enough */ - let argv: Vec<_> = c_strings.iter().map(|x| x.as_ptr()).collect(); - let argv_ptr = argv.as_ptr() as *const *mut c_char; let opts = CString::new(optstring).unwrap().into_raw(); let len = self.len() as c_int; unsafe { - match getopt(len, argv_ptr, opts) { + let ret = match getopt(len, argv, opts) { /* From getopt(3p): * - * The getopt() f unction shall return the next option character + * The getopt() function shall return the next option character * specified on the command line. * * A (':') shall be returned if getopt() detects a @@ -89,10 +111,24 @@ impl GetOpt for Vec { * Otherwise, getopt() shall return -1 when all command line * options are parsed. */ 58 => { /* numerical ASCII value for ':' */ - Some(Err(OptError::MissingArg(optopt.to_string()))) + Some(Opt { + /* opt argument */ + arg: None, + /* opt index */ + ind: std::ptr::addr_of_mut!(optind), + /* error containing option */ + opt: Err(OptError::MissingArg(optopt.to_string())), + }) }, 63 => { /* numerical ASCII value for '?' */ - Some(Err(OptError::UnknownOpt(optopt.to_string()))) + Some(Opt { + /* opt argument */ + arg: None, + /* opt index */ + ind: std::ptr::addr_of_mut!(optind), + /* error containing option */ + opt: Err(OptError::UnknownOpt(optopt.to_string())), + }) }, /* From getopt(3p): * @@ -109,31 +145,56 @@ impl GetOpt for Vec { * getopt() shall return -1 after incrementing optind. */ -1 => return None, opt => { - let arg = CStr::from_ptr(optarg) - .to_string_lossy() - .into_owned(); + let arg: Option; - Some(Ok(Opt { - arg: Some(arg), /* opt argument */ - /* From getopt(3p): - * - * The variable optind is the index of the next element - * of the argv[] vector to be processed. It shall be - * initialized to 1 by the system, and getopt() shall - * update it when it finishes with each element of - * argv[]. If the application sets optind to zero - * before calling getopt(), the behavior is unspecified. - * When an element of argv[] contains multiple option - * characters, it is unspecified how getopt() - * determines which options have already been processed. - * - * This API is can be utilized with the index() and - * set_index() methods implemented for Opt. */ - ind: std::ptr::addr_of_mut!(optind), - opt: opt.to_string(), /* option itself */ - })) + if optarg.is_null() { arg = None; } + else { + arg = Some(CStr::from_ptr(optarg) + .to_string_lossy() + .into_owned()); + } + + Some(Opt { + arg, /* opt argument */ + ind: std::ptr::addr_of_mut!(optind), /* opt index */ + /* option itself */ + opt: Ok((opt as u8 as char).to_string()), + }) }, - } + }; + + /* delloc argv */ + let _ = Box::from_raw(boxed); + return ret; } } } + +#[cfg(test)] +mod tests { + use GetOpt; + + #[test] + fn testing() { + let argv: Vec = ["test", "-b", "-f", "arg", "-o", "arg"] + .iter() + .map(|s| s.to_string()) + .collect(); + + while let Some(opt) = argv.getopt(":abf:o:") { + match opt.opt() { + Ok("a") => assert_eq!(opt.ind(), 1), + Ok("b") => assert_eq!(opt.ind(), 2), + Ok("f") | Ok("o") => { + assert_eq!(opt.arg(), Some("arg".into())); + }, + _ => assert!(false), + }; + } + + if let Some(opt) = argv.getopt("abc:") { + opt.clone().set_ind(1); + assert_eq!(opt.ind(), 1); + } + } +}