dyplugin 0.2.0

type-checked plugins in dynamic libraries
Documentation
pub use libloading;

#[macro_export]
macro_rules! lib_name {
	($name:expr) => {
		#[no_mangle]
		pub fn name() -> &'static str {
			$name
		}
	}
}

#[macro_export]
macro_rules! plugin_registry {
	($( fn $name:ident($( $param_name:ident: $param_ty:ty ),*) -> $ty:ty; )*) => {
		#[derive(Default)]
		pub struct Registry(Plugins);

		#[derive(Default)]
		pub struct Plugins {
			_libs: ::std::collections::HashMap<String, $crate::libloading::Library>,

			$( $name: Vec<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 load<'a>(&mut self, lib: &str) -> ::std::io::Result<()> {
				let lib = $crate::libloading::Library::new(lib)?;

				let name = unsafe { lib.get::<fn() -> &'a str>(b"name")?() }.to_owned();

				// probe for plugins
				$(
					if let Ok(_) = unsafe { lib.get::<fn($( $param_ty ),*) -> $ty>(stringify!($name).as_bytes()) } {
						(self.0).$name.push(name.clone());
					}
				)*

				self.0._libs.insert(name, lib);
				Ok(())
			}

			pub fn unload(&mut self, name: &str) {
				$( (self.0).$name.retain(|lib| lib != name); )*

				self.0._libs.remove(name);
			}

			pub fn plugins(&self) -> &Plugins {
				&self.0
			}
		}

		#[macro_export]
		macro_rules! plugin {
			$(
				($name: $func:path) => {
					#[no_mangle]
					pub fn $name($( $param_name: $param_ty ),*) -> $ty {
						$func($( $param_name ),*)
					}
				}
			);*
		}
	}
}

#[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 => ()
		}
	}
}