getopt.rs(3): adds testing

This commit is contained in:
Emma Tebibyte 2024-06-22 22:18:13 -06:00
parent 40da8135f1
commit 8f990ba515
Signed by: emma
GPG Key ID: 06FA419A1698C270
2 changed files with 111 additions and 46 deletions

View File

@ -49,9 +49,13 @@ install: dist
cp -r $(DESTDIR)/* / cp -r $(DESTDIR)/* /
.PHONY: test .PHONY: test
test: build test: build /tmp/getopt
tests/posix-compat.sh tests/posix-compat.sh
/tmp/getopt: src/getopt.rs
$(RUSTC) --test -o /tmp/getopt src/getopt.rs
/tmp/getopt
.PHONY: rustlibs .PHONY: rustlibs
rustlibs: build/o/libsysexits.rlib build/o/libgetopt.rlib \ rustlibs: build/o/libsysexits.rlib build/o/libgetopt.rlib \
build/o/libstrerror.rlib build/o/libstrerror.rlib

View File

@ -32,46 +32,68 @@ extern "C" {
) -> c_int; ) -> c_int;
} }
pub struct Opt { #[derive(Clone, Debug)]
pub arg: Option<String>,
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; } }
}
pub enum OptError { pub enum OptError {
MissingArg(String), MissingArg(String),
UnknownOpt(String), UnknownOpt(String),
} }
#[derive(Clone, Debug)]
pub struct Opt {
arg: Option<String>,
ind: *mut i32,
opt: Result<String, OptError>,
}
impl Opt {
pub fn arg(&self) -> Option<String> { 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 */ /* function signature */
pub trait GetOpt { pub trait GetOpt {
fn getopt(&self, optstring: &str) -> Option<Result<Opt, OptError>>; fn getopt(&self, optstring: &str) -> Option<Opt>;
} }
impl GetOpt for Vec<String> { impl GetOpt for Vec<String> {
fn getopt(&self, optstring: &str) -> Option<Result<Opt, OptError>> { fn getopt(&self, optstring: &str) -> Option<Opt> {
let c_strings: Vec<_> = self let c_strings: Vec<_> = self
.iter() .iter()
.cloned() .cloned()
.map(CString::new) .map(|x| CString::new(x).unwrap().into_raw())
.map(Result::unwrap)
.collect(); .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 /* these operations must be separated out into separate operations so
* the CStrings can live long enough */ * 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 opts = CString::new(optstring).unwrap().into_raw();
let len = self.len() as c_int; let len = self.len() as c_int;
unsafe { unsafe {
match getopt(len, argv_ptr, opts) { let ret = match getopt(len, argv, opts) {
/* From getopt(3p): /* From getopt(3p):
* *
* The getopt() function shall return the next option character * The getopt() function shall return the next option character
@ -89,10 +111,24 @@ impl GetOpt for Vec<String> {
* Otherwise, getopt() shall return -1 when all command line * Otherwise, getopt() shall return -1 when all command line
* options are parsed. */ * options are parsed. */
58 => { /* numerical ASCII value for ':' */ 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 '?' */ 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): /* From getopt(3p):
* *
@ -109,31 +145,56 @@ impl GetOpt for Vec<String> {
* getopt() shall return -1 after incrementing optind. */ * getopt() shall return -1 after incrementing optind. */
-1 => return None, -1 => return None,
opt => { opt => {
let arg = CStr::from_ptr(optarg) let arg: Option<String>;
.to_string_lossy()
.into_owned();
Some(Ok(Opt { if optarg.is_null() { arg = None; }
arg: Some(arg), /* opt argument */ else {
/* From getopt(3p): arg = Some(CStr::from_ptr(optarg)
* .to_string_lossy()
* The variable optind is the index of the next element .into_owned());
* 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 Some(Opt {
* argv[]. If the application sets optind to zero arg, /* opt argument */
* before calling getopt(), the behavior is unspecified. ind: std::ptr::addr_of_mut!(optind), /* opt index */
* When an element of argv[] contains multiple option /* option itself */
* characters, it is unspecified how getopt() opt: Ok((opt as u8 as char).to_string()),
* 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 */
}))
}, },
};
/* delloc argv */
let _ = Box::from_raw(boxed);
return ret;
} }
} }
} }
#[cfg(test)]
mod tests {
use GetOpt;
#[test]
fn testing() {
let argv: Vec<String> = ["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);
}
}
} }