aboutsummaryrefslogtreecommitdiff
path: root/src/dependency_history.rs
diff options
context:
space:
mode:
Diffstat (limited to 'src/dependency_history.rs')
-rw-r--r--src/dependency_history.rs186
1 files changed, 186 insertions, 0 deletions
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<Dependency: 'static + ?Sized>(&mut self);
+
+ #[doc(hidden)]
+ fn contains<Dependency: 'static + ?Sized>(&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<Dependency: 'static + ?Sized>(&mut self)
+ {
+ self.inner.push(type_name::<Dependency>());
+ }
+
+ #[doc(hidden)]
+ fn contains<Dependency: 'static + ?Sized>(&self) -> bool
+ {
+ self.inner.contains(&type_name::<Dependency>())
+ }
+}
+
+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::<dyn subjects::INumber>();
+
+ assert!(dependency_history
+ .inner
+ .contains(&type_name::<dyn subjects::INumber>()));
+ }
+
+ #[test]
+ fn contains_works()
+ {
+ let mut dependency_history = DependencyHistory::new();
+
+ dependency_history
+ .inner
+ .push(type_name::<dyn subjects::IUserManager>());
+
+ assert!(dependency_history.contains::<dyn subjects::IUserManager>());
+
+ assert!(!dependency_history.contains::<dyn subjects::INumber>());
+ }
+
+ #[test]
+ fn display_works()
+ {
+ trait Ninja {}
+ trait Katana {}
+ trait Blade {}
+
+ let mut dependency_history = DependencyHistory::new();
+
+ dependency_history.inner.push(type_name::<dyn Ninja>());
+ dependency_history.inner.push(type_name::<dyn Katana>());
+ dependency_history.inner.push(type_name::<dyn Blade>());
+
+ assert_eq!(
+ dependency_history.to_string(),
+ format!(
+ "{} -> {} -> {}",
+ type_name::<dyn Ninja>(),
+ type_name::<dyn Katana>(),
+ type_name::<dyn Blade>()
+ )
+ );
+
+ dependency_history.inner.push(type_name::<dyn Katana>());
+
+ assert_eq!(
+ dependency_history.to_string(),
+ format!(
+ concat!(
+ "{} -> {bold_mode}{}{reset_bold_mode} -> {} -> ",
+ "{bold_mode}{}{reset_bold_mode} -> ...",
+ ),
+ type_name::<dyn Ninja>(),
+ type_name::<dyn Katana>(),
+ type_name::<dyn Blade>(),
+ type_name::<dyn Katana>(),
+ bold_mode = BOLD_MODE,
+ reset_bold_mode = RESET_BOLD_MODE
+ )
+ );
+ }
+}