diff --git a/.gitignore b/.gitignore index a9d37c5..110d010 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ target Cargo.lock +.vscode \ No newline at end of file diff --git a/src/display.rs b/src/display.rs new file mode 100644 index 0000000..5744f39 --- /dev/null +++ b/src/display.rs @@ -0,0 +1,89 @@ +use std::fmt::Display; + +/// Indentation token +#[derive(Debug)] +struct Token { + /// Is followed by a brother + siblings: bool, + /// Is intermediate while printing children + children: bool, +} + +impl Display for Token { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + let Token { siblings, children } = self; + + write!( + f, + "{}", + match (siblings, children) { + (true, true) => "│ ", + (true, false) => "├── ", + (false, true) => " ", + (false, false) => "└── ", + } + ) + } +} + +impl Token { + /// Create a new indentation token + fn new(siblings: bool) -> Self { + Token { + siblings, + children: false, + } + } + + /// Set children flag before starting displaying children + fn set_children(&mut self) { + self.children = true; + } +} + +/// Manages the state during the display operation +#[derive(Debug)] +pub struct Indentation { + tokens: Vec, + ignore_root: bool, +} + +impl Display for Indentation { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + let first: usize = if self.ignore_root { 1 } else { 0 }; + + for token in &self.tokens[first..] { + write!(f, "{token}")?; + } + + Ok(()) + } +} + +impl Indentation { + /// Creates a new indentation handler + pub fn new(ignore_root: bool) -> Self { + Indentation { + tokens: Vec::new(), + ignore_root, + } + } + + /// Adds a new layer of indentation + pub fn indent(&mut self, siblings: bool) -> &mut Self { + // Setup children mode for previous tokens + let len = self.tokens.len(); + if len > 0 { + self.tokens[len - 1].set_children(); + } + + self.tokens.push(Token::new(siblings)); + self + } + + /// Removes the last layer of indentation + pub fn deindent(&mut self) -> &mut Self { + self.tokens.pop(); + self + } +} diff --git a/src/lib.rs b/src/lib.rs index 0a0257b..d84d563 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -35,7 +35,7 @@ missing_copy_implementations )] -use std::fmt::{self, Debug, Formatter}; +use std::fmt::{self, Debug, Display, Formatter}; use std::num::NonZeroUsize; /// Vec-backed ID-tree. @@ -718,3 +718,34 @@ impl Debug for Tree { } } } + +// Handles display +mod display; + +impl Display for Tree { + fn fmt(&self, f: &mut Formatter) -> Result<(), fmt::Error> { + use display::Indentation; + use iter::Edge; + + let mut indent: Indentation = Indentation::new(true); + + for edge in self.root().traverse() { + match edge { + Edge::Open(node) if node.has_children() => { + indent.indent(node.next_sibling().is_some()); + writeln!(f, "{indent}{}", node.value())?; + } + Edge::Open(node) => { + indent.indent(node.next_sibling().is_some()); + writeln!(f, "{indent}{}", node.value())?; + indent.deindent(); + } + Edge::Close(node) if node.has_children() => { + indent.deindent(); + } + _ => {} + } + } + Ok(()) + } +} diff --git a/tests/tree.rs b/tests/tree.rs index ac22561..78eee6a 100644 --- a/tests/tree.rs +++ b/tests/tree.rs @@ -156,3 +156,35 @@ fn insert_id_before() { ); assert_eq!(3, tree.root().children().count()); } + +#[test] +fn test_display() { + let tree = tree! { + "root" => { + "a" => { + "child 1", + }, + "b" => { + "child 2", + }, + } + }; + + let repr = format!("{tree}"); + let expected = "root\n├── a\n│ └── child 1\n└── b\n └── child 2\n"; + + assert_eq!(repr, expected); + + let tree = tree! { + "root" => { + "a", "b" => { + "x", "y" + }, "c" + } + }; + + let repr = format!("{tree}"); + let expected = "root\n├── a\n├── b\n│ ├── x\n│ └── y\n└── c\n"; + + assert_eq!(repr, expected); +}