refactor: Design plugin system

This commit is contained in:
Ed Page 2023-04-13 01:18:44 -05:00
parent fe70ddcb20
commit 1924f45289
2 changed files with 218 additions and 0 deletions

View file

@ -0,0 +1,217 @@
use crate::util::AnyValueId;
use crate::util::FlatMap;
#[allow(dead_code)]
#[derive(Default, Clone, Debug)]
pub(crate) struct Extensions {
extensions: FlatMap<AnyValueId, BoxedExtension>,
}
impl Extensions {
#[allow(dead_code)]
pub(crate) fn get<T: Extension>(&self) -> Option<&T> {
let id = AnyValueId::of::<T>();
self.extensions.get(&id).map(|e| e.as_ref::<T>())
}
#[allow(dead_code)]
pub(crate) fn get_mut<T: Extension>(&mut self) -> Option<&mut T> {
let id = AnyValueId::of::<T>();
self.extensions.get_mut(&id).map(|e| e.as_mut::<T>())
}
#[allow(dead_code)]
pub(crate) fn get_or_insert_default<T: Extension + Default>(&mut self) -> &mut T {
let id = AnyValueId::of::<T>();
self.extensions
.entry(id)
.or_insert_with(|| BoxedExtension::new(T::default()))
.as_mut::<T>()
}
#[allow(dead_code)]
pub(crate) fn set<T: Extension + Into<BoxedEntry>>(&mut self, tagged: T) -> bool {
let BoxedEntry { id, value } = tagged.into();
self.extensions.insert(id, value).is_some()
}
#[allow(dead_code)]
pub(crate) fn remove<T: Extension>(&mut self) -> Option<Box<dyn Extension>> {
let id = AnyValueId::of::<T>();
self.extensions.remove(&id).map(BoxedExtension::into_inner)
}
pub(crate) fn update(&mut self, other: &Self) {
for (key, value) in other.extensions.iter() {
self.extensions.insert(*key, value.clone());
}
}
}
/// Supports conversion to `Any`. Traits to be extended by `impl_downcast!` must extend `Extension`.
pub(crate) trait Extension: std::fmt::Debug + Send + Sync + 'static {
/// Convert `Box<dyn Trait>` (where `Trait: Extension`) to `Box<dyn Any>`.
///
/// `Box<dyn Any>` can /// then be further `downcast` into
/// `Box<ConcreteType>` where `ConcreteType` implements `Trait`.
fn into_any(self: Box<Self>) -> Box<dyn std::any::Any>;
/// Clone `&Box<dyn Trait>` (where `Trait: Extension`) to `Box<dyn Extension>`.
///
/// `Box<dyn Any>` can /// then be further `downcast` into
// `Box<ConcreteType>` where `ConcreteType` implements `Trait`.
fn clone_extension(&self) -> Box<dyn Extension>;
/// Convert `&Trait` (where `Trait: Extension`) to `&Any`.
///
/// This is needed since Rust cannot /// generate `&Any`'s vtable from
/// `&Trait`'s.
fn as_any(&self) -> &dyn std::any::Any;
/// Convert `&mut Trait` (where `Trait: Extension`) to `&Any`.
///
/// This is needed since Rust cannot /// generate `&mut Any`'s vtable from
/// `&mut Trait`'s.
fn as_any_mut(&mut self) -> &mut dyn std::any::Any;
}
impl<T> Extension for T
where
T: Clone + std::fmt::Debug + Send + Sync + 'static,
{
fn into_any(self: Box<Self>) -> Box<dyn std::any::Any> {
self
}
fn clone_extension(&self) -> Box<dyn Extension> {
Box::new(self.clone())
}
fn as_any(&self) -> &dyn std::any::Any {
self
}
fn as_any_mut(&mut self) -> &mut dyn std::any::Any {
self
}
}
impl Clone for Box<dyn Extension> {
fn clone(&self) -> Self {
self.as_ref().clone_extension()
}
}
#[derive(Clone)]
#[repr(transparent)]
struct BoxedExtension(Box<dyn Extension>);
impl BoxedExtension {
fn new<T: Extension>(inner: T) -> Self {
Self(Box::new(inner))
}
fn into_inner(self) -> Box<dyn Extension> {
self.0
}
fn as_ref<T: Extension>(&self) -> &T {
self.0.as_ref().as_any().downcast_ref::<T>().unwrap()
}
fn as_mut<T: Extension>(&mut self) -> &mut T {
self.0.as_mut().as_any_mut().downcast_mut::<T>().unwrap()
}
}
impl std::fmt::Debug for BoxedExtension {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> {
self.0.fmt(f)
}
}
#[derive(Clone)]
pub(crate) struct BoxedEntry {
id: AnyValueId,
value: BoxedExtension,
}
impl BoxedEntry {
pub(crate) fn new(r: impl Extension) -> Self {
let id = AnyValueId::from(&r);
let value = BoxedExtension::new(r);
BoxedEntry { id, value }
}
}
impl<R: Extension> From<R> for BoxedEntry {
fn from(inner: R) -> Self {
BoxedEntry::new(inner)
}
}
#[cfg(test)]
mod test {
use super::*;
#[derive(Default, Copy, Clone, Debug, PartialEq, Eq)]
struct Number(usize);
#[test]
fn get() {
let mut ext = Extensions::default();
ext.set(Number(10));
assert_eq!(ext.get::<Number>(), Some(&Number(10)));
}
#[test]
fn get_mut() {
let mut ext = Extensions::default();
ext.set(Number(10));
*ext.get_mut::<Number>().unwrap() = Number(20);
assert_eq!(ext.get::<Number>(), Some(&Number(20)));
}
#[test]
fn get_or_insert_default_empty() {
let mut ext = Extensions::default();
assert_eq!(ext.get_or_insert_default::<Number>(), &Number(0));
}
#[test]
fn get_or_insert_default_full() {
let mut ext = Extensions::default();
ext.set(Number(10));
assert_eq!(ext.get_or_insert_default::<Number>(), &Number(10));
}
#[test]
fn set() {
let mut ext = Extensions::default();
assert!(!ext.set(Number(10)));
assert_eq!(ext.get::<Number>(), Some(&Number(10)));
assert!(ext.set(Number(20)));
assert_eq!(ext.get::<Number>(), Some(&Number(20)));
}
#[test]
fn reset() {
let mut ext = Extensions::default();
assert_eq!(ext.get::<Number>(), None);
assert!(ext.remove::<Number>().is_none());
assert_eq!(ext.get::<Number>(), None);
assert!(!ext.set(Number(10)));
assert_eq!(ext.get::<Number>(), Some(&Number(10)));
assert!(ext.remove::<Number>().is_some());
assert_eq!(ext.get::<Number>(), None);
}
#[test]
fn update() {
let mut ext = Extensions::default();
assert_eq!(ext.get::<Number>(), None);
let mut new = Extensions::default();
assert!(!new.set(Number(10)));
ext.update(&new);
assert_eq!(ext.get::<Number>(), Some(&Number(10)));
}
}

View file

@ -7,6 +7,7 @@ mod arg_group;
mod arg_predicate;
mod arg_settings;
mod command;
mod ext;
mod os_str;
mod possible_value;
mod range;