summary refs log tree commit diff stats
path: root/src
diff options
context:
space:
mode:
authorSoniEx2 <endermoneymod@gmail.com>2022-04-26 00:06:29 -0300
committerSoniEx2 <endermoneymod@gmail.com>2022-04-26 00:06:29 -0300
commit25fde2f4231ae5c651d2e7a3a4d17868dd0d9c1a (patch)
tree42ebe008ef1f08e630aec329e539ee89f94810b8 /src
parentf9f854aa51e384358d935740bd64cf4f6d3057c5 (diff)
Add support for lists
Diffstat (limited to 'src')
-rw-r--r--src/internals.rs2
-rw-r--r--src/lib.rs61
-rw-r--r--src/list.rs413
3 files changed, 460 insertions, 16 deletions
diff --git a/src/internals.rs b/src/internals.rs
index e3b18fa..dc93aaa 100644
--- a/src/internals.rs
+++ b/src/internals.rs
@@ -118,7 +118,7 @@ pub struct Ph {
             string: *mut *const libc::c_char,
             integer: *mut libc::c_int) -> libc::c_int,
     pub hexchat_list_get: unsafe extern "C" fn(ph: *mut HexchatPlugin,
-            name: *const libc::c_char) -> *const HexchatList,
+            name: *const libc::c_char) -> *mut HexchatList,
     pub hexchat_list_free: unsafe extern "C" fn(ph: *mut HexchatPlugin,
             xlist: *const HexchatList),
     pub hexchat_list_fields: unsafe extern "C" fn(ph: *mut HexchatPlugin,
diff --git a/src/lib.rs b/src/lib.rs
index e7db70b..0852a24 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -188,22 +188,35 @@ extern crate impl_trait;
 #[doc(hidden)]
 pub extern crate libc;
 
+// private macros
+
+/// Calls a function on a PluginHandle struct.
+macro_rules! ph_call {
+    ($f:ident($ph:expr, $($args:expr),*)) => {
+        ((*$ph.data.ph).$f)($ph.plugin, $($args),*)
+    };
+    ($f:ident($ph:expr $(,)?)) => {
+        ((*$ph.data.ph).$f)($ph.plugin)
+    };
+}
+
 mod eat;
 mod extra_tests;
 mod infoid;
 mod internals;
+pub mod list;
 mod pluginfo;
 mod strip;
 mod word;
 
 pub use eat::*;
 pub use infoid::InfoId;
-pub use word::*;
 pub use strip::*;
+pub use word::*;
 
-use pluginfo::PluginInfo;
-use internals::Ph as RawPh;
 use internals::HexchatEventAttrs as RawAttrs;
+use internals::Ph as RawPh;
+use pluginfo::PluginInfo;
 
 use std::borrow::Cow;
 use std::cell::Cell;
@@ -227,18 +240,6 @@ use std::time::{SystemTime, UNIX_EPOCH, Duration};
 #[doc(hidden)]
 pub use libc::{c_char, c_int, c_void, time_t};
 
-// private macros
-
-/// Calls a function on a PluginHandle struct.
-macro_rules! ph_call {
-    ($f:ident($ph:expr, $($args:expr),*)) => {
-        ((*$ph.data.ph).$f)($ph.plugin, $($args),*)
-    };
-    ($f:ident($ph:expr $(,)?)) => {
-        ((*$ph.data.ph).$f)($ph.plugin)
-    };
-}
-
 // ****** //
 // PUBLIC //
 // ****** //
@@ -1520,6 +1521,36 @@ impl<'a, 'ph: 'a> ValidContext<'a, 'ph> {
         }
     }
 
