use crate::RoseTree;
use std::{fmt::Display, iter::repeat};
#[derive(Clone, Copy, Debug)]
pub struct Indenter {
current_level: usize,
level_width: usize,
vertical_bar: char,
inward_branch: char,
horizontal_bar: char,
last_entry: char,
}
impl Indenter {
pub fn next(&self) -> Indenter {
Indenter {
current_level: self.current_level + 1,
..*self
}
}
pub fn header_len(&self) -> usize {
self.current_level * self.level_width
}
pub fn line_header(&self, arms_to_continue: &Vec<bool>) -> String {
let mut accum = String::with_capacity(self.header_len());
let mut arm_iter = arms_to_continue.iter();
let is_last_line = if let Some(arm) = arm_iter.next_back() {
!arm
} else {
return String::with_capacity(0);
};
for arm in arm_iter {
if *arm {
accum.push(self.vertical_bar)
} else {
accum.push(' ')
};
accum.extend(repeat(' ').take(self.level_width - 1));
}
let bar = if is_last_line {
self.last_entry
} else {
self.inward_branch
};
accum.push(bar);
accum.extend(repeat(self.horizontal_bar).take(self.level_width - 1));
accum
}
pub fn pad_string(&self, continue_arms: &Vec<bool>, text: &String) -> String {
format!("{}{}", self.line_header(continue_arms), text)
}
pub fn pad_tree(&self, tree: &RoseTree<String>) -> RoseTree<String> {
fn rec_pad(
indenter: &Indenter,
tree: &RoseTree<String>,
continue_arms: &mut Vec<bool>,
) -> RoseTree<String> {
match tree {
RoseTree::Leaf(val) => RoseTree::Leaf(indenter.pad_string(continue_arms, val)),
RoseTree::Branch(head, children) => {
let new_head = indenter.pad_string(continue_arms, head);
let mut child_iter = children.iter();
let last_child = if let Some(child) = child_iter.next_back() {
child
} else {
return RoseTree::Leaf(new_head);
};
let child_indenter = indenter.next();
let mut new_children = Vec::with_capacity(children.len());
continue_arms.push(true);
for child in child_iter {
new_children.push(rec_pad(&child_indenter, child, continue_arms));
}
continue_arms.pop();
continue_arms.push(false);
new_children.push(rec_pad(&child_indenter, last_child, continue_arms));
continue_arms.pop();
RoseTree::Branch(new_head, new_children)
}
}
}
rec_pad(self, tree, &mut Vec::with_capacity(self.current_level))
}
}
impl Default for Indenter {
fn default() -> Self {
Self {
current_level: 0,
level_width: 2,
horizontal_bar: '═',
inward_branch: '╠',
last_entry: '╚',
vertical_bar: '║',
}
}
}
pub trait TreeFormat {
fn treeify(&self) -> RoseTree<String>;
fn indent(&self) -> RoseTree<String> {
Indenter::default().pad_tree(&self.treeify())
}
fn fmt_tree(&self) -> String {
self
.indent()
.preorder_iter()
.fold(String::new(), |accum, line| accum + "\n" + line)
}
}
impl<T: Display> TreeFormat for RoseTree<T> {
fn treeify(&self) -> RoseTree<String> {
self.map(|val| val.to_string())
}
}
impl Display for dyn TreeFormat {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.fmt_tree())
}
}
#[cfg(test)]
mod test {
use super::{Indenter, TreeFormat};
use crate::RoseTree;
#[test]
fn indent_levels() {
let indenter = Indenter::default();
assert_eq!(indenter.line_header(&vec![]), "", "{:#?}", indenter);
assert_eq!(indenter.line_header(&vec![]), "", "{:#?}", indenter);
let indenter = indenter.next();
assert_eq!(indenter.line_header(&vec![true]), "╠═", "{:#?}", indenter);
assert_eq!(indenter.line_header(&vec![false]), "╚═", "{:#?}", indenter);
let indenter = indenter.next();
assert_eq!(
indenter.line_header(&vec![true, true]),
"║ ╠═",
"{:#?}",
indenter
);
assert_eq!(
indenter.line_header(&vec![true, false]),
"║ ╚═",
"{:#?}",
indenter
);
}
#[test]
fn skip_level() {
let _indenter = Indenter::default();
let doc_tree = RoseTree::Branch(
"Level 0",
vec![
RoseTree::Branch(
"Level 1",
vec![RoseTree::Branch("Level 2", vec![RoseTree::Leaf("Level 3")])],
),
RoseTree::Leaf("Bottom"),
],
);
assert_eq!(
r#"
Level 0
╠═Level 1
║ ╚═Level 2
║ ╚═Level 3
╚═Bottom"#,
doc_tree.fmt_tree()
);
}
#[test]
fn print_the_docs() {
let doc_tree = RoseTree::Branch(
"Indenter Docs",
vec! [
RoseTree::Leaf("Controls how to render tree-format data"),
RoseTree::Branch(
"Character varieties",
vec! [
RoseTree::Leaf("left_bar <║>"),
RoseTree::Leaf("inward_branch <╠>"),
RoseTree::Leaf("horizontal_bar <═>"),
RoseTree::Leaf("last_entry <╚>")]),
RoseTree::Branch(
"Indenting parameters",
vec! [
RoseTree::Branch(
"current_level",
vec! [
RoseTree::Leaf("Tracks which indent level we're on. This sentence is on level 3!")]),
RoseTree::Branch(
"level_width",
vec! [
RoseTree::Leaf("States how wide each level's decorations should be.")]),
]),
RoseTree::Leaf("This takes TreeFormat types and renders them as pretty-printed trees. It does not currently cope with wraparound (oops).")]);
println!("{}", doc_tree.fmt_tree())
}
}