| use crate::tree_config::{tree_config, TreeConfig}; |
| use std::cmp::max; |
| use std::sync::{Arc, Mutex}; |
| |
| /// Tree that holds `text` for the current leaf and a list of `children` that are the branches. |
| #[derive(Debug)] |
| pub struct Tree { |
| pub text: Option<String>, |
| pub children: Vec<Tree>, |
| } |
| |
| /// Position of the element relative to its siblings |
| #[derive(Copy, Clone, Debug, Eq, PartialEq)] |
| pub enum Position { |
| Inside, |
| First, |
| Last, |
| Only, |
| } |
| |
| impl Tree { |
| /// Create a new tree with some optional text. |
| pub fn new(text: Option<&str>) -> Tree { |
| Tree { |
| text: text.map(|x| x.to_string()), |
| children: Vec::new(), |
| } |
| } |
| |
| /// Navigate to the branch at the given `path` relative to this tree. |
| /// If a valid branch is found by following the path, it is returned. |
| pub fn at_mut(&mut self, path: &[usize]) -> Option<&mut Tree> { |
| match path.first() { |
| Some(&i) => match self.children.get_mut(i) { |
| Some(x) => x.at_mut(&path[1..]), |
| _ => None, |
| }, |
| _ => Some(self), |
| } |
| } |
| |
| /// "Render" this tree as a list of `String`s. |
| /// Each string represents a line in the tree. |
| /// `does_continue` is a bool for each column indicating whether the tree continues. |
| pub fn lines( |
| &self, |
| does_continue: &Vec<bool>, |
| index: usize, |
| pool_size: usize, |
| config: &TreeConfig, |
| ) -> Vec<String> { |
| let does_continue = if config.show_first_level && does_continue.is_empty() { |
| vec![true] |
| } else { |
| does_continue.clone() |
| }; |
| let position = match index { |
| _ if pool_size == 1 => Position::Only, |
| _ if (index + 1) == pool_size => Position::Last, |
| 0 => Position::First, |
| _ => Position::Inside, |
| }; |
| let mut next_continue = does_continue.clone(); |
| next_continue.push(match position { |
| Position::Inside | Position::First => true, |
| Position::Last | Position::Only => false, |
| }); |
| |
| let mut txt = String::new(); |
| let pad: String; |
| if does_continue.len() > 1 { |
| for &i in &does_continue[2..] { |
| txt.push_str(&format!( |
| "{}{:indent$}", |
| if i { config.symbols.continued } else { " " }, |
| "", |
| indent = max(config.indent, 1) - 1 |
| )); |
| } |
| pad = txt.clone(); |
| let branch_size = max(config.indent, 2usize) - 2; |
| let branch = match config.symbols.branch.len() { |
| 0 => "-".repeat(branch_size), |
| 1 => config.symbols.branch.repeat(branch_size), |
| _n => config |
| .symbols |
| .branch |
| .repeat(branch_size) |
| .chars() |
| .take(branch_size) |
| .collect::<String>(), |
| }; |
| |
| let is_multiline = self |
| .text |
| .as_ref() |
| .map(|x| x.contains("\n")) |
| .unwrap_or(false); |
| |
| let first_leaf = match (is_multiline, config.symbols.multiline_first) { |
| (true, Some(x)) => x, |
| _ => config.symbols.leaf, |
| }; |
| txt.push_str(&format!( |
| "{}{}{}", |
| match position { |
| Position::Only => config.symbols.join_only, |
| Position::First => config.symbols.join_first, |
| Position::Last => config.symbols.join_last, |
| Position::Inside => config.symbols.join_inner, |
| }, |
| branch, |
| first_leaf, |
| )); |
| |
| let s = match &self.text { |
| Some(x) => match is_multiline { |
| true => format!( |
| "{}", |
| x.replace( |
| "\n", |
| &format!( |
| "\n{}{}{}{}", |
| &pad, |
| match position { |
| Position::Only | Position::Last => |
| " ".repeat(config.symbols.continued.chars().count()), |
| _ => config.symbols.continued.to_string(), |
| }, |
| " ".repeat(branch_size), |
| match &config.symbols.multiline_continued { |
| Some(multi) => multi.to_string(), |
| _ => " ".repeat(first_leaf.chars().count()), |
| } |
| ), |
| ) |
| ), |
| false => x.clone(), |
| }, |
| _ => String::new(), |
| }; |
| txt.push_str(&s); |
| } else { |
| if let Some(x) = &self.text { |
| txt.push_str(&x); |
| } |
| } |
| let mut ret = vec![txt]; |
| for (index, x) in self.children.iter().enumerate() { |
| for line in x.lines(&next_continue, index, self.children.len(), config) { |
| ret.push(line); |
| } |
| } |
| ret |
| } |
| } |
| |
| /// Holds the current state of the tree, including the path to the branch. |
| /// Multiple trees may point to the same data. |
| #[derive(Debug, Clone)] |
| pub(crate) struct TreeBuilderBase { |
| data: Arc<Mutex<Tree>>, |
| path: Vec<usize>, |
| dive_count: usize, |
| config: Option<TreeConfig>, |
| is_enabled: bool, |
| } |
| |
| impl TreeBuilderBase { |
| /// Create a new state |
| pub fn new() -> TreeBuilderBase { |
| TreeBuilderBase { |
| data: Arc::new(Mutex::new(Tree::new(None))), |
| path: vec![], |
| dive_count: 1, |
| config: None, |
| is_enabled: true, |
| } |
| } |
| |
| pub fn set_enabled(&mut self, enabled: bool) { |
| self.is_enabled = enabled; |
| } |
| pub fn is_enabled(&self) -> bool { |
| self.is_enabled |
| } |
| |
| pub fn add_leaf(&mut self, text: &str) { |
| let &dive_count = &self.dive_count; |
| if dive_count > 0 { |
| for i in 0..dive_count { |
| let mut n = 0; |
| if let Some(x) = self.data.lock().unwrap().at_mut(&self.path) { |
| x.children.push(Tree::new(if i == max(1, dive_count) - 1 { |
| Some(&text) |
| } else { |
| None |
| })); |
| n = x.children.len() - 1; |
| } |
| self.path.push(n); |
| } |
| self.dive_count = 0; |
| } else { |
| if let Some(x) = self |
| .data |
| .lock() |
| .unwrap() |
| .at_mut(&self.path[..max(1, self.path.len()) - 1]) |
| { |
| x.children.push(Tree::new(Some(&text))); |
| let n = match self.path.last() { |
| Some(&x) => x + 1, |
| _ => 0, |
| }; |
| self.path.last_mut().map(|x| *x = n); |
| } |
| } |
| } |
| |
| pub fn set_config_override(&mut self, config: Option<TreeConfig>) { |
| self.config = config; |
| } |
| |
| pub fn config_override(&self) -> &Option<TreeConfig> { |
| &self.config |
| } |
| pub fn config_override_mut(&mut self) -> &mut Option<TreeConfig> { |
| &mut self.config |
| } |
| |
| pub fn enter(&mut self) { |
| self.dive_count += 1; |
| } |
| |
| /// Try stepping up to the parent tree branch. |
| /// Returns false if already at the top branch. |
| pub fn exit(&mut self) -> bool { |
| if self.dive_count > 0 { |
| self.dive_count -= 1; |
| true |
| } else { |
| if self.path.len() > 1 { |
| self.path.pop(); |
| true |
| } else { |
| false |
| } |
| } |
| } |
| |
| pub fn depth(&self) -> usize { |
| max(1, self.path.len() + self.dive_count) - 1 |
| } |
| |
| pub fn peek_print(&self) { |
| println!("{}", self.peek_string()); |
| } |
| |
| pub fn print(&mut self) { |
| self.peek_print(); |
| self.clear(); |
| } |
| pub fn clear(&mut self) { |
| *self = Self::new(); |
| } |
| |
| pub fn string(&mut self) -> String { |
| let s = self.peek_string(); |
| self.clear(); |
| s |
| } |
| |
| pub fn peek_string(&self) -> String { |
| let config = self |
| .config_override() |
| .clone() |
| .unwrap_or_else(|| tree_config().clone()); |
| (&self.data.lock().unwrap().lines(&vec![], 0, 1, &config)[1..]).join("\n") |
| } |
| } |