use std::{
borrow::Cow,
cell::Cell,
collections::{
hash_map::{IntoIter as IterHashmap, Keys},
HashMap,
},
ffi::CStr,
fmt::Debug,
marker::PhantomData,
ptr,
rc::Rc,
time::{Duration, SystemTime},
};
use weechat_sys::{t_gui_buffer, t_infolist, t_weechat_plugin};
use crate::{
buffer::{Buffer, InnerBuffer, InnerBuffers},
LossyCString, Weechat,
};
pub struct Infolist<'a> {
ptr: *mut t_infolist,
infolist_name: String,
weechat_ptr: *mut t_weechat_plugin,
phantom_weechat: PhantomData<&'a Weechat>,
}
#[derive(Eq, Hash, Debug, PartialEq, Clone)]
#[allow(missing_docs)]
pub enum InfolistType {
Integer,
String,
Time,
Buffer,
}
impl From<&str> for InfolistType {
fn from(value: &str) -> Self {
match value {
"i" => InfolistType::Integer,
"s" => InfolistType::String,
"t" => InfolistType::Time,
"p" => InfolistType::Buffer,
v => panic!("Got unexpected value {}", v),
}
}
}
pub struct InfolistItem<'a> {
ptr: *mut t_infolist,
weechat_ptr: *mut t_weechat_plugin,
fields: HashMap<String, InfolistType>,
infolist: PhantomData<&'a Infolist<'a>>,
}
impl<'a> Debug for InfolistItem<'a> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_map().entries(self.fields.iter()).finish()
}
}
impl<'a> InfolistItem<'a> {
fn integer(&self, name: &str) -> i32 {
let weechat = Weechat::from_ptr(self.weechat_ptr);
let name = LossyCString::new(name);
let infolist_integer = weechat.get().infolist_integer.unwrap();
unsafe { infolist_integer(self.ptr, name.as_ptr()) }
}
fn string(&'a self, name: &str) -> Option<Cow<str>> {
let weechat = Weechat::from_ptr(self.weechat_ptr);
let name = LossyCString::new(name);
let infolist_string = weechat.get().infolist_string.unwrap();
unsafe {
let ptr = infolist_string(self.ptr, name.as_ptr());
if ptr.is_null() {
None
} else {
Some(CStr::from_ptr(ptr).to_string_lossy())
}
}
}
fn buffer(&self, name: &str) -> Option<Buffer> {
let weechat = Weechat::from_ptr(self.weechat_ptr);
let name = LossyCString::new(name);
let infolist_pointer = weechat.get().infolist_pointer.unwrap();
let ptr = unsafe { infolist_pointer(self.ptr, name.as_ptr()) as *mut t_gui_buffer };
if ptr.is_null() {
return None;
}
Some(Buffer {
inner: InnerBuffers::BorrowedBuffer(InnerBuffer {
weechat: self.weechat_ptr,
ptr,
weechat_phantom: PhantomData,
closing: Rc::new(Cell::new(false)),
}),
})
}
fn time(&self, name: &str) -> Option<SystemTime> {
let weechat = Weechat::from_ptr(self.weechat_ptr);
let name = LossyCString::new(name);
let infolist_time = weechat.get().infolist_time.unwrap();
let time = unsafe { infolist_time(self.ptr, name.as_ptr()) };
let unix = SystemTime::UNIX_EPOCH;
let duration = Duration::from_secs(time as u64);
unix.checked_add(duration)
}
pub fn get(&self, key: &str) -> Option<InfolistVariable> {
let infolist_type = self.fields.get(key)?;
let variable = match infolist_type {
InfolistType::Integer => InfolistVariable::Integer(self.integer(key)),
InfolistType::String => InfolistVariable::String(self.string(key)?),
InfolistType::Time => InfolistVariable::Time(self.time(key)?),
InfolistType::Buffer => InfolistVariable::Buffer(self.buffer(key)?),
};
Some(variable)
}
pub fn keys(&self) -> Keys<'_, String, InfolistType> {
self.fields.keys()
}
pub fn iter(&'a self) -> Iter<'a> {
Iter {
keys: self.fields.clone().into_iter(),
item: &self,
}
}
}
pub struct Iter<'a> {
item: &'a InfolistItem<'a>,
keys: IterHashmap<String, InfolistType>,
}
impl<'a> Iterator for Iter<'a> {
type Item = (String, InfolistVariable<'a>);
fn next(&mut self) -> Option<Self::Item> {
loop {
let (name, _) = self.keys.next()?;
let variable = self.item.get(&name);
if let Some(variable) = variable {
return Some((name, variable));
}
}
}
}
impl<'a> IntoIterator for &'a InfolistItem<'a> {
type Item = (String, InfolistVariable<'a>);
type IntoIter = Iter<'a>;
fn into_iter(self) -> Self::IntoIter {
self.iter()
}
}
#[derive(Debug, PartialEq, PartialOrd, Eq, Ord)]
pub enum InfolistVariable<'a> {
Integer(i32),
String(Cow<'a, str>),
Time(SystemTime),
Buffer(Buffer<'a>),
}
impl<'a> Infolist<'a> {
fn is_pointer_buffer(infolist_name: &str, variable_name: &str) -> bool {
match (infolist_name, variable_name) {
("logger_buffer", "buffer") => true,
("buffer", "pointer") => true,
("buflist", "buffer") => true,
("irc_server", "buffer") => true,
("hotlist", "buffer_pointer") => true,
("window", "buffer") => true,
_ => false,
}
}
fn get_fields(&self) -> HashMap<String, InfolistType> {
let weechat = Weechat::from_ptr(self.weechat_ptr);
let infolist_fields = weechat.get().infolist_fields.unwrap();
let mut fields: HashMap<String, InfolistType> = HashMap::new();
let fields_string = unsafe {
let ptr = infolist_fields(self.ptr);
CStr::from_ptr(ptr).to_string_lossy()
};
for field in fields_string.split(',') {
let split: Vec<&str> = field.split(':').collect();
let infolist_type = split[0];
let name = split[1];
if infolist_type == "b" {
continue;
}
let field = if infolist_type == "p" {
if Infolist::is_pointer_buffer(&self.infolist_name, name) {
InfolistType::Buffer
} else {
continue;
}
} else {
InfolistType::from(infolist_type)
};
fields.insert(name.to_owned(), field);
}
fields
}
}
impl<'a> Drop for Infolist<'a> {
fn drop(&mut self) {
let weechat = Weechat::from_ptr(self.weechat_ptr);
let infolist_free = weechat.get().infolist_free.unwrap();
unsafe { infolist_free(self.ptr) }
}
}
impl Weechat {
pub fn get_infolist(
&self,
infolist_name: &str,
arguments: Option<&str>,
) -> Result<Infolist, ()> {
let infolist_get = self.get().infolist_get.unwrap();
let name = LossyCString::new(infolist_name);
let arguments = if let Some(args) = arguments {
Some(LossyCString::new(args))
} else {
None
};
let infolist_ptr = unsafe {
infolist_get(
self.ptr,
name.as_ptr(),
ptr::null_mut(),
arguments.map_or(ptr::null_mut(), |a| a.as_ptr()),
)
};
if infolist_ptr.is_null() {
Err(())
} else {
Ok(Infolist {
ptr: infolist_ptr,
infolist_name: infolist_name.to_owned(),
weechat_ptr: self.ptr,
phantom_weechat: PhantomData,
})
}
}
}
impl<'a> Iterator for Infolist<'a> {
type Item = InfolistItem<'a>;
fn next(&mut self) -> Option<InfolistItem<'a>> {
let weechat = Weechat::from_ptr(self.weechat_ptr);
let infolist_next = weechat.get().infolist_next.unwrap();
let ret = unsafe { infolist_next(self.ptr) };
if ret == 1 {
let fields = self.get_fields();
Some(InfolistItem {
ptr: self.ptr,
weechat_ptr: self.weechat_ptr,
fields,
infolist: PhantomData,
})
} else {
None
}
}
}