+    /// Retrieves a list.
+    ///
+    /// # Examples
+    ///
+    /// ```no_run
+    /// use hexchat_unsafe_plugin::list::Contexts;
+    /// use hexchat_unsafe_plugin::PluginHandle;
+    /// 
+    /// fn print_contexts(ph: &mut PluginHandle<'_>) {
+    ///     ph.ensure_valid_context(|ph| {
+    ///         for context in ph.list(&Contexts) {
+    ///             write!(ph, "{}", context.name().unwrap());
+    ///         }
+    ///     })
+    /// }
+    /// ```
+    pub fn list<T: list::List>(&'a self, t: &'a T) -> list::Entries<'a, 'ph, T> {
+        let ph = &self.ph;
+        let list = CString::new(&*t.name()).unwrap();
+        let list = unsafe {
+            ph_call!(hexchat_list_get(ph, list.as_ptr()))
+        };
+        list::Entries {
+            context: self,
+            list: list,
+            t: t,
+            valid: Default::default(),
+        }
+    }
+
     // ******** //
     // FORWARDS //
     // ******** //
diff --git a/src/list.rs b/src/list.rs
new file mode 100644
index 0000000..257ef95
--- /dev/null
+++ b/src/list.rs
@@ -0,0 +1,413 @@
+// This file is part of Hexchat Plugin API Bindings for Rust
+// Copyright (C) 2022 Soni L.
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as
+// published by the Free Software Foundation, either version 3 of the
+// License, or (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program.  If not, see <https://www.gnu.org/licenses/>.
+
+//! List support module.
+
+use std::borrow::Cow;
+use std::cell::Cell;
+use std::ffi::CStr;
+use std::rc::Rc;
+
+use crate::Context;
+use crate::wrap_context;
+
+mod sealed {
+    pub trait Sealed {
+    }
+}
+
+/// A list. This trait is sealed.
+pub trait List: sealed::Sealed {
+    fn name(&self) -> Cow<'static, str>;
+}
+
+// List types
+
+/// A "channels" list.
+pub struct Contexts;
+
+/// A "dcc" list.
+pub struct Dcc;
+
+/// An "ignore" list.
+pub struct Ignore;
+
+/// A "notify" list.
+pub struct Notify;
+
+/// An "users" list.
+pub struct Users;
+
+/// Entries.
+pub struct Entries<'a, 'ph, T> {
+    pub(crate) context: &'a crate::ValidContext<'a, 'ph>,
+    pub(crate) list: *mut crate::internals::HexchatList,
+    pub(crate) t: &'a T,
+    pub(crate) valid: Rc<Cell<usize>>,
+}
+
+/// Fields.
+pub struct Fields<'a, 'ph, T> {
+    context: &'a crate::ValidContext<'a, 'ph>,
+    list: *mut crate::internals::HexchatList,
+    _t: &'a T,
+    id: usize,
+    valid: Rc<Cell<usize>>,
+}
+
+// impls
+
+impl sealed::Sealed for Contexts {}
+impl sealed::Sealed for Dcc {}
+impl sealed::Sealed for Ignore {}
+impl sealed::Sealed for Notify {}
+impl sealed::Sealed for Users {}
+
+impl List for Contexts {
+    fn name(&self) -> Cow<'static, str> { "channels".into() }
+}
+
+impl List for Dcc {
+    fn name(&self) -> Cow<'static, str> { "dcc".into() }
+}
+
+impl List for Ignore {
+    fn name(&self) -> Cow<'static, str> { "ignore".into() }
+}
+
+impl List for Notify {
+    fn name(&self) -> Cow<'static, str> { "notify".into() }
+}
+
+impl List for Users {
+    fn name(&self) -> Cow<'static, str> { "users".into() }
+}
+
+impl<'a, 'ph, T> Drop for Entries<'a, 'ph, T> {
+    fn drop(&mut self) {
+        let ph = &self.context.ph;
+        unsafe {
+            ph_call!(hexchat_list_free(ph, self.list));
+        }
+    }
+}
+
+impl<'a, 'ph, T> Iterator for Entries<'a, 'ph, T> {
+    type Item = Fields<'a, 'ph, T>;
+
+    fn next(&mut self) -> Option<Self::Item> {
+        let ph = &self.context.ph;
+        let id = self.valid.get();
+        if unsafe {
+            ph_call!(hexchat_list_next(ph, self.list))
+        } != 0 {
+            debug_assert!(self.valid.get() < usize::MAX);
+            self.valid.set(id + 1);
+            Some(Fields {
+                context: self.context,
+                list: self.list,
+                _t: self.t,
+                valid: Rc::clone(&self.valid),
+                id: id,
+            })
+        } else {
+            self.valid.set(usize::MAX);
+            None
+        }
+    }
+}
+
+macro_rules! field {
+    ($(#[$m:meta])* $f:ident, unsafe {$n:expr}, $r:ty, $ph:ident, $c:ident, $($conv:tt)+) => {
+        $(#[$m])* pub fn $f(&self) -> $r {
+            assert_eq!(
+                self.id, self.valid.get(),
+                "instances of Fields are invalidated by next()",
+            );
+            let $ph = &self.context.ph;
+            const NAME: &'static CStr = unsafe {
+                CStr::from_bytes_with_nul_unchecked(
+                    $n.as_bytes()
+                )
+            };
+            match unsafe {
+                ph_call!($c($ph, self.list, NAME.as_ptr()))
+            } {
+                $($conv)+
+            }
+        }
+    };
+    ($(#[$m:meta])* $f:ident, $r:ty, $ph:ident, $c:ident, $($conv:tt)+) => {
+        field!(
+            $(#[$m])* $f,
+            unsafe { ::std::concat!(::std::stringify!($f), "\0") },
+            $r,
+            $ph,
+            $c,
+            $($conv)+
+        );
+    };
+}
+
+macro_rules! field_int {
+    ($(#[$m:meta])* $f:ident, unsafe { $n:expr }) => {
+        field!($(#[$m])* $f, unsafe { $n }, i32, ph, hexchat_list_int, r => r as i32);
+    };
+    ($(#[$m:meta])* $f:ident) => {
+        field_int!($(#[$m])* $f, unsafe { ::std::concat!(::std::stringify!($f), "\0") });
+    };
+}
+
+macro_rules! field_bool {
+    ($(#[$m:meta])* $f:ident, unsafe { $n:expr }) => {
+        field!($(#[$m])* $f, unsafe { $n }, bool, ph, hexchat_list_int, r => r != 0);
+    };
+    ($(#[$m:meta])* $f:ident) => {
+        field_bool!($(#[$m])* $f, unsafe { ::std::concat!(::std::stringify!($f), "\0") });
+    };
+}
+
+macro_rules! field_str {
+    ($(#[$m:meta])* $f:ident, unsafe { $n:expr }) => {
+        field!(
+            $(#[$m])* $f, unsafe { $n }, Option<String>, ph, hexchat_list_str,
+            r if r.is_null() => {
+                None
+            },
+            r => Some(unsafe {
+                CStr::from_ptr(r)
+            }.to_owned().into_string().unwrap())
+        );
+    };
+    ($(#[$m:meta])* $f:ident) => {
+        field_str!($(#[$m])* $f, unsafe { ::std::concat!(::std::stringify!($f), "\0") });
+    };
+}
+
+
+macro_rules! field_time {
+    ($(#[$m:meta])* $f:ident) => {
+        $(#[$m])* pub fn $f(&self) -> ! {
+            todo!()
+        }
+    };
+}
+
+/// Contexts fields.
+impl<'a, 'ph> Fields<'a, 'ph, Contexts> {
+    field_str!(
+        /// The context's name.
+        name, unsafe { "channel" }
+    );
+    field_str!(
+        /// The channel key.
+        channelkey
+    );
+    field_str!(
+        /// The server's channel modes. Requires HexChat 2.12.2+.
+        chanmodes
+    );
+    field_str!(
+        /// The server's channel types.
+        chantypes
+    );
+    field!(
+        /// The context.
+        context, Context<'ph>, ph, hexchat_list_str, r => unsafe {
+            wrap_context(ph, r as *const _)
+        }
+    );
+    field_int!(
+        /// Raw flags about this context.
+        raw_flags, unsafe { "flags" }
+    );
+    field_int!(
+        /// The server ID.
+        server_id, unsafe { "id" }
+    );
+    field_int!(
+        /// The latency to server in milliseconds.
+        lag
+    );
+    field_int!(
+        /// The maximum number of mode changes per line accepted by the server.
+        maxmodes
+    );
+    field_str!(
+        /// The network name.
+        network
+    );
+    field_str!(
+        /// The server's nick prefixes.
+        nickprefixes
+    );
+    field_str!(
+        /// The server's nick modes.
+        nickmodes
+    );
+    field_int!(
+        /// The current length of the send-queue, in bytes.
+        queue_len, unsafe { "queue" }
+    );
+    field_str!(
+        /// The server's name.
+        server
+    );
+    field_int!(
+        /// The context's raw type.
+        raw_type, unsafe { "type" }
+    );
+    field_int!(
+        /// The number of users in the channel.
+        users
+    );
+}
+
+/// Dcc fields.
+impl<'a, 'ph> Fields<'a, 'ph, Dcc> {
+    field_int!(
+        /// The raw 32-bit IPv4 address of the remote user.
+        address32
+    );
+    field_int!(
+        /// The transfer rate (speed), in bytes per second.
+        rate, unsafe { "cps" }
+    );
+    field_str!(
+        /// The destination file path.
+        destfile
+    );
+    field_str!(
+        /// The filename.
+        file
+    );
+    field_str!(
+        /// The remote user's nick.
+        nick
+    );
+    field_int!(
+        /// The listener's port number.
+        port
+    );
+    field_int!(
+        /// File position, LSB 32 bits.
+        raw_pos, unsafe { "pos" }
+    );
+    field_int!(
+        /// File position, MSB 32 bits.
+        raw_poshigh, unsafe { "poshigh" }
+    );
+    field_int!(
+        /// Resumed position, LSB 32 bits.
+        raw_resume, unsafe { "resume" }
+    );
+    field_int!(
+        /// Resumed position, MSB 32 bits.
+        raw_resumehigh, unsafe { "resumehigh" }
+    );
+    field_int!(
+        /// File size, LSB 32 bits.
+        raw_size, unsafe { "size" }
+    );
+    field_int!(
+        /// File size, MSB 32 bits.
+        raw_sizehigh, unsafe { "sizehigh" }
+    );
+    field_int!(
+        /// Raw DCC status.
+        raw_status, unsafe { "status" }
+    );
+    field_int!(
+        /// Raw type.
+        raw_type, unsafe { "type" }
+    );
+}
+
+/// Ignore fields.
+impl<'a, 'ph> Fields<'a, 'ph, Ignore> {
+    field_str!(
+        /// The ignore mask.
+        mask
+    );
+    field_int!(
+        /// Raw ignore flags.
+        raw_flags, unsafe { "flags" }
+    );
+}
+
+/// Notify fields.
+impl<'a, 'ph> Fields<'a, 'ph, Notify> {
+    field_str!(
+        /// Comma-separated list of networks this notify entry applies to.
+        networks
+    );
+    field_str!(
+        /// The nick.
+        nick
+    );
+    field_int!(
+        /// Raw flags.
+        raw_flags, unsafe { "flags" }
+    );
+    field_time!(
+        /// Time when the user went online. [NYI]
+        on
+    );
+    field_time!(
+        /// Time when the user went offline. [NYI]
+        off
+    );
+    field_time!(
+        /// Time when the user was last seen. [NYI]
+        seen
+    );
+}
+
+/// Users fields.
+impl<'a, 'ph> Fields<'a, 'ph, Users> {
+    field_str!(
+        /// Account name.
+        account
+    );
+    field_bool!(
+        /// Whether the user is away.
+        away
+    );
+    field_time!(
+        /// Time when the user last talked. [NYI]
+        lasttalk
+    );
+    field_str!(
+        /// User's nick.
+        nick
+    );
+    field_str!(
+        /// User's user and hostname (`user@host`).
+        host
+    );
+    field_str!(
+        /// User's prefix.
+        prefix
+    );
+    field_str!(
+        /// User's userdata.
+        userdata, unsafe { "realname" }
+    );
+    field_bool!(
+        /// Whether the user is selected in the UI.
+        selected
+    );
+}