allow loading multiple plugins with the same name, namespace plugins
1 files changed, 66 insertions(+), 30 deletions(-)

M src/lib.rs
M src/lib.rs +66 -30
@@ 13,21 13,39 @@ macro_rules! lib_name {
 #[macro_export]
 macro_rules! plugin_registry {
 	($( fn $name:ident($( $param_name:ident: $param_ty:ty ),*) -> $ty:ty; )*) => {
-		pub struct Registry {
-			libs: ::std::collections::HashMap<String, $crate::libloading::Library>,
+		#[derive(Default)]
+		pub struct Registry(Plugins);
+
+		#[derive(Default)]
+		pub struct Plugins {
+			_libs: ::std::collections::HashMap<String, $crate::libloading::Library>,
+
+			$( $name: Vec<String> ),*
+		}
 
-			$( $name: Option<String> ),*
+		impl Plugins {
+			$(
+				pub fn $name(&self) -> impl
+					ExactSizeIterator<Item=impl '_ + Fn($( $param_ty ),*) -> $ty>
+					/* XXX maybe add these in a future rust version when caller can use them
+					 * (`let x: impl Trait`?)
+					DoubleEndedIterator<Item=impl '_ + Fn($( $param_ty ),*) -> $ty> +
+					::std::iter::FusedIterator<Item=impl '_ + Fn($( $param_ty ),*) -> $ty> +
+					Send + Sync + Clone + ::std::fmt::Debug
+					 */
+				{
+					self.$name.iter().map(move |name| {
+						let symbol = unsafe {
+							self._libs[name].get::<fn($( $param_ty ),*) -> $ty>(stringify!($name).as_bytes())
+						}.expect(concat!("symbol not found: ", stringify!($name)));
+
+						move |$( $param_name: $param_ty ),*| symbol($( $param_name ),*)
+					})
+				}
+			)*
 		}
 
 		impl Registry {
-			pub fn new() -> Self {
-				Self {
-					libs: Default::default(),
-
-					$( $name: None ),*
-				}
-			}
-
 			pub fn load<'a>(&mut self, lib: &str) -> ::std::io::Result<()> {
 				let lib = $crate::libloading::Library::new(lib)?;
 

          
@@ 36,34 54,23 @@ macro_rules! plugin_registry {
 				// probe for plugins
 				$(
 					if let Ok(_) = unsafe { lib.get::<fn($( $param_ty ),*) -> $ty>(stringify!($name).as_bytes()) } {
-						self.$name = Some(name.clone());
+						(self.0).$name.push(name.clone());
 					}
 				)*
 
-				self.libs.insert(name, lib);
+				self.0._libs.insert(name, lib);
 				Ok(())
 			}
 
-			pub fn unload(&mut self, lib: &str) {
-				let set_none_if_equal = |plugin: &mut Option<String>|
-					if plugin.is_some() && plugin.as_ref().unwrap() == lib {
-						*plugin = None;
-					}
-				;
-				$( set_none_if_equal(&mut self.$name); )*
+			pub fn unload(&mut self, name: &str) {
+				$( (self.0).$name.retain(|lib| lib != name); )*
 
-				self.libs.remove(lib);
+				self.0._libs.remove(name);
 			}
 
-			$(
-				pub fn $name(&self, $( $param_name: $param_ty ),*) -> Option<$ty> {
-					self.$name.as_ref().map(|name| unsafe {
-						self.libs[name].get::<fn($( $param_ty ),*) -> $ty>(stringify!($name).as_bytes())
-							.expect(concat!("symbol not found: ", stringify!($name)))
-							($( $param_name ),*)
-					})
-				}
-			)*
+			pub fn plugins(&self) -> &Plugins {
+				&self.0
+			}
 		}
 
 		#[macro_export]

          
@@ 79,3 86,32 @@ macro_rules! plugin_registry {
 		}
 	}
 }
+
+#[cfg(test)]
+mod test {
+	use super::*;
+
+	#[test]
+	fn types() {
+		plugin_registry!(
+			fn is_empty(x: &str) -> bool;
+		);
+
+		lib_name!("dyplugin");
+
+		plugin!(is_empty: plugin);
+
+		fn plugin(x: &str) -> bool {
+			x.is_empty()
+		}
+
+		let reg = Registry::default();
+		assert_eq!(reg.plugins().is_empty().len(), 0);
+
+		let is_empty = reg.plugins().is_empty().last();
+		match is_empty {
+			Some(is_empty) => assert_eq!(is_empty(""), true),
+			None => ()
+		}
+	}
+}