From 22ee2725943bf9b0b53e2501f2d80e31b1f486d4 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Pierre=20R=C3=A9veillon?= <pierre@citronmauve.com>
Date: Mon, 22 Nov 2021 17:37:39 +0100
Subject: [PATCH] macro to create linked list handling structure

---
 Cargo.toml   |   1 +
 src/core.rs  | 123 +++++++++----------------------
 src/tools.rs | 201 +++++++++++++++++++++++++++++++++++++++++++++++++++
 3 files changed, 235 insertions(+), 90 deletions(-)

diff --git a/Cargo.toml b/Cargo.toml
index b53bcfa..31a3994 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -20,6 +20,7 @@ crate-type = ["rlib"]
 [dependencies]
 libc = "0.2"
 libvlc-sys = "0.2"
+paste = "1.0"
 
 [target.'cfg(target_os = "windows")'.build-dependencies]
 vswhom = "0.1.0"
\ No newline at end of file
diff --git a/src/core.rs b/src/core.rs
index 93c8c9b..c5afa28 100644
--- a/src/core.rs
+++ b/src/core.rs
@@ -3,14 +3,13 @@
 // Licensed under the MIT license, see the LICENSE file.
 
 use crate::enums::*;
-use crate::tools::{from_cstr, from_cstr_ref, to_cstr};
+use crate::tools::{from_cstr, from_cstr_ref, linked_list_iter, to_cstr};
 use libc::{c_char, c_int, c_void};
 use std::borrow::Cow;
 use std::convert::TryInto;
 use std::ffi::CString;
 use std::fmt;
 use std::i32;
-use std::marker::PhantomData;
 use std::ptr;
 use vlc_sys as sys;
 
@@ -25,6 +24,36 @@ impl fmt::Display for InternalError {
 
 impl std::error::Error for InternalError {}
 
+
+linked_list_iter!(
+    libvlc_module_description, // C raw type
+    module_description, // Rust base struct name
+    {
+      name: (String, str),
+      shortname: (String, str),
+      longname: (String, str),
+      help: (String, str),
+    } // fields
+);
+
+linked_list_iter!(
+    libvlc_audio_output, // C raw type
+    audio_output, // Rust base struct name
+    {
+        name: (String, str),
+        description: (String, str),
+    } // fields
+);
+
+linked_list_iter!(
+    libvlc_audio_output_device, // C raw type
+    audio_output_device, // Rust base struct name
+    {
+        device: (String, str),
+        description: (String, str),
+    } // fields
+);
+
 /// Retrieve libvlc version.
 pub fn version() -> String {
     unsafe {
@@ -131,7 +160,7 @@ impl Instance {
             if p.is_null() {
                 None
             } else {
-                Some(ModuleDescriptionList { ptr: p })
+                Some(ModuleDescriptionList::new(p))
             }
         }
     }
@@ -143,7 +172,7 @@ impl Instance {
             if p.is_null() {
                 None
             } else {
-                Some(ModuleDescriptionList { ptr: p })
+                Some(ModuleDescriptionList::new(p))
             }
         }
     }
@@ -203,92 +232,6 @@ unsafe extern "C" fn logging_cb(
     );
 }
 
