rosary 0.1.2

A Library About Rose Trees
Documentation
use crate::RoseTree;
use std::{fmt::Display, iter::repeat};

#[derive(Clone, Copy, Debug)]
pub struct Indenter {
  /// Indenter Docs
  /// ╠═Controls how to render tree-format data
  /// ╠═Character varieties
  /// ║ ╠═left_bar <║>
  /// ║ ╠═inward_branch <╠>
  /// ║ ╠═horizontal_bar <═>
  /// ║ ╚═last_entry <╚>
  /// ╠═Indenting parameters
  /// ║ ╠═current_level
  /// ║ ║ ╚═Tracks which indent level we're on. This sentence is on level 3!
  /// ║ ╚═level_width
  /// ║   ╚═States how wide each level's decorations should be.
  /// ╚═This takes TreeFormat types and renders them as pretty-printed trees. It
  /// does not currently cope with wraparound (oops).
  current_level: usize,
  level_width: usize,
  vertical_bar: char,
  inward_branch: char,
  horizontal_bar: char,
  last_entry: char,
}

/// An object used by the TreeFormat to control and track formatting settings.
impl Indenter {
  pub fn next(&self) -> Indenter {
    Indenter {
      current_level: self.current_level + 1,
      ..*self
    }
  }

  /// How long the header of a line should be for this indenter.
  pub fn header_len(&self) -> usize {
    self.current_level * self.level_width
  }

  /// Generate the text of the header for the line.
  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 {
      // Add bridging bar, if needed.
      if *arm {
        accum.push(self.vertical_bar)
      } else {
        accum.push(' ')
      };
      // Adding the horizontal spacing.
      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
  }

  /// Pads a string with the header, toggling branches based on the arms outside
  /// of the current layer.
  pub fn pad_string(&self, continue_arms: &Vec<bool>, text: &String) -> String {
    format!("{}{}", self.line_header(continue_arms), text)
  }

  /// Pads every string in a RoseTree, producing a new, padded RoseTree.
  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::Empty => RoseTree::Empty,
        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))
  }
}

/// Very convenient, I'm not copy-pasting the fancy characters every single
/// time.
impl Default for Indenter {
  fn default() -> Self {
    Self {
      current_level: 0,
      level_width: 2,
      horizontal_bar: '',
      inward_branch: '',
      last_entry: '',
      vertical_bar: '',
    }
  }
}

/// A trait which allows implementers to specify how they should be printed as
/// a tree.
pub trait TreeFormat {
  /// The only thing you need to define is the conversion from your type to a
  /// RoseTree.
  fn treeify(&self) -> RoseTree<String>;

  /// Indent adds the decorations to the strings of the tree with the default
  /// formatter.
  fn indent(&self) -> RoseTree<String> {
    Indenter::default().pad_tree(&self.treeify())
  }

  /// Actually converts the tree into a single string for printing.
  fn fmt_tree(&self) -> String {
    self
      .indent()
      .preorder_iter()
      .fold(String::new(), |accum, line| accum + "\n" + line)
  }
}

/// If you're a RoseTree, you get TreeFormat for free so long as T can be
/// printed. If printing T involves printing newlines, don't. It'll get weird.
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())
  }
}