diff --git a/clap_builder/src/builder/ext.rs b/clap_builder/src/builder/ext.rs new file mode 100644 index 00000000..66354fb2 --- /dev/null +++ b/clap_builder/src/builder/ext.rs @@ -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, +} + +impl Extensions { + #[allow(dead_code)] + pub(crate) fn get(&self) -> Option<&T> { + let id = AnyValueId::of::(); + self.extensions.get(&id).map(|e| e.as_ref::()) + } + + #[allow(dead_code)] + pub(crate) fn get_mut(&mut self) -> Option<&mut T> { + let id = AnyValueId::of::(); + self.extensions.get_mut(&id).map(|e| e.as_mut::()) + } + + #[allow(dead_code)] + pub(crate) fn get_or_insert_default(&mut self) -> &mut T { + let id = AnyValueId::of::(); + self.extensions + .entry(id) + .or_insert_with(|| BoxedExtension::new(T::default())) + .as_mut::() + } + + #[allow(dead_code)] + pub(crate) fn set>(&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(&mut self) -> Option> { + let id = AnyValueId::of::(); + 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` (where `Trait: Extension`) to `Box`. + /// + /// `Box` can /// then be further `downcast` into + /// `Box` where `ConcreteType` implements `Trait`. + fn into_any(self: Box) -> Box; + /// Clone `&Box` (where `Trait: Extension`) to `Box`. + /// + /// `Box` can /// then be further `downcast` into + // `Box` where `ConcreteType` implements `Trait`. + fn clone_extension(&self) -> Box; + /// 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 Extension for T +where + T: Clone + std::fmt::Debug + Send + Sync + 'static, +{ + fn into_any(self: Box) -> Box { + self + } + fn clone_extension(&self) -> Box { + 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 { + fn clone(&self) -> Self { + self.as_ref().clone_extension() + } +} + +#[derive(Clone)] +#[repr(transparent)] +struct BoxedExtension(Box); + +impl BoxedExtension { + fn new(inner: T) -> Self { + Self(Box::new(inner)) + } + + fn into_inner(self) -> Box { + self.0 + } + + fn as_ref(&self) -> &T { + self.0.as_ref().as_any().downcast_ref::().unwrap() + } + + fn as_mut(&mut self) -> &mut T { + self.0.as_mut().as_any_mut().downcast_mut::().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 From 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::(), Some(&Number(10))); + } + + #[test] + fn get_mut() { + let mut ext = Extensions::default(); + ext.set(Number(10)); + *ext.get_mut::().unwrap() = Number(20); + assert_eq!(ext.get::(), Some(&Number(20))); + } + + #[test] + fn get_or_insert_default_empty() { + let mut ext = Extensions::default(); + assert_eq!(ext.get_or_insert_default::(), &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(10)); + } + + #[test] + fn set() { + let mut ext = Extensions::default(); + assert!(!ext.set(Number(10))); + assert_eq!(ext.get::(), Some(&Number(10))); + assert!(ext.set(Number(20))); + assert_eq!(ext.get::(), Some(&Number(20))); + } + + #[test] + fn reset() { + let mut ext = Extensions::default(); + assert_eq!(ext.get::(), None); + + assert!(ext.remove::().is_none()); + assert_eq!(ext.get::(), None); + + assert!(!ext.set(Number(10))); + assert_eq!(ext.get::(), Some(&Number(10))); + + assert!(ext.remove::().is_some()); + assert_eq!(ext.get::(), None); + } + + #[test] + fn update() { + let mut ext = Extensions::default(); + assert_eq!(ext.get::(), None); + + let mut new = Extensions::default(); + assert!(!new.set(Number(10))); + + ext.update(&new); + assert_eq!(ext.get::(), Some(&Number(10))); + } +} diff --git a/clap_builder/src/builder/mod.rs b/clap_builder/src/builder/mod.rs index 098ad576..76331694 100644 --- a/clap_builder/src/builder/mod.rs +++ b/clap_builder/src/builder/mod.rs @@ -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;