use crate::pool::Pool;
use alloc::borrow::ToOwned;
use alloc::string::String;
use alloc::vec::Vec;
use core::marker::PhantomData;
pub trait IntoAllowedOptionChars {
fn into_iter(self) -> impl Iterator<Item = char>;
}
impl IntoAllowedOptionChars for &str {
fn into_iter(self) -> impl core::iter::Iterator<Item = char> {
self.chars().collect::<Vec<_>>().into_iter()
}
}
impl IntoAllowedOptionChars for &[char] {
fn into_iter(self) -> impl core::iter::Iterator<Item = char> {
self.iter().copied()
}
}
pub struct Option<'pool> {
ptr: *mut apr_sys::apr_getopt_option_t,
_pool: PhantomData<&'pool Pool<'pool>>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Indicator {
Sentinel,
Letter(char),
Identifier(i32),
}
impl From<Indicator> for i32 {
fn from(indicator: Indicator) -> Self {
match indicator {
Indicator::Sentinel => 0,
Indicator::Letter(c) => c as i32,
Indicator::Identifier(i) => 255 + i,
}
}
}
impl From<i32> for Indicator {
fn from(indicator: i32) -> Self {
if indicator == 0 {
Indicator::Sentinel
} else if indicator > 255 {
Indicator::Identifier(indicator - 255)
} else {
Indicator::Letter(indicator as u8 as char)
}
}
}
impl<'pool> Option<'pool> {
pub fn new(
pool: &'pool crate::pool::Pool<'pool>,
name: &str,
has_arg: bool,
indicator: Indicator,
description: core::option::Option<&'pool str>,
) -> Self {
let name = alloc::ffi::CString::new(name).unwrap();
let description = description.map(|s| alloc::ffi::CString::new(s).unwrap());
let option = pool.calloc::<apr_sys::apr_getopt_option_t>();
unsafe {
(*option).name = name.as_ptr() as *mut _;
(*option).has_arg = if has_arg { 1 } else { 0 };
(*option).optch = indicator.into();
if let Some(description) = description {
(*option).description = description.as_ptr() as *mut _;
}
}
Self {
ptr: option,
_pool: PhantomData,
}
}
pub fn name(&self) -> &str {
unsafe {
let name = (*self.ptr).name;
core::ffi::CStr::from_ptr(name).to_str().unwrap()
}
}
pub fn has_arg(&self) -> bool {
unsafe { (*self.ptr).has_arg != 0 }
}
pub fn optch(&self) -> core::option::Option<u8> {
unsafe {
let v = (*self.ptr).optch;
if v > 255 {
None
} else {
Some(v as u8)
}
}
}
pub fn description(&self) -> &str {
unsafe {
let description = (*self.ptr).description;
core::ffi::CStr::from_ptr(description).to_str().unwrap()
}
}
pub fn as_ptr(&self) -> *const apr_sys::apr_getopt_option_t {
self.ptr
}
pub fn as_mut_ptr(&mut self) -> *mut apr_sys::apr_getopt_option_t {
self.ptr
}
}
pub struct Getopt<'pool> {
ptr: *mut apr_sys::apr_getopt_t,
_pool: Pool<'pool>, }
pub enum GetoptResult {
Option(Indicator, core::option::Option<String>),
MissingArgument(char),
BadOption(char),
End,
}
impl Getopt<'_> {
pub fn new(args: &[&str]) -> Result<Self, crate::Status> {
let mut os = core::ptr::null_mut();
let pool = crate::pool::Pool::new();
let argv = args
.iter()
.map(|s| {
let s = alloc::ffi::CString::new(*s).unwrap();
unsafe { apr_sys::apr_pstrdup(pool.as_mut_ptr(), s.as_ptr()) }
})
.collect::<Vec<_>>();
let rv = unsafe {
apr_sys::apr_getopt_init(
&mut os,
pool.as_mut_ptr(),
args.len() as i32,
argv.as_slice().as_ptr() as *mut _,
)
};
let status = crate::Status::from(rv);
if status.is_success() {
Ok(Self {
ptr: os,
_pool: pool,
})
} else {
Err(status)
}
}
pub fn args(&self) -> Vec<&str> {
unsafe {
let args = (*self.ptr).argv;
let args = core::slice::from_raw_parts(args, (*self.ptr).argc as usize);
args.iter()
.map(|&s| core::ffi::CStr::from_ptr(s).to_str().unwrap())
.collect::<Vec<_>>()
}
}
pub fn allow_interleaving(&mut self, allow: bool) {
unsafe {
(*self.ptr).interleave = if allow { 1 } else { 0 };
}
}
pub fn skip_start(&mut self, skip: i32) {
unsafe {
(*self.ptr).skip_start = skip;
}
}
pub fn skip_end(&mut self, skip: i32) {
unsafe {
(*self.ptr).skip_end = skip;
}
}
pub fn getopt(&mut self, opts: impl IntoAllowedOptionChars) -> GetoptResult {
let mut opts: Vec<core::ffi::c_char> =
opts.into_iter().map(|c| c as core::ffi::c_char).collect();
opts.push(0);
let mut option_ch = 0;
let mut option_arg: *const core::ffi::c_char = core::ptr::null_mut();
let rv = unsafe {
apr_sys::apr_getopt(
self.ptr,
opts.as_slice().as_ptr(),
&mut option_ch,
&mut option_arg,
)
};
match rv as u32 {
apr_sys::APR_SUCCESS => {
let option_ch = option_ch as u8;
let option_arg = if option_arg.is_null() {
None
} else {
Some(
unsafe { core::ffi::CStr::from_ptr(option_arg) }
.to_str()
.unwrap()
.to_owned(),
)
};
GetoptResult::Option(Indicator::Letter(option_ch as char), option_arg)
}
apr_sys::APR_EOF => GetoptResult::End,
apr_sys::APR_BADCH => GetoptResult::BadOption(option_ch as u8 as char),
apr_sys::APR_BADARG => GetoptResult::MissingArgument(option_ch as u8 as char),
_ => panic!("unexpected status: {}", rv),
}
}
pub fn getopt_long(&mut self, opts: &[Option]) -> GetoptResult {
let mut option_ch: i32 = 0;
let mut option_arg: *const core::ffi::c_char = core::ptr::null();
let mut opts = opts
.iter()
.map(|o| o.as_ptr())
.map(|ptr| unsafe { *ptr })
.collect::<Vec<_>>();
opts.push(apr_sys::apr_getopt_option_t {
name: core::ptr::null(),
has_arg: 0,
optch: 0,
description: core::ptr::null(),
});
let rv = unsafe {
apr_sys::apr_getopt_long(
self.ptr,
opts.as_slice().as_ptr(),
&mut option_ch,
&mut option_arg,
)
};
match rv as u32 {
apr_sys::APR_SUCCESS => {
let option_arg = if option_arg.is_null() {
None
} else {
Some(
unsafe { core::ffi::CStr::from_ptr(option_arg) }
.to_str()
.unwrap()
.to_owned(),
)
};
GetoptResult::Option(option_ch.into(), option_arg)
}
apr_sys::APR_EOF => GetoptResult::End,
apr_sys::APR_BADCH => GetoptResult::BadOption(option_ch as u8 as char),
apr_sys::APR_BADARG => GetoptResult::MissingArgument(option_ch as u8 as char),
_ => panic!("unexpected status: {}", rv),
}
}
pub fn as_ptr(&self) -> *const apr_sys::apr_getopt_t {
self.ptr
}
pub fn as_mut_ptr(&mut self) -> *mut apr_sys::apr_getopt_t {
self.ptr
}
}
#[cfg(test)]
mod tests {
use alloc::borrow::ToOwned;
use alloc::string::ToString;
use alloc::vec;
#[test]
fn test_getopt_long() {
let pool = crate::pool::Pool::new();
let args = vec!["test", "-a", "-b", "foo", "-c", "bar"];
let mut getopt = crate::getopt::Getopt::new(&args).unwrap();
assert_eq!(getopt.args(), &args[..]);
let opts = vec![
crate::getopt::Option::new(&pool, "a", false, super::Indicator::Letter('a'), None),
crate::getopt::Option::new(&pool, "b", true, super::Indicator::Letter('b'), None),
crate::getopt::Option::new(&pool, "c", true, super::Indicator::Letter('c'), None),
];
let mut got = vec![];
loop {
match getopt.getopt_long(&opts) {
super::GetoptResult::Option(ch, arg) => got.push((ch, arg)),
super::GetoptResult::End => break,
super::GetoptResult::BadOption(o) => panic!("unexpected option: {}", o),
super::GetoptResult::MissingArgument(o) => panic!("missing argument: {}", o),
}
}
assert_eq!(
got,
vec![
(super::Indicator::Letter('a'), None),
(super::Indicator::Letter('b'), Some("foo".to_owned())),
(super::Indicator::Letter('c'), Some("bar".to_owned()))
]
);
}
#[test]
fn test_getopt() {
let args = vec!["test", "-a", "-b", "foo", "-c", "bar"];
let mut getopt = crate::getopt::Getopt::new(&args).unwrap();
assert_eq!(getopt.args(), &args[..]);
getopt.allow_interleaving(true);
getopt.skip_start(1);
getopt.skip_end(1);
let mut got = vec![];
loop {
match getopt.getopt("ab:c:") {
super::GetoptResult::Option(ch, arg) => got.push((ch, arg)),
super::GetoptResult::End => break,
_ => panic!("unexpected result"),
}
}
assert_eq!(
got,
vec![
(super::Indicator::Letter('a'), None),
(super::Indicator::Letter('b'), Some("foo".to_owned())),
(super::Indicator::Letter('c'), Some("bar".to_owned()))
]
);
}
}