From aa548ded39c7ba1927019c748c359523b21d59e8 Mon Sep 17 00:00:00 2001 From: HampusM Date: Sat, 29 Oct 2022 14:38:51 +0200 Subject: refactor!: add dependency history type BREAKING CHANGE: Binding builders & configurators now take dependency history type arguments, the DetectedCircular variant of InjectableError now contains a dependency history field & the injectable traits take dependency history instead of a Vec --- src/dependency_history.rs | 186 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 186 insertions(+) create mode 100644 src/dependency_history.rs (limited to 'src/dependency_history.rs') diff --git a/src/dependency_history.rs b/src/dependency_history.rs new file mode 100644 index 0000000..4e36a7b --- /dev/null +++ b/src/dependency_history.rs @@ -0,0 +1,186 @@ +//! Dependency history. + +use std::any::type_name; +use std::collections::HashSet; +use std::fmt::{Debug, Display}; + +const BOLD_MODE: &str = "\x1b[1m"; +const RESET_BOLD_MODE: &str = "\x1b[22m"; + +/// Dependency history interface. +/// +/// **This trait is sealed and cannot be implemented for types outside this crate.** +pub trait IDependencyHistory: private::Sealed +{ + #[doc(hidden)] + fn push(&mut self); + + #[doc(hidden)] + fn contains(&self) -> bool; +} + +/// Dependency history. +#[derive(Clone, Debug)] +pub struct DependencyHistory +{ + inner: Vec<&'static str>, +} + +impl DependencyHistory +{ + #[must_use] + pub(crate) fn new() -> Self + { + Self { inner: vec![] } + } +} + +impl IDependencyHistory for DependencyHistory +{ + #[doc(hidden)] + fn push(&mut self) + { + self.inner.push(type_name::()); + } + + #[doc(hidden)] + fn contains(&self) -> bool + { + self.inner.contains(&type_name::()) + } +} + +impl Display for DependencyHistory +{ + fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result + { + let mut found_items = HashSet::new(); + + let opt_dupe_item = self.inner.iter().find(|item| { + if found_items.contains(item) { + return true; + } + + found_items.insert(*item); + + false + }); + + for (index, item) in self.inner.iter().enumerate() { + let mut item_is_dupe = false; + + if let Some(dupe_item) = opt_dupe_item { + if *item == *dupe_item { + formatter + .write_fmt(format_args!("{BOLD_MODE}{item}{RESET_BOLD_MODE}"))?; + + item_is_dupe = true; + } + } + + if !item_is_dupe { + formatter.write_str(item)?; + } + + if index != self.inner.len() - 1 { + formatter.write_str(" -> ")?; + } + } + + if opt_dupe_item.is_some() { + formatter.write_str(" -> ...")?; + } + + Ok(()) + } +} + +impl Default for DependencyHistory +{ + fn default() -> Self + { + Self::new() + } +} + +impl private::Sealed for DependencyHistory {} + +pub(crate) mod private +{ + pub trait Sealed {} +} + +#[cfg(test)] +mod tests +{ + use super::*; + use crate::test_utils::subjects; + + #[test] + fn can_push() + { + let mut dependency_history = DependencyHistory::new(); + + dependency_history.push::(); + + assert!(dependency_history + .inner + .contains(&type_name::())); + } + + #[test] + fn contains_works() + { + let mut dependency_history = DependencyHistory::new(); + + dependency_history + .inner + .push(type_name::()); + + assert!(dependency_history.contains::()); + + assert!(!dependency_history.contains::()); + } + + #[test] + fn display_works() + { + trait Ninja {} + trait Katana {} + trait Blade {} + + let mut dependency_history = DependencyHistory::new(); + + dependency_history.inner.push(type_name::()); + dependency_history.inner.push(type_name::()); + dependency_history.inner.push(type_name::()); + + assert_eq!( + dependency_history.to_string(), + format!( + "{} -> {} -> {}", + type_name::(), + type_name::(), + type_name::() + ) + ); + + dependency_history.inner.push(type_name::()); + + assert_eq!( + dependency_history.to_string(), + format!( + concat!( + "{} -> {bold_mode}{}{reset_bold_mode} -> {} -> ", + "{bold_mode}{}{reset_bold_mode} -> ...", + ), + type_name::(), + type_name::(), + type_name::(), + type_name::(), + bold_mode = BOLD_MODE, + reset_bold_mode = RESET_BOLD_MODE + ) + ); + } +} -- cgit v1.2.3-18-g5258