-/// List of module description.
-pub struct ModuleDescriptionList {
-    ptr: *mut sys::libvlc_module_description_t,
-}
-
-impl ModuleDescriptionList {
-    /// Returns raw pointer
-    pub fn raw(&self) -> *mut sys::libvlc_module_description_t {
-        self.ptr
-    }
-}
-
-impl Drop for ModuleDescriptionList {
-    fn drop(&mut self) {
-        unsafe { sys::libvlc_module_description_list_release(self.ptr) };
-    }
-}
-
-impl<'a> IntoIterator for &'a ModuleDescriptionList {
-    type Item = ModuleDescriptionRef<'a>;
-    type IntoIter = ModuleDescriptionListIter<'a>;
-
-    fn into_iter(self) -> Self::IntoIter {
-        ModuleDescriptionListIter {
-            ptr: self.ptr,
-            _phantomdata: PhantomData,
-        }
-    }
-}
-
-pub struct ModuleDescriptionListIter<'a> {
-    ptr: *mut sys::libvlc_module_description_t,
-    _phantomdata: PhantomData<&'a sys::libvlc_module_description_t>,
-}
-
-/// Description of a module.
-/// The strings are owned.
-#[derive(Clone, PartialEq, Eq, Hash, Debug)]
-pub struct ModuleDescription {
-    pub name: Option<String>,
-    pub shortname: Option<String>,
-    pub longname: Option<String>,
-    pub help: Option<String>,
-}
-
-/// Description of a module.
-#[derive(Clone, PartialEq, Eq, Hash, Debug)]
-pub struct ModuleDescriptionRef<'a> {
-    pub name: Option<Cow<'a, str>>,
-    pub shortname: Option<Cow<'a, str>>,
-    pub longname: Option<Cow<'a, str>>,
-    pub help: Option<Cow<'a, str>>,
-}
-
-impl<'a> Iterator for ModuleDescriptionListIter<'a> {
-    type Item = ModuleDescriptionRef<'a>;
-
-    fn next(&mut self) -> Option<Self::Item> {
-        unsafe {
-            if self.ptr.is_null() {
-                return None;
-            }
-            let p = self.ptr;
-            self.ptr = (*p).p_next;
-            Some(ModuleDescriptionRef {
-                name: from_cstr_ref((*p).psz_name),
-                shortname: from_cstr_ref((*p).psz_shortname),
-                longname: from_cstr_ref((*p).psz_longname),
-                help: from_cstr_ref((*p).psz_help),
-            })
-        }
-    }
-}
-
-impl<'a> ModuleDescriptionRef<'a> {
-    /// Convert to owned strings.
-    pub fn into_owned(&'a self) -> ModuleDescription {
-        ModuleDescription {
-            name: self.name.as_ref().map(|s| s.clone().into_owned()),
-            shortname: self.shortname.as_ref().map(|s| s.clone().into_owned()),
-            longname: self.name.as_ref().map(|s| s.clone().into_owned()),
-            help: self.shortname.as_ref().map(|s| s.clone().into_owned()),
-        }
-    }
-}
-
 pub fn errmsg() -> Option<String> {
     unsafe { from_cstr(sys::libvlc_errmsg()) }
 }
diff --git a/src/tools.rs b/src/tools.rs
index abc6ea6..82ea5da 100644
--- a/src/tools.rs
+++ b/src/tools.rs
@@ -41,3 +41,204 @@ pub fn path_to_cstr(path: &Path) -> Result<CString, NulError> {
 
     Ok(path)
 }
