#![feature(trait_alias)]
use std::{
any::Any,
borrow::Cow,
collections::HashMap,
error::Error,
ffi::{c_char, CStr, CString},
};
use features::Features;
pub use options::Opts;
use zsh_sys as zsys;
mod features;
mod hashtable;
pub mod log;
mod options;
pub mod zsh;
pub use hashtable::HashTable;
pub type AnyError = Box<dyn Error>;
pub type MaybeError<E = AnyError> = Result<(), E>;
trait AnyCmd = Cmd<dyn Any, AnyError>;
pub trait Cmd<A: Any + ?Sized, E: Into<AnyError>> =
'static + FnMut(&mut A, &str, &[&str], Opts) -> MaybeError<E>;
pub(crate) fn to_cstr(string: impl Into<Vec<u8>>) -> CString {
CString::new(string).expect("Strings should not contain a null byte!")
}
pub trait ToCString {
fn into_cstr<'a>(self) -> Cow<'a, CStr>
where
Self: 'a;
}
macro_rules! impl_tocstring {
($($type:ty),*) => {
$(impl ToCString for $type {
fn into_cstr<'a>(self) -> Cow<'a, CStr> where Self: 'a {
Cow::Owned(to_cstr(self))
}
})*
};
}
impl_tocstring!(Vec<u8>, &[u8], &str, String);
impl ToCString for &CStr {
fn into_cstr<'a>(self) -> Cow<'a, CStr>
where
Self: 'a,
{
Cow::Borrowed(self)
}
}
impl ToCString for CString {
fn into_cstr<'a>(self) -> Cow<'a, CStr> {
Cow::Owned(self)
}
}
impl ToCString for *const c_char {
fn into_cstr<'a>(self) -> Cow<'a, CStr> {
Cow::Borrowed(unsafe { CStr::from_ptr(self) })
}
}
impl ToCString for *mut c_char {
fn into_cstr<'a>(self) -> Cow<'a, CStr> {
Cow::Borrowed(unsafe { CStr::from_ptr(self) })
}
}
pub struct Builtin {
minargs: i32,
maxargs: i32,
flags: Option<CString>,
name: CString,
}
impl Builtin {
pub fn new(name: &str) -> Self {
Self {
minargs: 0,
maxargs: -1,
flags: None,
name: to_cstr(name),
}
}
pub fn minargs(mut self, value: i32) -> Self {
self.minargs = value;
self
}
pub fn maxargs(mut self, value: Option<u32>) -> Self {
self.maxargs = value.map(|i| i as i32).unwrap_or(-1);
self
}
pub fn flags(mut self, value: &str) -> Self {
self.flags = Some(to_cstr(value));
self
}
}
type Bintable = HashMap<Box<CStr>, Box<dyn AnyCmd>>;
pub struct ModuleBuilder<A> {
user_data: A,
binaries: Vec<zsys::builtin>,
bintable: Bintable,
strings: Vec<Box<CStr>>,
}
impl<A> ModuleBuilder<A>
where
A: Any + 'static,
{
pub fn new(user_data: A) -> Self {
Self {
user_data,
binaries: vec![],
bintable: HashMap::new(),
strings: Vec::with_capacity(8),
}
}
pub fn builtin<E, C>(self, mut cb: C, builtin: Builtin) -> Self
where
E: Into<Box<dyn Error>>,
C: Cmd<A, E>,
{
let closure: Box<dyn AnyCmd> = Box::new(
move |data: &mut (dyn Any + 'static), name, args, opts| -> MaybeError<AnyError> {
cb(data.downcast_mut::<A>().unwrap(), name, args, opts).map_err(E::into)
},
);
self.add_builtin(
builtin.name,
builtin.minargs,
builtin.maxargs,
builtin.flags,
closure,
)
}
fn hold_cstring(&mut self, value: impl Into<Vec<u8>>) -> *mut i8 {
let value = to_cstr(value).into_boxed_c_str();
let ptr = value.as_ptr();
self.strings.push(value);
ptr as *mut _
}
fn add_builtin(
mut self,
name: CString,
minargs: i32,
maxargs: i32,
options: Option<CString>,
cb: Box<dyn AnyCmd + 'static>,
) -> Self {
let name = name.into_boxed_c_str();
let flags = match options {
Some(flags) => self.hold_cstring(flags),
None => std::ptr::null_mut(),
};
let raw = zsys::builtin {
node: zsys::hashnode {
next: std::ptr::null_mut(),
nam: name.as_ptr() as *mut _,
flags: 0,
},
handlerfunc: None,
minargs,
maxargs,
funcid: 0,
optstr: flags,
defopts: std::ptr::null_mut(),
};
self.binaries.push(raw);
self.bintable.insert(name, cb);
self
}
pub fn build(self) -> Module {
Module::new(self)
}
}
pub struct Module {
user_data: Box<dyn Any>,
features: Features,
bintable: Bintable,
#[allow(dead_code)]
strings: Vec<Box<CStr>>,
name: Option<&'static str>,
}
impl Module {
fn new<A: Any + 'static>(desc: ModuleBuilder<A>) -> Self {
let features = Features::empty().binaries(desc.binaries.into());
Self {
user_data: Box::new(desc.user_data),
features,
bintable: desc.bintable,
strings: desc.strings,
name: None,
}
}
}
#[cfg(feature = "export_module")]
#[doc(hidden)]
pub mod export_module;