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)/* /
.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

View File

@ -32,49 +32,71 @@ extern "C" {
) -> c_int;
}
pub struct Opt {
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; } }
}
#[derive(Clone, Debug)]
pub enum OptError {
MissingArg(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 */
pub trait GetOpt {
fn getopt(&self, optstring: &str) -> Option<Result<Opt, OptError>>;
fn getopt(&self, optstring: &str) -> Option<Opt>;
}
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
.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 <colon> (':') shall be returned if getopt() detects a
@ -89,10 +111,24 @@ impl GetOpt for Vec<String> {
* 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<String> {
* 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<String>;
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<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);
}
}
}