pub mod path;
pub mod utf8;
#[derive(Clone, Debug, Default)]
pub struct WordExpander {
allow_commands: bool,
allow_undefined: bool,
show_stderr: bool,
}
impl WordExpander {
pub fn allow_commands(self) -> Self {
Self {
allow_commands: true,
..self
}
}
pub fn allow_undefined(self) -> Self {
Self {
allow_undefined: true,
..self
}
}
pub fn show_stderr(self) -> Self {
Self {
show_stderr: true,
..self
}
}
pub fn expand<'l, S: AsRef<str>>(self, string: S) -> Result<WordList<'l>, WordError> {
let words = std::ffi::CString::new(string.as_ref())?;
self.expand_cstr(&words)
}
pub fn expand_cstr<'l>(self, string: &std::ffi::CStr) -> Result<WordList<'l>, WordError> {
let words = string.as_ptr();
let flags = {
let mut flags = 0;
if !self.allow_commands {
flags |= wordexp_sys::WRDE_NOCMD;
}
if !self.allow_undefined {
flags |= wordexp_sys::WRDE_UNDEF;
}
if self.show_stderr {
flags |= wordexp_sys::WRDE_SHOWERR;
}
flags.try_into().expect("All flags are positive.")
};
let mut raw = wordexp_sys::wordexp_t {
we_wordc: 0,
we_wordv: std::ptr::null_mut(),
we_offs: 0,
};
let code = unsafe { wordexp_sys::wordexp(words, &mut raw, flags) };
if code == 0 {
let wordexp_sys::wordexp_t {
we_wordc: count,
we_wordv: values,
we_offs: offsets,
} = raw;
assert!(
!values.is_null(),
"This pointer should always point to at least a null pointer."
);
assert_eq!(offsets, 0, "Offsets can't have been set.");
let count = count
.try_into()
.expect("Bindgen should have handled making sure this works");
Ok(WordList(unsafe {
std::slice::from_raw_parts(values as *const *const _, count)
}))
} else {
unsafe { wordexp_sys::wordfree(&mut raw) };
Err(
match code
.try_into()
.expect("All the values it can take are positive.")
{
wordexp_sys::WRDE_BADCHAR => WordError::IllegalCharacter,
wordexp_sys::WRDE_BADVAL => WordError::UndefinedVariable,
wordexp_sys::WRDE_CMDSUB => WordError::CommandSubstitution,
wordexp_sys::WRDE_NOSPACE => panic!("out of memory"),
wordexp_sys::WRDE_SYNTAX => WordError::Syntax,
_ => unreachable!("Only these values can be returned from the call."),
},
)
}
}
}
#[derive(Clone, Debug, thiserror::Error, Eq, PartialEq)]
pub enum WordError {
#[error("command substitution was attempted")]
CommandSubstitution,
#[error("the input string has a null-byte in the middle of it: {0}")]
EmbeddedNul(#[from] std::ffi::NulError),
#[error("an illegal character was passed in the input")]
IllegalCharacter,
#[error("there was a syntax error in the input")]
Syntax,
#[error("a variable used in the input was undefined")]
UndefinedVariable,
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct WordList<'l>(&'l [*const std::os::raw::c_char]);
impl<'l> Drop for WordList<'l> {
fn drop(&mut self) {
let mut raw = wordexp_sys::wordexp_t {
we_wordc: self
.0
.len()
.try_into()
.expect("Bindgen should have handled making this work."),
we_wordv: self.0.as_ptr() as *mut _,
we_offs: 0,
};
unsafe { wordexp_sys::wordfree(&mut raw) };
}
}
impl<'l> std::ops::Index<usize> for WordList<'l> {
type Output = std::ffi::CStr;
fn index(&self, index: usize) -> &'l Self::Output {
unsafe { std::ffi::CStr::from_ptr(self.0[index]) }
}
}
impl<'l> WordList<'l> {
pub fn is_empty(&self) -> bool {
self.0.is_empty()
}
pub fn len(&self) -> usize {
self.0.len()
}
pub fn get(&self, index: usize) -> Option<&<Self as std::ops::Index<usize>>::Output> {
if index < self.len() {
Some(&self[index])
} else {
None
}
}
pub unsafe fn get_unchecked(&self, index: usize) -> &<Self as std::ops::Index<usize>>::Output {
let element = self.0.get_unchecked(index);
std::ffi::CStr::from_ptr(*element)
}
}
impl<'u, 'l: 'u> WordList<'l> {
pub fn utf8(&'u self) -> utf8::WordList<'u, 'l> {
utf8::WordList::new(self)
}
pub fn utf8_eager(&'u self) -> Result<utf8::WordList<'u, 'l>, std::str::Utf8Error> {
utf8::WordList::new_eager(self)
}
}
impl<'p, 'l: 'p> WordList<'l> {
pub fn path(&'p self) -> path::WordList<'p, 'l> {
path::WordList::new(self)
}
}
pub struct Iter<'r, 'l: 'r> {
word_list: &'r WordList<'l>,
index: usize,
}
impl<'r, 'l: 'r> IntoIterator for &'r WordList<'l> {
type IntoIter = Iter<'r, 'l>;
type Item = <Self::IntoIter as Iterator>::Item;
fn into_iter(self) -> Self::IntoIter {
Iter {
word_list: self,
index: 0,
}
}
}
impl<'r, 'l: 'r> Iterator for Iter<'r, 'l> {
type Item = &'r <WordList<'l> as std::ops::Index<usize>>::Output;
fn next(&mut self) -> Option<Self::Item> {
if self.index >= self.word_list.len() {
None
} else {
let result = &self.word_list[self.index];
self.index += 1;
Some(result)
}
}
}