use libc::{c_char, c_int};
use std::{
cell::{Ref, RefCell, RefMut},
collections::HashMap,
ffi::CStr,
ops::{Deref, DerefMut},
os::raw::c_void,
ptr,
rc::Weak,
};
use std::marker::PhantomData;
use weechat_sys::{t_config_file, t_config_option, t_config_section, t_weechat_plugin};
use crate::{
config::{
config_options::{CheckCB, OptionPointers, OptionType},
BaseConfigOption, BooleanOption, BooleanOptionSettings, ColorOption, ColorOptionSettings,
Conf, Config, ConfigOptions, IntegerOption, IntegerOptionSettings, OptionChanged,
StringOption, StringOptionSettings,
},
LossyCString, Weechat,
};
#[derive(Default)]
struct OptionDescription<'a> {
pub name: &'a str,
pub option_type: OptionType,
pub description: &'a str,
pub string_values: &'a str,
pub min: i32,
pub max: i32,
pub default_value: &'a str,
pub value: &'a str,
pub null_allowed: bool,
}
#[allow(missing_docs)]
pub enum ConfigOption<'a> {
Boolean(BooleanOption<'a>),
Integer(IntegerOption<'a>),
String(StringOption<'a>),
Color(ColorOption<'a>),
}
impl<'a> ConfigOption<'a> {
fn as_base_config_option(&self) -> &(dyn BaseConfigOption + 'a) {
match self {
ConfigOption::Color(ref o) => o,
ConfigOption::Boolean(ref o) => o,
ConfigOption::Integer(ref o) => o,
ConfigOption::String(ref o) => o,
}
}
}
impl<'a> Deref for ConfigOption<'a> {
type Target = dyn BaseConfigOption + 'a;
fn deref(&self) -> &Self::Target {
self.as_base_config_option()
}
}
impl<'a> AsRef<dyn BaseConfigOption + 'a> for dyn BaseConfigOption + 'a {
fn as_ref(&self) -> &(dyn BaseConfigOption + 'a) {
self
}
}
impl<'a> AsRef<dyn BaseConfigOption + 'a> for BooleanOption<'a> {
fn as_ref(&self) -> &(dyn BaseConfigOption + 'a) {
self
}
}
impl<'a> AsRef<dyn BaseConfigOption + 'a> for ColorOption<'a> {
fn as_ref(&self) -> &(dyn BaseConfigOption + 'a) {
self
}
}
impl<'a> AsRef<dyn BaseConfigOption + 'a> for IntegerOption<'a> {
fn as_ref(&self) -> &(dyn BaseConfigOption + 'a) {
self
}
}
impl<'a> AsRef<dyn BaseConfigOption + 'a> for StringOption<'a> {
fn as_ref(&self) -> &(dyn BaseConfigOption + 'a) {
self
}
}
impl<'a> AsRef<dyn BaseConfigOption + 'a> for ConfigOption<'a> {
fn as_ref(&self) -> &(dyn BaseConfigOption + 'a) {
self.as_base_config_option()
}
}
#[derive(Debug)]
pub(crate) enum ConfigOptionPointers {
Boolean(*const c_void),
Integer(*const c_void),
String(*const c_void),
Color(*const c_void),
}
pub struct SectionHandleMut<'a> {
pub(crate) inner: RefMut<'a, ConfigSection>,
}
pub struct SectionHandle<'a> {
pub(crate) inner: Ref<'a, ConfigSection>,
}
impl<'a> Deref for SectionHandle<'a> {
type Target = ConfigSection;
fn deref(&self) -> &Self::Target {
&*self.inner
}
}
impl<'a> Deref for SectionHandleMut<'a> {
type Target = ConfigSection;
fn deref(&self) -> &Self::Target {
&*self.inner
}
}
impl<'a> DerefMut for SectionHandleMut<'a> {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut *self.inner
}
}
#[derive(Debug)]
pub struct ConfigSection {
pub(crate) ptr: *mut t_config_section,
pub(crate) config_ptr: *mut t_config_file,
pub(crate) weechat_ptr: *mut t_weechat_plugin,
pub(crate) name: String,
pub(crate) section_data: *const c_void,
pub(crate) option_pointers: HashMap<String, ConfigOptionPointers>,
}
pub trait SectionWriteCallback: 'static {
fn callback(&mut self, weechat: &Weechat, config: &Conf, section: &mut ConfigSection);
}
impl<T: FnMut(&Weechat, &Conf, &mut ConfigSection) + 'static> SectionWriteCallback for T {
fn callback(&mut self, weechat: &Weechat, config: &Conf, section: &mut ConfigSection) {
self(weechat, config, section)
}
}
pub trait SectionWriteDefaultCallback: 'static {
fn callback(&mut self, weechat: &Weechat, config: &Conf, section: &mut ConfigSection);
}
impl<T: FnMut(&Weechat, &Conf, &mut ConfigSection) + 'static> SectionWriteDefaultCallback for T {
fn callback(&mut self, weechat: &Weechat, config: &Conf, section: &mut ConfigSection) {
self(weechat, config, section)
}
}
pub trait SectionReadCallback: 'static {
fn callback(
&mut self,
weechat: &Weechat,
config: &Conf,
section: &mut ConfigSection,
option_name: &str,
option_value: &str,
) -> OptionChanged;
}
impl<T: FnMut(&Weechat, &Conf, &mut ConfigSection, &str, &str) -> OptionChanged + 'static>
SectionReadCallback for T
{
fn callback(
&mut self,
weechat: &Weechat,
config: &Conf,
section: &mut ConfigSection,
option_name: &str,
option_value: &str,
) -> OptionChanged {
self(weechat, config, section, option_name, option_value)
}
}
pub(crate) struct ConfigSectionPointers {
pub(crate) read_cb: Option<Box<dyn SectionReadCallback>>,
pub(crate) write_cb: Option<Box<dyn SectionWriteCallback>>,
pub(crate) write_default_cb: Option<Box<dyn SectionWriteDefaultCallback>>,
pub(crate) section: Option<Weak<RefCell<ConfigSection>>>,
pub(crate) weechat_ptr: *mut t_weechat_plugin,
}
impl std::fmt::Debug for ConfigSectionPointers {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(
f,
"ConfigSectionPointers {{ section_ptr: {:?} weechat_ptr: {:?}}}",
self.section, self.weechat_ptr
)
}
}
#[derive(Default)]
pub struct ConfigSectionSettings {
pub(crate) name: String,
pub(crate) read_callback: Option<Box<dyn SectionReadCallback>>,
pub(crate) write_callback: Option<Box<dyn SectionWriteCallback>>,
pub(crate) write_default_callback: Option<Box<dyn SectionWriteDefaultCallback>>,
}
impl ConfigSectionSettings {
pub fn new<P: Into<String>>(name: P) -> Self {
ConfigSectionSettings {
name: name.into(),
..Default::default()
}
}
pub fn set_read_callback(mut self, callback: impl SectionReadCallback) -> Self {
self.read_callback = Some(Box::new(callback));
self
}
pub fn set_write_callback(
mut self,
callback: impl FnMut(&Weechat, &Conf, &mut ConfigSection) + 'static,
) -> Self {
self.write_callback = Some(Box::new(callback));
self
}
pub fn set_write_default_callback(
mut self,
callback: impl FnMut(&Weechat, &Conf, &mut ConfigSection) + 'static,
) -> Self {
self.write_default_callback = Some(Box::new(callback));
self
}
}
impl Drop for ConfigSection {
fn drop(&mut self) {
let weechat = Weechat::from_ptr(self.weechat_ptr);
let options_free = weechat.get().config_section_free_options.unwrap();
let section_free = weechat.get().config_section_free.unwrap();
for (_, option_ptrs) in self.option_pointers.drain() {
unsafe {
match option_ptrs {
ConfigOptionPointers::Integer(p) => {
Box::from_raw(p as *mut OptionPointers<IntegerOption>);
}
ConfigOptionPointers::Boolean(p) => {
Box::from_raw(p as *mut OptionPointers<BooleanOption>);
}
ConfigOptionPointers::String(p) => {
Box::from_raw(p as *mut OptionPointers<StringOption>);
}
ConfigOptionPointers::Color(p) => {
Box::from_raw(p as *mut OptionPointers<ColorOption>);
}
}
}
}
unsafe {
Box::from_raw(self.section_data as *mut ConfigSectionPointers);
options_free(self.ptr);
section_free(self.ptr);
};
}
}
pub(crate) type SectionReadCbT = unsafe extern "C" fn(
pointer: *const c_void,
_data: *mut c_void,
_config: *mut t_config_file,
_section: *mut t_config_section,
option_name: *const i8,
value: *const i8,
) -> c_int;
pub(crate) type SectionWriteCbT = unsafe extern "C" fn(
pointer: *const c_void,
_data: *mut c_void,
_config: *mut t_config_file,
section_name: *const c_char,
) -> c_int;
type WeechatOptChangeCbT = unsafe extern "C" fn(
pointer: *const c_void,
_data: *mut c_void,
option_pointer: *mut t_config_option,
);
type WeechatOptCheckCbT = unsafe extern "C" fn(
pointer: *const c_void,
_data: *mut c_void,
option_pointer: *mut t_config_option,
value: *const c_char,
) -> c_int;
impl ConfigSection {
pub fn name(&self) -> &str {
&self.name
}
pub fn options(&self) -> Vec<ConfigOption> {
self.option_pointers
.keys()
.map(|option_name| self.search_option(option_name).unwrap())
.collect()
}
pub fn free_option(&mut self, option_name: &str) -> Result<(), ()> {
let weechat = Weechat::from_ptr(self.weechat_ptr);
let option_pointers = self.option_pointers.remove(option_name);
if option_pointers.is_none() {
return Err(());
}
let option = self
.search_option(option_name)
.expect("No option found even though option pointers are there");
let config_option_free = weechat.get().config_option_free.unwrap();
unsafe { config_option_free(option.get_ptr()) }
Ok(())
}
pub fn search_option(&self, option_name: &str) -> Option<ConfigOption> {
let weechat = Weechat::from_ptr(self.weechat_ptr);
let config_search_option = weechat.get().config_search_option.unwrap();
let name = LossyCString::new(option_name);
let ptr = unsafe { config_search_option(self.config_ptr, self.ptr, name.as_ptr()) };
if ptr.is_null() {
return None;
}
let option_type = weechat.config_option_get_string(ptr, "type").unwrap();
Some(Config::option_from_type_and_ptr(
self.weechat_ptr,
ptr,
option_type.as_ref(),
))
}
pub fn new_string_option(
&mut self,
settings: StringOptionSettings,
) -> Result<StringOption, ()> {
let ret = self.new_option(
OptionDescription {
name: &settings.name,
description: &settings.description,
option_type: OptionType::String,
default_value: &settings.default_value,
value: &settings.default_value,
..Default::default()
},
settings.check_cb,
settings.change_cb,
None,
);
let (ptr, option_pointers) = if let Some((ptr, ptrs)) = ret {
(ptr, ptrs)
} else {
return Err(());
};
let option_ptrs = ConfigOptionPointers::String(option_pointers);
self.option_pointers.insert(settings.name, option_ptrs);
let option = StringOption {
ptr,
weechat_ptr: self.weechat_ptr,
_phantom: PhantomData,
};
Ok(option)
}
pub fn new_boolean_option(
&mut self,
settings: BooleanOptionSettings,
) -> Result<BooleanOption, ()> {
let value = if settings.default_value { "on" } else { "off" };
let default_value = if settings.default_value { "on" } else { "off" };
let ret = self.new_option(
OptionDescription {
name: &settings.name,
description: &settings.description,
option_type: OptionType::Boolean,
default_value,
value,
..Default::default()
},
None,
settings.change_cb,
None,
);
let (ptr, option_pointers) = if let Some((ptr, ptrs)) = ret {
(ptr, ptrs)
} else {
return Err(());
};
let option_ptrs = ConfigOptionPointers::Boolean(option_pointers);
self.option_pointers.insert(settings.name, option_ptrs);
let option = BooleanOption {
ptr,
weechat_ptr: self.weechat_ptr,
_phantom: PhantomData,
};
Ok(option)
}
pub fn new_integer_option(
&mut self,
settings: IntegerOptionSettings,
) -> Result<IntegerOption, ()> {
let ret = self.new_option(
OptionDescription {
name: &settings.name,
option_type: OptionType::Integer,
description: &settings.description,
string_values: &settings.string_values,
min: settings.min,
max: settings.max,
default_value: &settings.default_value.to_string(),
value: &settings.default_value.to_string(),
..Default::default()
},
None,
settings.change_cb,
None,
);
let (ptr, option_pointers) = if let Some((ptr, ptrs)) = ret {
(ptr, ptrs)
} else {
return Err(());
};
let option_ptrs = ConfigOptionPointers::Integer(option_pointers);
self.option_pointers.insert(settings.name, option_ptrs);
let option = IntegerOption {
ptr,
weechat_ptr: self.weechat_ptr,
_phantom: PhantomData,
};
Ok(option)
}
pub fn new_color_option(&mut self, settings: ColorOptionSettings) -> Result<ColorOption, ()> {
let ret = self.new_option(
OptionDescription {
name: &settings.name,
description: &settings.description,
option_type: OptionType::Color,
default_value: &settings.default_value,
value: &settings.default_value,
..Default::default()
},
None,
settings.change_cb,
None,
);
let (ptr, option_pointers) = if let Some((ptr, ptrs)) = ret {
(ptr, ptrs)
} else {
return Err(());
};
let option_ptrs = ConfigOptionPointers::Color(option_pointers);
self.option_pointers.insert(settings.name, option_ptrs);
let option = ColorOption {
ptr,
weechat_ptr: self.weechat_ptr,
_phantom: PhantomData,
};
Ok(option)
}
fn new_option<T>(
&self,
option_description: OptionDescription,
check_cb: Option<Box<CheckCB<T>>>,
change_cb: Option<Box<dyn FnMut(&Weechat, &T)>>,
delete_cb: Option<Box<dyn FnMut(&Weechat, &T)>>,
) -> Option<(*mut t_config_option, *const c_void)>
where
T: ConfigOptions,
{
unsafe extern "C" fn c_check_cb<T>(
pointer: *const c_void,
_data: *mut c_void,
option_pointer: *mut t_config_option,
value: *const c_char,
) -> c_int
where
T: ConfigOptions,
{
let value = CStr::from_ptr(value).to_string_lossy();
let pointers: &mut OptionPointers<T> = { &mut *(pointer as *mut OptionPointers<T>) };
let weechat = Weechat::from_ptr(pointers.weechat_ptr);
let option = T::from_ptrs(option_pointer, pointers.weechat_ptr);
let ret = if let Some(callback) = &mut pointers.check_cb {
callback(&weechat, &option, value)
} else {
true
};
ret as i32
}
unsafe extern "C" fn c_change_cb<T>(
pointer: *const c_void,
_data: *mut c_void,
option_pointer: *mut t_config_option,
) where
T: ConfigOptions,
{
let pointers: &mut OptionPointers<T> = { &mut *(pointer as *mut OptionPointers<T>) };
let weechat = Weechat::from_ptr(pointers.weechat_ptr);
let option = T::from_ptrs(option_pointer, pointers.weechat_ptr);
if let Some(callback) = &mut pointers.change_cb {
callback(&weechat, &option)
};
}
unsafe extern "C" fn c_delete_cb<T>(
pointer: *const c_void,
_data: *mut c_void,
option_pointer: *mut t_config_option,
) where
T: ConfigOptions,
{
let pointers: &mut OptionPointers<T> = { &mut *(pointer as *mut OptionPointers<T>) };
let weechat = Weechat::from_ptr(pointers.weechat_ptr);
let option = T::from_ptrs(option_pointer, pointers.weechat_ptr);
if let Some(callback) = &mut pointers.delete_cb {
callback(&weechat, &option)
};
}
let weechat = Weechat::from_ptr(self.weechat_ptr);
let name = LossyCString::new(option_description.name);
let description = LossyCString::new(option_description.description);
let option_type = LossyCString::new(option_description.option_type.as_str());
let string_values = LossyCString::new(option_description.string_values);
let default_value = LossyCString::new(option_description.default_value);
let value = LossyCString::new(option_description.value);
let c_check_cb = match check_cb {
Some(_) => Some(c_check_cb::<T> as WeechatOptCheckCbT),
None => None,
};
let c_change_cb: Option<WeechatOptChangeCbT> = match change_cb {
Some(_) => Some(c_change_cb::<T>),
None => None,
};
let c_delete_cb: Option<WeechatOptChangeCbT> = match delete_cb {
Some(_) => Some(c_delete_cb::<T>),
None => None,
};
let option_pointers = Box::new(OptionPointers {
weechat_ptr: self.weechat_ptr,
check_cb,
change_cb,
delete_cb,
});
let option_pointers_ref: &OptionPointers<T> = Box::leak(option_pointers);
let config_new_option = weechat.get().config_new_option.unwrap();
let ptr = unsafe {
config_new_option(
self.config_ptr,
self.ptr,
name.as_ptr(),
option_type.as_ptr(),
description.as_ptr(),
string_values.as_ptr(),
option_description.min,
option_description.max,
default_value.as_ptr(),
value.as_ptr(),
option_description.null_allowed as i32,
c_check_cb,
option_pointers_ref as *const _ as *const c_void,
ptr::null_mut(),
c_change_cb,
option_pointers_ref as *const _ as *const c_void,
ptr::null_mut(),
c_delete_cb,
option_pointers_ref as *const _ as *const c_void,
ptr::null_mut(),
)
};
if ptr.is_null() {
None
} else {
Some((ptr, option_pointers_ref as *const _ as *const c_void))
}
}
}