+
+/// Defines a module with all necessary structures to easily handle a C linked list
+macro_rules! linked_list_iter {
+    (
+      $c_type:ident,
+      $name:ident,
+      {
+        $($(#[$field_meta:meta])*
+        $field_vis:vis $field_name:ident: ($field_type:ty, $field_b_type:ty)),* $(,)+
+      }
+    ) => {
+      paste::item! {
+        mod $name {
+          use std::borrow::Cow;
+          use std::marker::PhantomData;
+          use crate::tools::from_cstr_ref;
+
+          use vlc_sys::[<$c_type _t>];
+          use vlc_sys::[<$c_type _list_release>];
+  
+          #[derive(Clone, PartialEq, Eq, Hash, Debug)]
+          pub struct Item {
+            $(
+              $(#[$field_meta:meta])*
+              $field_vis $field_name : Option<$field_type>,
+            )*
+          }
+  
+          #[derive(Clone, PartialEq, Eq, Hash, Debug)]
+          pub struct ItemRef<'a> {
+            $(
+              $(#[$field_meta:meta])*
+              $field_vis $field_name : Option<Cow<'a, $field_b_type>>,
+            )*
+          }
+  
+          impl<'a> ItemRef<'a> {
+            /// Convert to owned strings.
+            pub fn into_owned(&'a self) -> Item {
+              Item {
+                $($field_name: self.$field_name.as_ref().map(|s| s.clone().into_owned()),)*
+              }
+            }
+          }
+  
+          pub struct ListIter<'a> {
+            ptr: *mut [<$c_type _t>],
+            _phantomdata: PhantomData<&'a [<$c_type _t>]>,
+          }
+  
+          impl<'a> Iterator for ListIter<'a> {
+            type Item = ItemRef<'a>;
+  
+            fn next(&mut self) -> Option<Self::Item> {
+              unsafe {
+                if self.ptr.is_null() {
+                  return None;
+                }
+                let p = self.ptr;
+                self.ptr = (*p).p_next;
+                Some(ItemRef {
+                  $($field_name: from_cstr_ref((*p).[<psz_ $field_name>]),)*
+                })
+              }
+            }
+          }
+  
+          pub struct List {
+            ptr: *mut [<$c_type _t>]
+          }
+  
+          impl List {
+            pub fn new(ptr: *mut [<$c_type _t>]) -> List {
+              Self { ptr }
+            }
+  
+            /// Returns raw pointer
+            pub fn raw(&self) -> *mut [<$c_type _t>] {
+              self.ptr
+            }
+          }
+  
+          impl Drop for List {
+            fn drop(&mut self) {
+              unsafe{ [<$c_type _list_release>](self.ptr) };
+            }
+          }
+  
+          impl<'a> IntoIterator for &'a List {
+            type Item = ItemRef<'a>;
+            type IntoIter = ListIter<'a>;
+  
+            fn into_iter(self) -> Self::IntoIter {
+              ListIter{ptr: self.ptr, _phantomdata: PhantomData}
+            }
+          }
+        }
+  
+        // backward compatibility types
+        pub type [<$name:camel>] = $name::Item;
+        pub type [<$name:camel Ref>]<'a> = $name::ItemRef<'a>;
+        pub type [<$name:camel List>] = $name::List;
+        pub type [<$name:camel ListIter>]<'a> = $name::ListIter<'a>;
+      }
+    }
+  }
+  
+  /*
+  macro_rules! linked_list_iter {
+    (
+      $namespace:path,
+      $c_type: ident,
+      $name: ident,
+      {
+        $($(#[$field_meta:meta])*
+        $field_vis:vis $field_name:ident: ($field_type:ty, $field_b_type:ty)),* $(,)+
+      }
+    ) => {
+      paste::item! {
+        use std::marker::PhantomData;
+        use crate::$namespace::[<$c_type _t>];
+        use crate::$namespace::[<$c_type _list_release>];
+  
+        #[derive(Clone, PartialEq, Eq, Hash, Debug)]
+        pub struct $name {
+          $(
+            $(#[$field_meta:meta])*
+            $field_vis $field_name : Option<$field_type>,
+          )*
+        }
+  
+        #[derive(Clone, PartialEq, Eq, Hash, Debug)]
+        pub struct [<$name Ref>]<'a> {
+          $(
+            $(#[$field_meta:meta])*
+            $field_vis $field_name : Option<Cow<'a, $field_b_type>>,
+          )*
+        }
+  
+        impl<'a> [<$name Ref>]<'a> {
+          /// Convert to owned strings.
+          pub fn into_owned(&'a self) -> $name {
+            $name {
+              $($field_name: self.$field_name.as_ref().map(|s| s.clone().into_owned()),)*
+            }
+          }
+        }
+  
+        pub struct [<$name ListIter>]<'a> {
+          ptr: *mut [<$c_type _t>],
+          _phantomdata: PhantomData<&'a [<$c_type _t>]>,
+        }
+  
+        impl<'a> Iterator for [<$name ListIter>]<'a> {
+          type Item = [<$name Ref>]<'a>;
+  
+          fn next(&mut self) -> Option<Self::Item> {
+            unsafe {
+              if self.ptr.is_null() {
+                return None;
+              }
+              let p = self.ptr;
+              self.ptr = (*p).p_next;
+              Some([<$name Ref>] {
+                $($field_name: from_cstr_ref((*p).[<psz_ $field_name>]),)*
+              })
+            }
+          }
+        }
+  
+        pub struct [<$name List>] {
+          ptr: *mut [<$c_type _t>]
+        }
+  
+        impl [<$name List>] {
+          /// Returns raw pointer
+          pub fn raw(&self) -> *mut [<$c_type _t>] {
+            self.ptr
+          }
+        }
+  
+        impl Drop for [<$name List>] {
+          fn drop(&mut self) {
+            unsafe{ [<$c_type _list_release>](self.ptr) };
+          }
+        }
+  
+        impl<'a> IntoIterator for &'a [<$name List>] {
+          type Item = [<$name Ref>]<'a>;
+          type IntoIter = [<$name ListIter>]<'a>;
+  
+          fn into_iter(self) -> Self::IntoIter {
+            [<$name ListIter>]{ptr: self.ptr, _phantomdata: PhantomData}
+          }
+        }
+      }
+    }
+  }
+  */
+  
+  pub(crate) use linked_list_iter;
\ No newline at end of file