use crate::{ffi, Error};
use std::ffi::CStr;
use std::marker::PhantomData;
use std::mem;
use std::os::raw::c_int;
use std::str::Utf8Error;
pub struct Args {
word_list: *const ffi::WordList,
reset_pending: bool,
}
impl Args {
#[doc(hidden)]
pub unsafe fn new(word_list: *const ffi::WordList) -> Args {
Args {
word_list,
reset_pending: true,
}
}
pub fn is_empty(&self) -> bool {
self.word_list.is_null()
}
pub fn options<'a, T>(&'a mut self) -> impl Iterator<Item = crate::Result<T>> + 'a
where
T: crate::BuiltinOptions<'a> + 'a,
{
self.ensure_reset();
OptionsIterator {
args: self,
phantom: PhantomData,
}
}
pub fn raw_arguments(&mut self) -> impl Iterator<Item = &'_ CStr> {
self.ensure_reset();
WordListIterator(self)
}
#[cfg(unix)]
#[cfg_attr(docsrs, doc(cfg(unix)))]
pub fn path_arguments(&mut self) -> impl Iterator<Item = &'_ std::path::Path> {
use std::ffi::OsStr;
use std::os::unix::ffi::OsStrExt;
use std::path::Path;
self.raw_arguments()
.map(|a| Path::new(OsStr::from_bytes(a.to_bytes())))
}
pub fn string_arguments(&mut self) -> impl Iterator<Item = Result<&'_ str, Utf8Error>> {
self.raw_arguments()
.map(|a| std::str::from_utf8(a.to_bytes()))
}
pub fn finished(&mut self) -> crate::Result<()> {
if self.word_list.is_null() {
Ok(())
} else {
crate::log::error("too many arguments");
Err(Error::Usage)
}
}
pub fn no_options(&mut self) -> crate::Result<()> {
if unsafe { ffi::no_options(self.word_list) } == 0 {
Ok(())
} else {
Err(Error::Usage)
}
}
#[inline]
fn ensure_reset(&mut self) {
if mem::take(&mut self.reset_pending) {
unsafe {
ffi::reset_internal_getopt();
ffi::list_optarg = std::ptr::null();
ffi::list_optopt = 0;
};
}
}
}
struct WordListIterator<'a>(&'a mut Args);
impl<'a> Iterator for WordListIterator<'a> {
type Item = &'a CStr;
fn next(&mut self) -> Option<Self::Item> {
if self.0.word_list.is_null() {
return None;
}
let word = unsafe {
let current = &*self.0.word_list;
self.0.word_list = current.next;
CStr::from_ptr((*current.word).word)
};
Some(word)
}
}
#[doc(hidden)]
pub trait BuiltinOptions<'a>: Sized {
fn options() -> &'static [u8];
fn from_option(opt: c_int, arg: Option<&'a CStr>) -> crate::Result<Self>;
}
struct OptionsIterator<'a, T> {
args: &'a mut Args,
phantom: PhantomData<T>,
}
impl<'a, T: BuiltinOptions<'a>> Iterator for OptionsIterator<'a, T> {
type Item = crate::Result<T>;
fn next(&mut self) -> Option<Self::Item> {
let opt =
unsafe { ffi::internal_getopt(self.args.word_list, T::options().as_ptr().cast()) };
match opt {
ffi::GETOPT_EOF => {
self.args.word_list = unsafe { ffi::loptend };
None
}
ffi::GETOPT_HELP => {
crate::log::show_help();
Some(Err(Error::Usage))
}
_ => Some(T::from_option(opt, unsafe { Self::optarg() })),
}
}
}
impl<'a, T> OptionsIterator<'a, T> {
unsafe fn optarg() -> Option<&'a CStr> {
let optarg = ffi::list_optarg;
if optarg.is_null() {
None
} else {
Some(::std::ffi::CStr::from_ptr(optarg))
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use ffi::{WordDesc, WordList};
#[test]
fn update_word_list_after_arguments() {
let words = [
WordDesc {
word: b"abc\0".as_ptr().cast(),
flags: 0,
},
WordDesc {
word: b"def\0".as_ptr().cast(),
flags: 0,
},
];
let wl1 = WordList {
word: &words[1],
next: std::ptr::null(),
};
let wl0 = WordList {
word: &words[0],
next: &wl1,
};
let mut args = unsafe { Args::new(&wl0) };
let mut string_args = args.string_arguments();
assert_eq!(string_args.next(), Some(Ok("abc")));
assert_eq!(string_args.next(), Some(Ok("def")));
assert_eq!(string_args.next(), None);
drop(string_args);
args.finished().unwrap();
}
}