#![allow(unsafe_op_in_unsafe_fn)]
use core::ffi::CStr;
use core::ptr::NonNull;
use std::ffi::{CString, c_char, c_void};
use std::sync::Mutex;
use crate::ffi::{noesis_markup_extension_register, noesis_markup_extension_unregister};
pub enum MarkupValue<'a> {
String(&'a str),
Component(NonNull<c_void>),
Unset,
}
pub trait MarkupExtensionHandler: Send + 'static {
fn provide_value(&mut self, key: &str) -> MarkupValue<'_>;
}
impl<F> MarkupExtensionHandler for ClosureHandler<F>
where
F: FnMut(&str) -> Option<String> + Send + 'static,
{
fn provide_value(&mut self, key: &str) -> MarkupValue<'_> {
match (self.f)(key) {
Some(s) => {
self.scratch = s;
MarkupValue::String(&self.scratch)
}
None => MarkupValue::Unset,
}
}
}
pub struct ClosureHandler<F: FnMut(&str) -> Option<String> + Send + 'static> {
f: F,
scratch: String,
}
#[must_use = "dropping the guard immediately clears the registration"]
pub struct MarkupExtensionRegistration {
token: NonNull<c_void>,
}
unsafe impl Send for MarkupExtensionRegistration {}
impl MarkupExtensionRegistration {
pub fn new<H: MarkupExtensionHandler>(name: &str, handler: H) -> Option<Self> {
let cname = CString::new(name).expect("extension name contained NUL");
let boxed: Box<Box<dyn MarkupExtensionHandler>> = Box::new(Box::new(handler));
let userdata = Box::into_raw(boxed);
let token = unsafe {
noesis_markup_extension_register(
cname.as_ptr(),
provide_trampoline,
userdata.cast(),
markup_handler_free_trampoline,
)
};
let Some(token) = NonNull::new(token) else {
unsafe { drop(Box::from_raw(userdata)) };
return None;
};
Some(Self { token })
}
pub fn from_closure<F>(name: &str, f: F) -> Option<Self>
where
F: FnMut(&str) -> Option<String> + Send + 'static,
{
let handler = ClosureHandler {
f,
scratch: String::new(),
};
Self::new(name, handler)
}
pub fn token(&self) -> NonNull<c_void> {
self.token
}
}
impl Drop for MarkupExtensionRegistration {
fn drop(&mut self) {
unsafe { noesis_markup_extension_unregister(self.token.as_ptr()) };
}
}
unsafe extern "C" fn markup_handler_free_trampoline(userdata: *mut c_void) {
crate::panic_guard::guard(|| {
if userdata.is_null() {
return;
}
forget_string_scratch(userdata);
drop(Box::from_raw(
userdata.cast::<Box<dyn MarkupExtensionHandler>>(),
));
})
}
unsafe extern "C" fn provide_trampoline(
userdata: *mut c_void,
key: *const c_char,
out_string: *mut *const c_char,
out_component: *mut *mut c_void,
) -> bool {
crate::panic_guard::guard(|| {
let handler = &mut *userdata.cast::<Box<dyn MarkupExtensionHandler>>();
let key_str = if key.is_null() {
""
} else {
CStr::from_ptr(key).to_str().unwrap_or("")
};
*out_string = core::ptr::null();
*out_component = core::ptr::null_mut();
match handler.provide_value(key_str) {
MarkupValue::Unset => false,
MarkupValue::Component(ptr) => {
*out_component = ptr.as_ptr();
true
}
MarkupValue::String(s) => {
let Ok(cstring) = CString::new(s.as_bytes()) else {
return false;
};
let key = userdata as usize;
let mut table = STRING_SCRATCH.lock().expect("STRING_SCRATCH poisoned");
let slot = table.iter_mut().find(|(k, _)| *k == key);
let cstr_ptr = match slot {
Some(slot) => {
slot.1 = cstring;
slot.1.as_ptr()
}
None => {
table.push((key, cstring));
table.last().expect("just pushed").1.as_ptr()
}
};
*out_string = cstr_ptr;
true
}
}
})
}
static STRING_SCRATCH: Mutex<Vec<(usize, CString)>> = Mutex::new(Vec::new());
fn forget_string_scratch(userdata: *mut c_void) {
let key = userdata as usize;
let mut table = STRING_SCRATCH.lock().expect("STRING_SCRATCH poisoned");
table.retain(|(k, _)| *k != key);
}