extern crate libc;
mod ll;
use std::ffi::{CStr, CString};
pub const WRDE_DOOFFS: i32 = 1;
pub const WRDE_APPEND: i32 = (1 << 1);
pub const WRDE_NOCMD: i32 = (1 << 2);
pub const WRDE_REUSE: i32 = (1 << 3);
pub const WRDE_SHOWERR: i32 = (1 << 4);
pub const WRDE_UNDEF: i32 = (1 << 5);
pub const WRDE_NOSPACE: i32 = 1;
pub const WRDE_BADCHAR: i32 = 2;
pub const WRDE_BADVAL: i32 = 3;
pub const WRDE_CMDSUB: i32 = 4;
pub const WRDE_SYNTAX: i32 = 5;
trait ToCStr {
fn to_c_str(&self) -> CString;
}
impl<'a> ToCStr for &'a str {
fn to_c_str(&self) -> CString {
CString::new(*self).unwrap()
}
}
#[derive(Clone, Debug)]
pub enum WordexpErrorType {
BadChar,
BadVal,
CmdSub,
NoSpace,
Syntax,
Unknown,
}
impl WordexpErrorType {
pub fn from(error_code: i32) -> Self {
match error_code {
WRDE_BADCHAR => WordexpErrorType::BadChar,
WRDE_BADVAL => WordexpErrorType::BadVal,
WRDE_CMDSUB => WordexpErrorType::CmdSub,
WRDE_NOSPACE => WordexpErrorType::NoSpace,
WRDE_SYNTAX => WordexpErrorType::Syntax,
_ => WordexpErrorType::Unknown,
}
}
}
#[derive(Clone, Debug)]
pub struct WordexpError {
pub error_type: WordexpErrorType,
}
impl WordexpError {
pub fn new(error_type: WordexpErrorType) -> Self {
WordexpError { error_type }
}
}
impl std::fmt::Display for WordexpError {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
write!(f, "{:?}", self.error_type)
}
}
impl std::error::Error for WordexpError {}
pub struct Wordexp<'a> {
pub we_offs: usize,
pub we_wordv: Vec<Option<&'a str>>,
pub wordexp_ref: Option<ll::wordexp_t>,
counter: usize,
}
impl<'a> Wordexp<'a> {
pub fn new(we_offs: usize) -> Self {
Wordexp {
we_offs,
we_wordv: Vec::default(),
wordexp_ref: None,
counter: usize::default(),
}
}
pub fn update(&mut self) {
if let Some(wordexp_ref) = self.wordexp_ref.as_mut() {
let we_wordc: usize = wordexp_ref.we_wordc as usize;
let we_offs: usize = wordexp_ref.we_offs as usize;
let we_wordv: Vec<Option<&str>> = unsafe {
let ptr: *const *const libc::c_char = wordexp_ref.we_wordv;
(0..we_wordc)
.map(|i| {
let nptr = ptr.add(i);
if nptr == std::ptr::null() {
None
} else {
let cstr = CStr::from_ptr(*nptr);
if let Ok(s) = cstr.to_str() {
Some(s)
} else {
None
}
}
})
.collect()
};
self.we_wordv = we_wordv;
self.we_offs = we_offs;
}
}
}
impl<'a> std::iter::Iterator for Wordexp<'a> {
type Item = &'a str;
#[inline]
fn next(&mut self) -> Option<Self::Item> {
if self.counter >= self.we_wordv.len() {
self.counter = 0;
None
} else {
let item = self.we_wordv[self.counter];
self.counter += 1;
item
}
}
}
pub fn wordexp<'a>(s: &str, mut p: Wordexp<'a>, flags: i32) -> Result<Wordexp<'a>, WordexpError> {
if p.wordexp_ref.is_none() {
let wordexp_c = ll::wordexp_t {
we_wordc: 0,
we_wordv: std::ptr::null(),
we_offs: p.we_offs,
};
p.wordexp_ref = Some(wordexp_c);
}
let result = unsafe {
let result = ll::wordexp(
s.to_c_str().as_ptr(),
p.wordexp_ref.as_mut().unwrap(),
flags,
);
if result == 0 {
p.update();
Ok(Wordexp::from(p))
} else {
let err_type = WordexpErrorType::from(result);
Err(WordexpError::new(err_type))
}
};
result
}
#[cfg(test)]
mod tests {
use super::{wordexp, Wordexp};
#[test]
fn it_works() {
std::env::set_var("HOME", "/home/wordexp");
let s = "~/";
let p = Wordexp::new(0);
let flags = 0;
match wordexp(s, p, flags) {
Ok(s) => {
assert_eq!(0, s.we_offs);
assert_eq!(1, s.we_wordv.len());
match s.we_wordv[0] {
None => assert!(false),
Some(s) => assert_eq!("/home/wordexp/", s),
}
}
Err(_) => assert!(false),
}
}
}