//! 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 ) ); } }