diff options
Diffstat (limited to 'src/lib.rs')
-rw-r--r-- | src/lib.rs | 210 |
1 files changed, 210 insertions, 0 deletions
diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..7893d6d --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,210 @@ +#![allow(clippy::branches_sharing_code)] + +#[cfg(test)] +mod tests; + +use std::collections::VecDeque; +use std::fmt::{self, Display}; +use std::rc::Rc; + +/// a simple recursive type which is able to render its +/// components in a tree-like format +#[derive(Debug, Clone)] +pub struct Tree<D: Display> { + pub root: D, + pub leaves: Vec<Tree<D>>, + multiline: bool, + glyphs: GlyphPalette, +} + +impl<D: Display> Tree<D> { + pub fn new(root: D) -> Self { + Tree { + root, + leaves: Vec::new(), + multiline: false, + glyphs: GlyphPalette::new(), + } + } + + pub fn with_leaves(mut self, leaves: impl IntoIterator<Item = impl Into<Tree<D>>>) -> Self { + self.leaves = leaves.into_iter().map(Into::into).collect(); + self + } + + /// Ensure all lines for `root` are indented + pub fn with_multiline(mut self, yes: bool) -> Self { + self.multiline = yes; + self + } + + /// Customize the rendering of this node + pub fn with_glyphs(mut self, glyphs: GlyphPalette) -> Self { + self.glyphs = glyphs; + self + } +} + +impl<D: Display> Tree<D> { + /// Ensure all lines for `root` are indented + pub fn set_multiline(&mut self, yes: bool) -> &mut Self { + self.multiline = yes; + self + } + + /// Customize the rendering of this node + pub fn set_glyphs(&mut self, glyphs: GlyphPalette) -> &mut Self { + self.glyphs = glyphs; + self + } +} + +impl<D: Display> Tree<D> { + pub fn push(&mut self, leaf: impl Into<Tree<D>>) -> &mut Self { + self.leaves.push(leaf.into()); + self + } +} + +impl<D: Display> From<D> for Tree<D> { + fn from(inner: D) -> Self { + Self::new(inner) + } +} + +impl<D: Display> Extend<D> for Tree<D> { + fn extend<T: IntoIterator<Item = D>>(&mut self, iter: T) { + self.leaves.extend(iter.into_iter().map(Into::into)) + } +} + +impl<D: Display> Extend<Tree<D>> for Tree<D> { + fn extend<T: IntoIterator<Item = Tree<D>>>(&mut self, iter: T) { + self.leaves.extend(iter) + } +} + +impl<D: Display> Display for Tree<D> { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + self.root.fmt(f)?; // Pass along `f.alternate()` + writeln!(f)?; + let mut queue = DisplauQueue::new(); + let no_space = Rc::new(Vec::new()); + enqueue_leaves(&mut queue, self, no_space); + while let Some((last, leaf, spaces)) = queue.pop_front() { + let mut prefix = ( + if last { + leaf.glyphs.last_item + } else { + leaf.glyphs.middle_item + }, + leaf.glyphs.item_indent, + ); + + if leaf.multiline { + let rest_prefix = ( + if last { + leaf.glyphs.last_skip + } else { + leaf.glyphs.middle_skip + }, + leaf.glyphs.skip_indent, + ); + debug_assert_eq!(prefix.0.chars().count(), rest_prefix.0.chars().count()); + debug_assert_eq!(prefix.1.chars().count(), rest_prefix.1.chars().count()); + + let root = if f.alternate() { + format!("{:#}", leaf.root) + } else { + format!("{:}", leaf.root) + }; + for line in root.lines() { + // print single line + for s in spaces.as_slice() { + if *s { + self.glyphs.last_skip.fmt(f)?; + self.glyphs.skip_indent.fmt(f)?; + } else { + self.glyphs.middle_skip.fmt(f)?; + self.glyphs.skip_indent.fmt(f)?; + } + } + prefix.0.fmt(f)?; + prefix.1.fmt(f)?; + line.fmt(f)?; + writeln!(f)?; + prefix = rest_prefix; + } + } else { + // print single line + for s in spaces.as_slice() { + if *s { + self.glyphs.last_skip.fmt(f)?; + self.glyphs.skip_indent.fmt(f)?; + } else { + self.glyphs.middle_skip.fmt(f)?; + self.glyphs.skip_indent.fmt(f)?; + } + } + prefix.0.fmt(f)?; + prefix.1.fmt(f)?; + leaf.root.fmt(f)?; // Pass along `f.alternate()` + writeln!(f)?; + } + + // recurse + if !leaf.leaves.is_empty() { + let s: &Vec<bool> = &spaces; + let mut child_spaces = s.clone(); + child_spaces.push(last); + let child_spaces = Rc::new(child_spaces); + enqueue_leaves(&mut queue, leaf, child_spaces); + } + } + Ok(()) + } +} + +type DisplauQueue<'t, D> = VecDeque<(bool, &'t Tree<D>, Rc<Vec<bool>>)>; + +fn enqueue_leaves<'t, D: Display>( + queue: &mut DisplauQueue<'t, D>, + parent: &'t Tree<D>, + spaces: Rc<Vec<bool>>, +) { + for (i, leaf) in parent.leaves.iter().rev().enumerate() { + let last = i == 0; + queue.push_front((last, leaf, spaces.clone())); + } +} + +#[derive(Copy, Clone, Debug, PartialEq, Eq)] +pub struct GlyphPalette { + pub middle_item: &'static str, + pub last_item: &'static str, + pub item_indent: &'static str, + + pub middle_skip: &'static str, + pub last_skip: &'static str, + pub skip_indent: &'static str, +} + +impl GlyphPalette { + pub const fn new() -> Self { + Self { + middle_item: "├", + last_item: "└", + item_indent: "── ", + + middle_skip: "│", + last_skip: " ", + skip_indent: " ", + } + } +} + +impl Default for GlyphPalette { + fn default() -> Self { + Self::new() + } +} |