use crate::ffi::variables as ffi;
use std::collections::HashMap;
use std::ffi::{CStr, CString};
use std::fmt;
use std::os::raw::c_char;
use std::ptr::NonNull;
mod arrays;
mod assoc;
mod dynvars;
pub use arrays::{array_get, array_set};
pub use assoc::{assoc_get, assoc_set};
pub use dynvars::DynamicVariable;
pub fn find_as_string(name: &str) -> Option<CString> {
unsafe { find_raw(name).and_then(|var| var.as_str().map(|cstr| cstr.to_owned())) }
}
pub fn find(name: &str) -> Option<Variable> {
unsafe { find_raw(name).map(|var| var.get()) }
}
pub fn find_raw(name: &str) -> Option<RawVariable> {
let name = CString::new(name).ok()?;
let shell_var = unsafe { ffi::find_variable(name.as_ptr()) as *mut _ };
NonNull::new(shell_var).map(RawVariable)
}
pub fn set<T>(name: &str, value: T) -> Result<(), VariableError>
where
T: AsRef<[u8]>,
{
let name = CString::new(name).map_err(|_| VariableError::InvalidName)?;
let value = CString::new(value.as_ref()).map_err(|_| VariableError::InvalidValue)?;
let res = unsafe {
if ffi::legal_identifier(name.as_ptr()) == 0 {
return Err(VariableError::InvalidName);
}
ffi::bind_variable(name.as_ptr(), value.as_ptr(), 0)
};
if res.is_null() {
Err(VariableError::InvalidValue)
} else {
Ok(())
}
}
pub fn unset(name: &str) -> bool {
let name = match CString::new(name) {
Ok(s) => s,
Err(_) => return false,
};
unsafe { ffi::unbind_variable(name.as_ptr()) == 0 }
}
pub fn bind(name: &str, dynvar: impl DynamicVariable + 'static) -> Result<(), VariableError> {
dynvars::bind_dynvar(name, Box::new(dynvar) as Box<dyn DynamicVariable>)
}
#[derive(Debug)]
pub enum VariableError {
InvalidName,
InvalidValue,
NotAssocArray,
InternalError(&'static str),
}
impl fmt::Display for VariableError {
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
match self {
VariableError::InvalidName => fmt.write_str("invalid variable name"),
VariableError::InvalidValue => fmt.write_str("invalid variable value"),
VariableError::NotAssocArray => fmt.write_str("variable is not an associative array"),
VariableError::InternalError(cause) => write!(fmt, "internal error: {}", cause),
}
}
}
impl std::error::Error for VariableError {}
#[derive(Debug)]
pub enum Variable {
Str(CString),
Array(Vec<CString>),
Assoc(HashMap<CString, CString>),
}
#[derive(Debug)]
pub struct RawVariable(NonNull<ffi::ShellVar>);
impl RawVariable {
pub unsafe fn is_array(&self) -> bool {
self.0.as_ref().attributes & ffi::ATT_ARRAY != 0
}
pub unsafe fn is_assoc(&self) -> bool {
self.0.as_ref().attributes & ffi::ATT_ASSOC != 0
}
pub unsafe fn get(&self) -> Variable {
unsafe fn cstr(addr: *const c_char) -> CString {
CStr::from_ptr(addr).to_owned()
}
if self.is_assoc() {
let items = self.assoc_items().map(|(k, v)| (cstr(k), cstr(v)));
Variable::Assoc(items.collect())
} else if self.is_array() {
let items = self.array_items().map(|(_, s)| cstr(s));
Variable::Array(items.collect())
} else {
Variable::Str(cstr(self.0.as_ref().value))
}
}
pub unsafe fn as_str(&self) -> Option<&CStr> {
let var = self.0.as_ref();
if var.attributes & (ffi::ATT_ARRAY | ffi::ATT_ASSOC) == 0 {
Some(CStr::from_ptr(var.value))
} else {
None
}
}
pub unsafe fn array_items(&self) -> impl Iterator<Item = (libc::intmax_t, *const c_char)> + '_ {
let array = &*(self.0.as_ref().value as *const ffi::Array);
arrays::ArrayItemsIterator::new(array)
}
pub unsafe fn assoc_items(&self) -> impl Iterator<Item = (*const c_char, *const c_char)> + '_ {
let table = &*(self.0.as_ref().value as *const ffi::HashTable);
assoc::AssocItemsIterator::new(table)
}
}