├── .md-inc.toml ├── examples ├── out │ ├── no_macros.txt │ ├── multi_line.txt │ ├── multiple_trees_A.txt │ ├── nested.txt │ ├── fibonacci.txt │ ├── multiple_trees_B.txt │ └── panic.txt ├── multi_line.rs ├── nested.rs ├── fibonacci.rs ├── no_macros.rs ├── panic.rs └── multiple_trees.rs ├── .gitignore ├── Cargo.toml ├── src ├── scoped_branch.rs ├── defer.rs ├── default.rs ├── tree_config.rs ├── internal.rs ├── test.rs └── lib.rs ├── doc ├── build │ ├── LICENSE.adoc │ └── asciidoc-coalescer.rb └── readme_template.adoc └── README.md /.md-inc.toml: -------------------------------------------------------------------------------- 1 | base_dir = "examples" 2 | files = ["README.md"] -------------------------------------------------------------------------------- /examples/out/no_macros.txt: -------------------------------------------------------------------------------- 1 | 1 Branch 2 | └╼ 1.1 Child 3 | 2 Sibling -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | **/*.rs.bk 3 | .idea 4 | *.lock 5 | test_out 6 | 7 | .DS_Store 8 | -------------------------------------------------------------------------------- /examples/out/multi_line.txt: -------------------------------------------------------------------------------- 1 | 1 2 | ├╼ 1.1 3 | │ Another line... 4 | │ ... and one more line 5 | └╼ 1.2 -------------------------------------------------------------------------------- /examples/out/multiple_trees_A.txt: -------------------------------------------------------------------------------- 1 | A TREE 2 | ├╼ A TREE 3 | │ └╼ A TREE 4 | └╼ A TREE 5 | └╼ A TREE -------------------------------------------------------------------------------- /examples/out/nested.txt: -------------------------------------------------------------------------------- 1 | a 2 | ├╼ b 3 | │ └╼ c 4 | │ └╼ Nothing to see here 5 | └╼ c 6 | └╼ Nothing to see here -------------------------------------------------------------------------------- /examples/out/fibonacci.txt: -------------------------------------------------------------------------------- 1 | A Fibonacci Tree 2 | ├╼ 6 3 | │ ├╼ 1 4 | │ ├╼ 2 5 | │ │ └╼ 1 6 | │ └╼ 3 7 | │ └╼ 1 8 | └╼ That's All Folks! -------------------------------------------------------------------------------- /examples/out/multiple_trees_B.txt: -------------------------------------------------------------------------------- 1 | B TREE 2 | ├──> B TREE 3 | │ ╰──> B TREE 4 | ├──> B TREE 5 | │ ╰──> B TREE 6 | ╰──> B TREE 7 | ╰──> B TREE -------------------------------------------------------------------------------- /examples/out/panic.txt: -------------------------------------------------------------------------------- 1 | By using the 'defer_' functions 2 | └╼ Output will still be generated 3 | └╼ Otherwise you might lose your valuable tree! 4 | Now for something crazy... 5 | └╼ Here are my last words 6 | └╼ Stay calm, and try not to panic -------------------------------------------------------------------------------- /examples/multi_line.rs: -------------------------------------------------------------------------------- 1 | use debug_tree::*; 2 | fn main() { 3 | // output to file at the end of this block 4 | defer_write!("examples/out/multi_line.txt"); 5 | add_branch!("1"); 6 | add_leaf!("1.1\nAnother line...\n... and one more line"); 7 | add_leaf!("1.2"); 8 | } 9 | -------------------------------------------------------------------------------- /examples/nested.rs: -------------------------------------------------------------------------------- 1 | use debug_tree::*; 2 | fn a() { 3 | add_branch!("a"); 4 | b(); 5 | c(); 6 | } 7 | fn b() { 8 | add_branch!("b"); 9 | c(); 10 | } 11 | fn c() { 12 | add_branch!("c"); 13 | add_leaf!("Nothing to see here"); 14 | } 15 | 16 | fn main() { 17 | defer_write!("examples/out/nested.txt"); 18 | a(); 19 | } 20 | -------------------------------------------------------------------------------- /examples/fibonacci.rs: -------------------------------------------------------------------------------- 1 | use debug_tree::*; 2 | 3 | fn factors(x: usize) { 4 | add_branch!("{}", x); // <~ THE MAGIC LINE 5 | for i in 1..x { 6 | if x % i == 0 { 7 | factors(i); 8 | } 9 | } 10 | } 11 | 12 | fn main() { 13 | // output to file at the end of this block 14 | defer_write!("examples/out/fibonacci.txt"); 15 | add_branch!("A Fibonacci Tree"); 16 | factors(6); 17 | add_leaf!("That's All Folks!"); 18 | } 19 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "debug_tree" 3 | version = "0.4.0" 4 | authors = ["Marty Papamanolis "] 5 | edition = "2018" 6 | repository = "https://github.com/martypapa/debug-tree" 7 | readme = "README.md" 8 | license = "MIT" 9 | description = "Build a tree one element at a time and output it as a pretty string." 10 | 11 | [lib] 12 | name = "debug_tree" 13 | crate-type = ["lib"] 14 | 15 | [dependencies] 16 | once_cell = "1" 17 | 18 | [dev-dependencies] 19 | tokio = {version = "0.2.9", features = ["macros", "fs"] } 20 | futures = "0.3.4" -------------------------------------------------------------------------------- /examples/no_macros.rs: -------------------------------------------------------------------------------- 1 | use debug_tree::TreeBuilder; 2 | 3 | fn main() { 4 | // Make a new tree. 5 | let tree = TreeBuilder::new(); 6 | 7 | // Add a scoped branch. The next item added will belong to the branch. 8 | let mut branch = tree.add_branch("1 Branch"); 9 | 10 | // Add a leaf to the current branch 11 | tree.add_leaf("1.1 Child"); 12 | 13 | // Leave scope early 14 | branch.release(); 15 | tree.add_leaf("2 Sibling"); 16 | // output to file 17 | tree.write("examples/out/no_macros.txt").ok(); // Write and flush. 18 | } 19 | -------------------------------------------------------------------------------- /examples/panic.rs: -------------------------------------------------------------------------------- 1 | use debug_tree::*; 2 | 3 | fn i_will_panic() { 4 | add_branch!("Here are my last words"); 5 | add_leaf!("Stay calm, and try not to panic"); 6 | panic!("I told you so...") 7 | } 8 | 9 | fn main() { 10 | // output to file at the end of this block 11 | defer_write!("examples/out/panic.txt"); 12 | // print at the end of this block 13 | { 14 | add_branch!("By using the 'defer_' functions"); 15 | add_branch!("Output will still be generated"); 16 | add_branch!("Otherwise you might lose your valuable tree!"); 17 | } 18 | add_branch!("Now for something crazy..."); 19 | i_will_panic(); 20 | } 21 | -------------------------------------------------------------------------------- /src/scoped_branch.rs: -------------------------------------------------------------------------------- 1 | use crate::TreeBuilder; 2 | 3 | pub struct ScopedBranch { 4 | state: Option, 5 | } 6 | 7 | impl ScopedBranch { 8 | pub fn new(state: TreeBuilder) -> ScopedBranch { 9 | state.enter(); 10 | ScopedBranch { state: Some(state) } 11 | } 12 | pub fn none() -> ScopedBranch { 13 | ScopedBranch { state: None } 14 | } 15 | pub fn release(&mut self) { 16 | if let Some(x) = &self.state { 17 | x.exit(); 18 | } 19 | self.state = None; 20 | } 21 | } 22 | impl Drop for ScopedBranch { 23 | fn drop(&mut self) { 24 | self.release(); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /examples/multiple_trees.rs: -------------------------------------------------------------------------------- 1 | use debug_tree::*; 2 | 3 | fn populate(tree_name: &str, n_children: usize) { 4 | add_branch_to!(tree_name, "{} TREE", tree_name); 5 | for _ in 0..n_children { 6 | populate(tree_name, n_children / 2); 7 | } 8 | } 9 | fn main() { 10 | // Override tree config (just for "B") 11 | let b_tree = tree("B"); 12 | b_tree.set_config_override( 13 | TreeConfig::new() 14 | .indent(4) 15 | .symbols(TreeSymbols::with_rounded().leaf("> ")), 16 | ); 17 | defer_write!(b_tree, "examples/out/multiple_trees_B.txt"); 18 | defer_write!("A", "examples/out/multiple_trees_A.txt"); 19 | 20 | populate("A", 2); 21 | populate("B", 3); 22 | } 23 | -------------------------------------------------------------------------------- /doc/build/LICENSE.adoc: -------------------------------------------------------------------------------- 1 | Copyright (C) 2014-2019 The Asciidoctor Project 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in 11 | all copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | THE SOFTWARE. -------------------------------------------------------------------------------- /src/defer.rs: -------------------------------------------------------------------------------- 1 | use crate::TreeBuilder; 2 | 3 | /// A deferred function called with an argument, `TreeBuilder` 4 | pub struct DeferredFn ()> { 5 | tree: Option, 6 | action: Option, 7 | } 8 | 9 | impl DeferredFn 10 | where 11 | F: Fn(TreeBuilder) -> (), 12 | { 13 | /// Create a new deferred function based on `tree` 14 | pub fn new(tree: TreeBuilder, action: F) -> Self { 15 | DeferredFn { 16 | tree: Some(tree), 17 | action: Some(action), 18 | } 19 | } 20 | /// Create an empty deferred function 21 | /// This does nothing when scope ends 22 | pub fn none() -> Self { 23 | DeferredFn { 24 | tree: None, 25 | action: None, 26 | } 27 | } 28 | 29 | /// Disables the deferred function 30 | /// This prevents the function from executing when the scope ends 31 | pub fn cancel(&mut self) { 32 | self.tree = None; 33 | self.action = None; 34 | } 35 | } 36 | 37 | impl Drop for DeferredFn 38 | where 39 | F: Fn(TreeBuilder) -> (), 40 | { 41 | fn drop(&mut self) { 42 | if let (Some(x), Some(action)) = (&self.tree, &self.action) { 43 | action(x.clone()); 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /doc/build/asciidoc-coalescer.rb: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | # This script coalesces the AsciiDoc content from a document master into a 4 | # single output file. It does so by resolving all preprocessor directives in 5 | # the document, and in any files which are included. The resolving of include 6 | # directives is likely of most interest to users of this script. 7 | # 8 | # This script works by using Asciidoctor's PreprocessorReader to read and 9 | # resolve all the lines in the specified input file. The script then writes the 10 | # result to the output. 11 | # 12 | # The script only recognizes attributes passed in as options or those defined 13 | # in the document header. It does not currently process attributes defined in 14 | # other, arbitrary locations within the document. 15 | # 16 | # You can find a similar extension written against AsciidoctorJ here: 17 | # https://github.com/hibernate/hibernate-asciidoctor-extensions/blob/master/src/main/java/org/hibernate/infra/asciidoctor/extensions/savepreprocessed/SavePreprocessedOutputPreprocessor.java 18 | 19 | # TODO 20 | # - add cli option to write attributes passed to cli to header of document 21 | # - escape all preprocessor directives after lines are processed (these are preprocessor directives that were escaped in the input) 22 | # - wrap in a custom converter so it can be used as an extension 23 | 24 | require 'asciidoctor' 25 | require 'optparse' 26 | 27 | options = { attributes: [], output: '-' } 28 | OptionParser.new do |opts| 29 | opts.banner = 'Usage: ruby asciidoc-coalescer.rb [OPTIONS] FILE' 30 | opts.on('-a', '--attribute key[=value]', 'A document attribute to set in the form of key[=value]') do |a| 31 | options[:attributes] << a 32 | end 33 | opts.on('-o', '--output FILE', 'Write output to FILE instead of stdout.') do |o| 34 | options[:output] = o 35 | end 36 | end.parse! 37 | 38 | unless (source_file = ARGV.shift) 39 | warn 'Please specify an AsciiDoc source file to coalesce.' 40 | exit 1 41 | end 42 | 43 | unless (output_file = options[:output]) == '-' 44 | if (output_file = File.expand_path output_file) == (File.expand_path source_file) 45 | warn 'Source and output cannot be the same file.' 46 | exit 1 47 | end 48 | end 49 | 50 | # NOTE first, resolve attributes defined at the end of the document header 51 | # QUESTION can we do this in a single load? 52 | doc = Asciidoctor.load_file source_file, safe: :unsafe, header_only: true, attributes: options[:attributes] 53 | # NOTE quick and dirty way to get the attributes set or unset by the document header 54 | header_attr_names = (doc.instance_variable_get :@attributes_modified).to_a 55 | header_attr_names.each {|k| doc.attributes[%(#{k}!)] = '' unless doc.attr? k } 56 | 57 | doc = Asciidoctor.load_file source_file, safe: :unsafe, parse: false, attributes: doc.attributes 58 | # FIXME also escape ifdef, ifndef, ifeval and endif directives 59 | # FIXME do this more carefully by reading line by line; if input differs by output by leading backslash, restore original line 60 | lines = doc.reader.read.gsub(/^include::(?=.*\[\]$)/m, '\\include::') 61 | 62 | if output_file == '-' 63 | puts lines 64 | else 65 | File.open(output_file, 'w') {|f| f.write lines } 66 | end -------------------------------------------------------------------------------- /doc/readme_template.adoc: -------------------------------------------------------------------------------- 1 | :examples: ../examples/ 2 | 3 | = Debug Tree 4 | 5 | This library allows you to build a tree one element at a time and output it as a pretty string. 6 | 7 | The tree can easily be output to a `String`, `stdout` or a file. 8 | 9 | This is particularly convenient for generating clean output from nested and recursive functions. 10 | 11 | 12 | :toc: 13 | 14 | == Recursive Fibonacci Example 15 | 16 | Using the `add_branch!()` macro at the start of the `factors()` function, you can generate an entire call tree, with minimal effort. 17 | 18 | [source,rust] 19 | ---- 20 | include::{examples}fibonacci.rs[] 21 | ---- 22 | 23 | ---- 24 | include::{examples}out/fibonacci.txt[] 25 | ---- 26 | == Overview 27 | 28 | * Add a branch 29 | - `add_branch!("Hello, {}", "World")` 30 | - The branch will exit at the end of the current block 31 | 32 | * Add a leaf 33 | - `add_leaf!("I am a {}", "leaf")` 34 | - Added to the current scoped branch 35 | 36 | * Print a tree, or write it to file at the end of a block 37 | - `defer_print!()` 38 | - `defer_write!("filename.txt")` 39 | - The tree will be empty after these calls 40 | - To prevent clearing, use `defer_peek_print!` and `defer_peek_write!` 41 | 42 | * Get the trees pretty-string 43 | - 44 | 45 | * Handle multiple trees using named trees 46 | - `add_branch_to!("A", "I'm a branch on tree 'A'")` 47 | - `add_leaf_to!("A", "I'm a leaf on tree 'A'")` 48 | - `defer_print!("A")` 49 | - `defer_write!("A", "filename.txt")` 50 | 51 | * Get a named tree 52 | - `tree("TREE_NAME")` 53 | 54 | * Retrieve the pretty-string from a tree 55 | - `tree("TREE_NAME").string()` 56 | 57 | 58 | * Usage across threads 59 | - `default_tree()` is local to each thread 60 | - Named trees are shared between threads 61 | 62 | == More Examples 63 | 64 | === Multiple Tagged Trees 65 | 66 | If you need multiple, separated trees you can use a name tag. 67 | 68 | [source,rust] 69 | ---- 70 | include::{examples}multiple_trees.rs[] 71 | ---- 72 | ---- 73 | include::{examples}out/multiple_trees_A.txt[] 74 | ---- 75 | ---- 76 | include::{examples}out/multiple_trees_B.txt[] 77 | ---- 78 | 79 | === Nested Functions 80 | 81 | Branches also make nested function calls a lot easier to follow. 82 | 83 | [source,rust] 84 | ---- 85 | include::{examples}nested.rs[] 86 | ---- 87 | ---- 88 | include::{examples}out/nested.txt[] 89 | ---- 90 | 91 | === Line Breaks 92 | 93 | Newlines in multi-line strings are automatically indented. 94 | 95 | [source,rust] 96 | ---- 97 | include::{examples}multi_line.rs[] 98 | ---- 99 | ---- 100 | include::{examples}out/multi_line.txt[] 101 | ---- 102 | 103 | === Panics 104 | Even if there is a panic, the tree is not lost! 105 | The `defer_` functions were introduced to allow the tree 106 | to be printed our written to file in the case of a `panic!` or early return. 107 | 108 | [source,rust] 109 | ---- 110 | include::{examples}panic.rs[] 111 | ---- 112 | ---- 113 | include::{examples}out/panic.txt[] 114 | ---- 115 | 116 | 117 | === Without Macros 118 | 119 | If you prefer not using macros, you can construct `TreeBuilder`s manually. 120 | 121 | [source,rust] 122 | ---- 123 | include::{examples}no_macros.rs[] 124 | ---- 125 | ---- 126 | include::{examples}out/no_macros.txt[] 127 | ---- 128 | -------------------------------------------------------------------------------- /src/default.rs: -------------------------------------------------------------------------------- 1 | use crate::TreeBuilder; 2 | 3 | /// Returns the default tree for the current thread 4 | /// 5 | /// # Example 6 | /// 7 | /// ``` 8 | /// use debug_tree::default_tree; 9 | /// default_tree().add_leaf("A new leaf"); 10 | /// assert_eq!("A new leaf", default_tree().peek_string()); 11 | /// ``` 12 | pub fn default_tree() -> TreeBuilder { 13 | thread_local! { 14 | static DEFAULT_BUILDER: TreeBuilder = TreeBuilder::new(); 15 | } 16 | DEFAULT_BUILDER.with(|f| f.clone()) 17 | } 18 | 19 | /// Adds a leaf to the default tree with the given text and formatting arguments 20 | /// 21 | /// # Arguments 22 | /// * `text...` - Formatted text arguments, as per `format!(...)`. 23 | /// 24 | /// # Example 25 | /// 26 | /// ``` 27 | /// #[macro_use] 28 | /// use debug_tree::{default_tree, add_leaf}; 29 | /// fn main() { 30 | /// add_leaf!("A {} leaf", "new"); 31 | /// assert_eq!("A new leaf", &default_tree().peek_string()); 32 | /// } 33 | /// ``` 34 | #[macro_export] 35 | macro_rules! add_leaf { 36 | ($($arg:tt)*) => { 37 | if $crate::default::default_tree().is_enabled() { 38 | $crate::default::default_tree().add_leaf(&format!($($arg)*)) 39 | } 40 | }; 41 | } 42 | 43 | /// Adds the value as a leaf to the default tree. 44 | /// 45 | /// Returns the given `value` argument. 46 | /// 47 | /// # Arguments 48 | /// * `value` - An expression that implements the `Display` trait. 49 | /// 50 | /// # Example 51 | /// 52 | /// ``` 53 | /// #[macro_use] 54 | /// use debug_tree::{default_tree, add_leaf_value}; 55 | /// fn main() { 56 | /// let value = add_leaf_value!(10); 57 | /// assert_eq!("10", &default_tree().string()); 58 | /// assert_eq!(10, value); 59 | /// } 60 | /// ``` 61 | #[macro_export] 62 | macro_rules! add_leaf_value { 63 | ($value:expr) => {{ 64 | let v = $value; 65 | if $crate::default::default_tree().is_enabled() { 66 | $crate::default::default_tree().add_leaf(&format!("{}", &v)); 67 | } 68 | v 69 | }}; 70 | } 71 | 72 | /// Adds a scoped branch to the default tree with the given text and formatting arguments 73 | /// The branch will be exited at the end of the current block. 74 | /// 75 | /// # Arguments 76 | /// * `text...` - Formatted text arguments, as per `format!(...)`. 77 | /// 78 | /// # Example 79 | /// 80 | /// ``` 81 | /// #[macro_use] 82 | /// use debug_tree::{default_tree, add_branch, add_leaf}; 83 | /// fn main() { 84 | /// { 85 | /// add_branch!("New {}", "Branch"); // _branch enters scope 86 | /// // tree is now pointed inside new branch. 87 | /// add_leaf!("Child of {}", "Branch"); 88 | /// // Block ends, so tree exits the current branch. 89 | /// } 90 | /// add_leaf!("Sibling of {}", "Branch"); 91 | /// assert_eq!("\ 92 | /// New Branch 93 | /// └╼ Child of Branch 94 | /// Sibling of Branch" , &default_tree().string()); 95 | /// } 96 | /// ``` 97 | #[macro_export] 98 | macro_rules! add_branch { 99 | () => { 100 | let _debug_tree_branch = if $crate::default::default_tree().is_enabled() { 101 | $crate::default::default_tree().enter_scoped() 102 | } else { 103 | $crate::scoped_branch::ScopedBranch::none() 104 | }; 105 | }; 106 | ($($arg:tt)*) => { 107 | let _debug_tree_branch = if $crate::default::default_tree().is_enabled() { 108 | $crate::default::default_tree().add_branch(&format!($($arg)*)) 109 | } else { 110 | $crate::scoped_branch::ScopedBranch::none() 111 | }; 112 | }; 113 | 114 | } 115 | 116 | #[cfg(test)] 117 | mod test { 118 | use crate::default_tree; 119 | use crate::*; 120 | 121 | #[test] 122 | fn unnamed_branch() { 123 | add_leaf!("1"); 124 | add_branch!(); 125 | add_leaf!("1.1"); 126 | { 127 | add_branch!(); 128 | add_leaf!("1.1.1"); 129 | } 130 | add_leaf!("1.2"); 131 | default_tree().peek_print(); 132 | assert_eq!( 133 | "\ 134 | 1 135 | ├╼ 1.1 136 | │ └╼ 1.1.1 137 | └╼ 1.2", 138 | default_tree().string() 139 | ); 140 | } 141 | #[test] 142 | fn named_branch() { 143 | add_branch!("11"); 144 | { 145 | add_branch!("11.1"); 146 | add_leaf!("11.1.1"); 147 | } 148 | add_leaf!("11.2"); 149 | default_tree().peek_print(); 150 | assert_eq!( 151 | "\ 152 | 11 153 | ├╼ 11.1 154 | │ └╼ 11.1.1 155 | └╼ 11.2", 156 | default_tree().string() 157 | ); 158 | } 159 | 160 | #[test] 161 | fn leaf_with_value() { 162 | let value = add_leaf_value!(10); 163 | default_tree().peek_print(); 164 | assert_eq!("10", default_tree().string()); 165 | assert_eq!(10, value); 166 | } 167 | } 168 | -------------------------------------------------------------------------------- /src/tree_config.rs: -------------------------------------------------------------------------------- 1 | use once_cell::sync::Lazy; 2 | use std::sync::{Arc, Mutex}; 3 | 4 | #[derive(Debug, Clone)] 5 | pub struct TreeSymbols { 6 | /// A vertical base of the tree (│) 7 | pub continued: &'static str, 8 | 9 | /// Symbol for joining the first branch in a group (├) 10 | pub join_first: &'static str, 11 | 12 | /// Symbol for joining the last branch in a group (└) 13 | pub join_last: &'static str, 14 | 15 | /// Symbol for joining a branch that is not first or last in its group (├) 16 | pub join_inner: &'static str, 17 | 18 | /// Symbol for joining a branch if it is the only one in its group (├) 19 | pub join_only: &'static str, 20 | 21 | /// A repeated branch token (─) 22 | pub branch: &'static str, 23 | 24 | /// End of a leaf (╼) 25 | pub leaf: &'static str, 26 | 27 | pub multiline_first: Option<&'static str>, 28 | pub multiline_continued: Option<&'static str>, 29 | } 30 | 31 | #[derive(Debug, Clone)] 32 | pub struct TreeConfig { 33 | pub symbols: TreeSymbols, 34 | 35 | /// Aside from the first branch, `indent` is equal to the number of spaces a child branch is 36 | /// shifted from its parent. 37 | pub indent: usize, 38 | 39 | pub show_first_level: bool, 40 | } 41 | impl TreeSymbols { 42 | pub fn new() -> Self { 43 | Self { 44 | continued: "│", 45 | join_first: "├", 46 | join_inner: "├", 47 | join_last: "└", 48 | join_only: "└", 49 | branch: "─", 50 | leaf: "╼ ", 51 | multiline_first: None, 52 | multiline_continued: None, 53 | } 54 | } 55 | pub fn with_pipes() -> Self { 56 | Self { 57 | continued: "║", 58 | join_first: "╠", 59 | join_inner: "╠", 60 | join_last: "╚", 61 | join_only: "╚", 62 | branch: "═", 63 | leaf: "╼ ", 64 | multiline_first: None, 65 | multiline_continued: None, 66 | } 67 | } 68 | pub fn with_thick() -> Self { 69 | Self { 70 | continued: "┃", 71 | join_first: "┣", 72 | join_inner: "┣", 73 | join_last: "┗", 74 | join_only: "┗", 75 | branch: "━", 76 | leaf: "╼ ", 77 | multiline_first: None, 78 | multiline_continued: None, 79 | } 80 | } 81 | pub fn with_rounded() -> Self { 82 | Self { 83 | continued: "│", 84 | join_first: "├", 85 | join_inner: "├", 86 | join_last: "╰", 87 | join_only: "╰", 88 | branch: "─", 89 | leaf: "╼ ", 90 | multiline_first: None, 91 | multiline_continued: None, 92 | } 93 | } 94 | pub fn with_dashed() -> Self { 95 | Self { 96 | continued: "┊", 97 | join_first: "┊", 98 | join_inner: "┊", 99 | join_last: "'", 100 | join_only: "'", 101 | branch: "╌", 102 | leaf: "- ", 103 | multiline_first: None, 104 | multiline_continued: None, 105 | } 106 | } 107 | 108 | pub fn continued(mut self, sym: &'static str) -> Self { 109 | self.continued = sym; 110 | self 111 | } 112 | pub fn join_first(mut self, sym: &'static str) -> Self { 113 | self.join_first = sym; 114 | self 115 | } 116 | pub fn join_inner(mut self, sym: &'static str) -> Self { 117 | self.join_inner = sym; 118 | self 119 | } 120 | pub fn join_last(mut self, sym: &'static str) -> Self { 121 | self.join_last = sym; 122 | self 123 | } 124 | pub fn join_only(mut self, sym: &'static str) -> Self { 125 | self.join_only = sym; 126 | self 127 | } 128 | 129 | pub fn branch(mut self, sym: &'static str) -> Self { 130 | self.branch = sym; 131 | self 132 | } 133 | pub fn leaf(mut self, sym: &'static str) -> Self { 134 | self.leaf = sym; 135 | self 136 | } 137 | pub fn multiline_first(mut self, sym: &'static str) -> Self { 138 | self.multiline_first = Some(sym); 139 | self 140 | } 141 | pub fn multiline_continued(mut self, sym: &'static str) -> Self { 142 | self.multiline_continued = Some(sym); 143 | self 144 | } 145 | } 146 | 147 | impl TreeConfig { 148 | pub fn new() -> Self { 149 | Self { 150 | symbols: TreeSymbols::new(), 151 | indent: 2, 152 | show_first_level: false, 153 | } 154 | } 155 | pub fn with_symbols(symbols: TreeSymbols) -> Self { 156 | Self { 157 | symbols, 158 | indent: 2, 159 | show_first_level: false, 160 | } 161 | } 162 | pub fn indent(mut self, x: usize) -> Self { 163 | self.indent = x; 164 | self 165 | } 166 | pub fn show_first_level(mut self) -> Self { 167 | self.show_first_level = true; 168 | self 169 | } 170 | pub fn hide_first_level(mut self) -> Self { 171 | self.show_first_level = false; 172 | self 173 | } 174 | pub fn symbols(mut self, x: TreeSymbols) -> Self { 175 | self.symbols = x; 176 | self 177 | } 178 | } 179 | 180 | impl Default for TreeSymbols { 181 | fn default() -> Self { 182 | tree_config_symbols() 183 | } 184 | } 185 | impl Default for TreeConfig { 186 | fn default() -> Self { 187 | tree_config() 188 | } 189 | } 190 | 191 | static DEFAULT_CONFIG: Lazy>> = 192 | Lazy::new(|| -> Arc> { Arc::new(Mutex::new(TreeConfig::new())) }); 193 | 194 | /// Set the default tree config 195 | pub fn set_tree_config(x: TreeConfig) { 196 | *DEFAULT_CONFIG.lock().unwrap() = x; 197 | } 198 | 199 | /// The default tree config 200 | pub fn tree_config() -> TreeConfig { 201 | DEFAULT_CONFIG.lock().unwrap().clone() 202 | } 203 | 204 | /// Set the default tree symbols config 205 | pub fn set_tree_config_symbols(x: TreeSymbols) { 206 | DEFAULT_CONFIG.lock().unwrap().symbols = x; 207 | } 208 | 209 | /// The default tree symbols config 210 | pub fn tree_config_symbols() -> TreeSymbols { 211 | DEFAULT_CONFIG.lock().unwrap().symbols.clone() 212 | } 213 | 214 | /// The default tree symbols config 215 | pub fn update_tree_config(mut update: F) { 216 | let mut x = DEFAULT_CONFIG.lock().unwrap(); 217 | update(&mut x); 218 | } 219 | 220 | /// The default tree symbols config 221 | pub fn update_tree_config_symbols(mut update: F) { 222 | let mut x = DEFAULT_CONFIG.lock().unwrap(); 223 | update(&mut x.symbols); 224 | } 225 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Debug Tree 2 | 3 | This library allows you to build a tree one element at a time and output it as a pretty string. 4 | 5 | The tree can easily be output to a `String`, `stdout` or a file. 6 | 7 | This is particularly convenient for generating clean output from nested and recursive functions. 8 | 9 | * [Recursive Fibonacci Example](#recursive-fibonacci-example) 10 | * [Overview](#overview) 11 | * [More Examples](#more-examples) 12 | * [Multiple Tagged Trees](#multiple-tagged-trees) 13 | * [Nested Functions](#nested-functions) 14 | * [Panic](#panics) 15 | * [Without Macros](#without-macros) 16 | 17 | 18 | ## Recursive Fibonacci Example 19 | 20 | Using the `add_branch!()` macro at the start of the `factors()` function, you can generate an entire call tree, with minimal effort. 21 | 22 | 23 | ```rust 24 | use debug_tree::*; 25 | 26 | fn factors(x: usize) { 27 | add_branch!("{}", x); // <~ THE MAGIC LINE 28 | for i in 1..x { 29 | if x % i == 0 { 30 | factors(i); 31 | } 32 | } 33 | } 34 | 35 | fn main() { 36 | // output to file at the end of this block 37 | defer_write!("examples/out/fibonacci.txt"); 38 | add_branch!("A Fibonacci Tree"); 39 | factors(6); 40 | add_leaf!("That's All Folks!"); 41 | } 42 | ``` 43 | 44 | 45 | 46 | ``` 47 | A Fibonacci Tree 48 | ├╼ 6 49 | │ ├╼ 1 50 | │ ├╼ 2 51 | │ │ └╼ 1 52 | │ └╼ 3 53 | │ └╼ 1 54 | └╼ That's All Folks! 55 | ``` 56 | 57 | 58 | ## Overview 59 | 60 | - Add a branch 61 | - `add_branch!("Hello, {}", "World")` 62 | - The branch will exit at the end of the current block 63 | 64 | - Add a leaf 65 | - `add_leaf!("I am a {}", "leaf")` 66 | - Added to the current scoped branch 67 | 68 | - Print a tree, or write it to file at the end of a block 69 | - `defer_print!()` 70 | - `defer_write!("filename.txt")` 71 | - The tree will be empty after these calls 72 | - To prevent clearing, use `defer_peek_print!` and `defer_peek_write!` 73 | 74 | 75 | - Handle multiple trees using named trees 76 | - `add_branch_to!("A", "I'm a branch on tree 'A'")` 77 | - `add_leaf_to!("A", "I'm a leaf on tree 'A'")` 78 | - `defer_print!("A")` 79 | - `defer_write!("A", "filename.txt")` 80 | 81 | - Get a named tree 82 | - `tree("TREE_NAME")` 83 | 84 | - Retrieve the pretty-string from a tree 85 | - `tree("TREE_NAME").string()` 86 | 87 | 88 | - Usage across threads 89 | - `default_tree()` is local to each thread 90 | - Named trees are shared between threads 91 | 92 | ## More Examples 93 | 94 | ### Multiple Tagged Trees 95 | 96 | If you need multiple, separated trees you can use a name tag. 97 | 98 | 99 | ```rust 100 | use debug_tree::*; 101 | 102 | fn populate(tree_name: &str, n_children: usize) { 103 | add_branch_to!(tree_name, "{} TREE", tree_name); 104 | for _ in 0..n_children { 105 | populate(tree_name, n_children / 2); 106 | } 107 | } 108 | fn main() { 109 | // Override tree config (just for "B") 110 | let b_tree = tree("B"); 111 | b_tree.set_config_override( 112 | TreeConfig::new() 113 | .indent(4) 114 | .symbols(TreeSymbols::with_rounded().leaf("> ")), 115 | ); 116 | defer_write!(b_tree, "examples/out/multiple_trees_B.txt"); 117 | defer_write!("A", "examples/out/multiple_trees_A.txt"); 118 | 119 | populate("A", 2); 120 | populate("B", 3); 121 | } 122 | ``` 123 | 124 | 125 | ``` 126 | A TREE 127 | ├╼ A TREE 128 | │ └╼ A TREE 129 | └╼ A TREE 130 | └╼ A TREE 131 | ``` 132 | 133 | 134 | ``` 135 | B TREE 136 | ├──> B TREE 137 | │ ╰──> B TREE 138 | ├──> B TREE 139 | │ ╰──> B TREE 140 | ╰──> B TREE 141 | ╰──> B TREE 142 | ``` 143 | 144 | 145 | ### Nested Functions 146 | 147 | Branches also make nested function calls a lot easier to follow. 148 | 149 | 150 | ```rust 151 | use debug_tree::*; 152 | fn a() { 153 | add_branch!("a"); 154 | b(); 155 | c(); 156 | } 157 | fn b() { 158 | add_branch!("b"); 159 | c(); 160 | } 161 | fn c() { 162 | add_branch!("c"); 163 | add_leaf!("Nothing to see here"); 164 | } 165 | 166 | fn main() { 167 | defer_write!("examples/out/nested.txt"); 168 | a(); 169 | } 170 | ``` 171 | 172 | 173 | ``` 174 | a 175 | ├╼ b 176 | │ └╼ c 177 | │ └╼ Nothing to see here 178 | └╼ c 179 | └╼ Nothing to see here 180 | ``` 181 | 182 | 183 | ### Line Breaks 184 | 185 | Newlines in multi-line strings are automatically indented. 186 | 187 | 188 | ```rust 189 | use debug_tree::*; 190 | fn main() { 191 | // output to file at the end of this block 192 | defer_write!("examples/out/multi_line.txt"); 193 | add_branch!("1"); 194 | add_leaf!("1.1\nAnother line...\n... and one more line"); 195 | add_leaf!("1.2"); 196 | } 197 | ``` 198 | 199 | 200 | 201 | ``` 202 | 1 203 | ├╼ 1.1 204 | │ Another line... 205 | │ ... and one more line 206 | └╼ 1.2 207 | ``` 208 | 209 | 210 | ### Panics 211 | Even if there is a panic, the tree is not lost! 212 | The `defer_` functions were introduced to allow the tree 213 | to be printed our written to file in the case of a `panic!` or early return. 214 | 215 | 216 | ```rust 217 | use debug_tree::*; 218 | 219 | fn i_will_panic() { 220 | add_branch!("Here are my last words"); 221 | add_leaf!("Stay calm, and try not to panic"); 222 | panic!("I told you so...") 223 | } 224 | 225 | fn main() { 226 | // output to file at the end of this block 227 | defer_write!("examples/out/panic.txt"); 228 | // print at the end of this block 229 | { 230 | add_branch!("By using the 'defer_' functions"); 231 | add_branch!("Output will still be generated"); 232 | add_branch!("Otherwise you might lose your valuable tree!"); 233 | } 234 | add_branch!("Now for something crazy..."); 235 | i_will_panic(); 236 | } 237 | ``` 238 | 239 | 240 | 241 | ``` 242 | By using the 'defer_' functions 243 | └╼ Output will still be generated 244 | └╼ Otherwise you might lose your valuable tree! 245 | Now for something crazy... 246 | └╼ Here are my last words 247 | └╼ Stay calm, and try not to panic 248 | ``` 249 | 250 | 251 | 252 | ### Without Macros 253 | 254 | If you prefer not using macros, you can construct `TreeBuilder`s manually. 255 | 256 | 257 | ```rust 258 | use debug_tree::TreeBuilder; 259 | 260 | fn main() { 261 | // Make a new tree. 262 | let tree = TreeBuilder::new(); 263 | 264 | // Add a scoped branch. The next item added will belong to the branch. 265 | let mut branch = tree.add_branch("1 Branch"); 266 | 267 | // Add a leaf to the current branch 268 | tree.add_leaf("1.1 Child"); 269 | 270 | // Leave scope early 271 | branch.release(); 272 | tree.add_leaf("2 Sibling"); 273 | // output to file 274 | tree.write("examples/out/no_macros.txt").ok(); // Write and flush. 275 | } 276 | ``` 277 | 278 | 279 | ``` 280 | 1 Branch 281 | └╼ 1.1 Child 282 | 2 Sibling 283 | ``` 284 | -------------------------------------------------------------------------------- /src/internal.rs: -------------------------------------------------------------------------------- 1 | use crate::tree_config::{tree_config, TreeConfig}; 2 | use std::cmp::max; 3 | use std::sync::{Arc, Mutex}; 4 | 5 | /// Tree that holds `text` for the current leaf and a list of `children` that are the branches. 6 | #[derive(Debug)] 7 | pub struct Tree { 8 | pub text: Option, 9 | pub children: Vec, 10 | } 11 | 12 | /// Position of the element relative to its siblings 13 | #[derive(Copy, Clone, Debug, Eq, PartialEq)] 14 | pub enum Position { 15 | Inside, 16 | First, 17 | Last, 18 | Only, 19 | } 20 | 21 | impl Tree { 22 | /// Create a new tree with some optional text. 23 | pub fn new(text: Option<&str>) -> Tree { 24 | Tree { 25 | text: text.map(|x| x.to_string()), 26 | children: Vec::new(), 27 | } 28 | } 29 | 30 | /// Navigate to the branch at the given `path` relative to this tree. 31 | /// If a valid branch is found by following the path, it is returned. 32 | pub fn at_mut(&mut self, path: &[usize]) -> Option<&mut Tree> { 33 | match path.first() { 34 | Some(&i) => match self.children.get_mut(i) { 35 | Some(x) => x.at_mut(&path[1..]), 36 | _ => None, 37 | }, 38 | _ => Some(self), 39 | } 40 | } 41 | 42 | /// "Render" this tree as a list of `String`s. 43 | /// Each string represents a line in the tree. 44 | /// `does_continue` is a bool for each column indicating whether the tree continues. 45 | pub fn lines( 46 | &self, 47 | does_continue: &Vec, 48 | index: usize, 49 | pool_size: usize, 50 | config: &TreeConfig, 51 | ) -> Vec { 52 | let does_continue = if config.show_first_level && does_continue.is_empty() { 53 | vec![true] 54 | } else { 55 | does_continue.clone() 56 | }; 57 | let position = match index { 58 | _ if pool_size == 1 => Position::Only, 59 | _ if (index + 1) == pool_size => Position::Last, 60 | 0 => Position::First, 61 | _ => Position::Inside, 62 | }; 63 | let mut next_continue = does_continue.clone(); 64 | next_continue.push(match position { 65 | Position::Inside | Position::First => true, 66 | Position::Last | Position::Only => false, 67 | }); 68 | 69 | let mut txt = String::new(); 70 | let pad: String; 71 | if does_continue.len() > 1 { 72 | for &i in &does_continue[2..] { 73 | txt.push_str(&format!( 74 | "{}{:indent$}", 75 | if i { config.symbols.continued } else { " " }, 76 | "", 77 | indent = max(config.indent, 1) - 1 78 | )); 79 | } 80 | pad = txt.clone(); 81 | let branch_size = max(config.indent, 2usize) - 2; 82 | let branch = match config.symbols.branch.len() { 83 | 0 => "-".repeat(branch_size), 84 | 1 => config.symbols.branch.repeat(branch_size), 85 | _n => config 86 | .symbols 87 | .branch 88 | .repeat(branch_size) 89 | .chars() 90 | .take(branch_size) 91 | .collect::(), 92 | }; 93 | 94 | let is_multiline = self 95 | .text 96 | .as_ref() 97 | .map(|x| x.contains("\n")) 98 | .unwrap_or(false); 99 | 100 | let first_leaf = match (is_multiline, config.symbols.multiline_first) { 101 | (true, Some(x)) => x, 102 | _ => config.symbols.leaf, 103 | }; 104 | txt.push_str(&format!( 105 | "{}{}{}", 106 | match position { 107 | Position::Only => config.symbols.join_only, 108 | Position::First => config.symbols.join_first, 109 | Position::Last => config.symbols.join_last, 110 | Position::Inside => config.symbols.join_inner, 111 | }, 112 | branch, 113 | first_leaf, 114 | )); 115 | 116 | let s = match &self.text { 117 | Some(x) => match is_multiline { 118 | true => format!( 119 | "{}", 120 | x.replace( 121 | "\n", 122 | &format!( 123 | "\n{}{}{}{}", 124 | &pad, 125 | match position { 126 | Position::Only | Position::Last => 127 | " ".repeat(config.symbols.continued.chars().count()), 128 | _ => config.symbols.continued.to_string(), 129 | }, 130 | " ".repeat(branch_size), 131 | match &config.symbols.multiline_continued { 132 | Some(multi) => multi.to_string(), 133 | _ => " ".repeat(first_leaf.chars().count()), 134 | } 135 | ), 136 | ) 137 | ), 138 | false => x.clone(), 139 | }, 140 | _ => String::new(), 141 | }; 142 | txt.push_str(&s); 143 | } else { 144 | if let Some(x) = &self.text { 145 | txt.push_str(&x); 146 | } 147 | } 148 | let mut ret = vec![txt]; 149 | for (index, x) in self.children.iter().enumerate() { 150 | for line in x.lines(&next_continue, index, self.children.len(), config) { 151 | ret.push(line); 152 | } 153 | } 154 | ret 155 | } 156 | } 157 | 158 | /// Holds the current state of the tree, including the path to the branch. 159 | /// Multiple trees may point to the same data. 160 | #[derive(Debug, Clone)] 161 | pub(crate) struct TreeBuilderBase { 162 | data: Arc>, 163 | path: Vec, 164 | dive_count: usize, 165 | config: Option, 166 | is_enabled: bool, 167 | } 168 | 169 | impl TreeBuilderBase { 170 | /// Create a new state 171 | pub fn new() -> TreeBuilderBase { 172 | TreeBuilderBase { 173 | data: Arc::new(Mutex::new(Tree::new(None))), 174 | path: vec![], 175 | dive_count: 1, 176 | config: None, 177 | is_enabled: true, 178 | } 179 | } 180 | 181 | pub fn set_enabled(&mut self, enabled: bool) { 182 | self.is_enabled = enabled; 183 | } 184 | pub fn is_enabled(&self) -> bool { 185 | self.is_enabled 186 | } 187 | 188 | pub fn add_leaf(&mut self, text: &str) { 189 | let &dive_count = &self.dive_count; 190 | if dive_count > 0 { 191 | for i in 0..dive_count { 192 | let mut n = 0; 193 | if let Some(x) = self.data.lock().unwrap().at_mut(&self.path) { 194 | x.children.push(Tree::new(if i == max(1, dive_count) - 1 { 195 | Some(&text) 196 | } else { 197 | None 198 | })); 199 | n = x.children.len() - 1; 200 | } 201 | self.path.push(n); 202 | } 203 | self.dive_count = 0; 204 | } else { 205 | if let Some(x) = self 206 | .data 207 | .lock() 208 | .unwrap() 209 | .at_mut(&self.path[..max(1, self.path.len()) - 1]) 210 | { 211 | x.children.push(Tree::new(Some(&text))); 212 | let n = match self.path.last() { 213 | Some(&x) => x + 1, 214 | _ => 0, 215 | }; 216 | self.path.last_mut().map(|x| *x = n); 217 | } 218 | } 219 | } 220 | 221 | pub fn set_config_override(&mut self, config: Option) { 222 | self.config = config; 223 | } 224 | 225 | pub fn config_override(&self) -> &Option { 226 | &self.config 227 | } 228 | pub fn config_override_mut(&mut self) -> &mut Option { 229 | &mut self.config 230 | } 231 | 232 | pub fn enter(&mut self) { 233 | self.dive_count += 1; 234 | } 235 | 236 | /// Try stepping up to the parent tree branch. 237 | /// Returns false if already at the top branch. 238 | pub fn exit(&mut self) -> bool { 239 | if self.dive_count > 0 { 240 | self.dive_count -= 1; 241 | true 242 | } else { 243 | if self.path.len() > 1 { 244 | self.path.pop(); 245 | true 246 | } else { 247 | false 248 | } 249 | } 250 | } 251 | 252 | pub fn depth(&self) -> usize { 253 | max(1, self.path.len() + self.dive_count) - 1 254 | } 255 | 256 | pub fn peek_print(&self) { 257 | println!("{}", self.peek_string()); 258 | } 259 | 260 | pub fn print(&mut self) { 261 | self.peek_print(); 262 | self.clear(); 263 | } 264 | pub fn clear(&mut self) { 265 | *self = Self::new(); 266 | } 267 | 268 | pub fn string(&mut self) -> String { 269 | let s = self.peek_string(); 270 | self.clear(); 271 | s 272 | } 273 | 274 | pub fn peek_string(&self) -> String { 275 | let config = self 276 | .config_override() 277 | .clone() 278 | .unwrap_or_else(|| tree_config().clone()); 279 | (&self.data.lock().unwrap().lines(&vec![], 0, 1, &config)[1..]).join("\n") 280 | } 281 | } 282 | -------------------------------------------------------------------------------- /src/test.rs: -------------------------------------------------------------------------------- 1 | #[cfg(test)] 2 | mod test { 3 | use crate::*; 4 | use futures::future::join5; 5 | use std::cmp::{max, min}; 6 | use std::fs::{create_dir, read_to_string, remove_file}; 7 | 8 | #[test] 9 | fn test_branch() { 10 | let d: TreeBuilder = TreeBuilder::new(); 11 | d.add_leaf("1"); 12 | { 13 | let _l = d.enter_scoped(); 14 | d.add_leaf("1.1"); 15 | d.add_leaf("1.2"); 16 | } 17 | d.add_leaf("2"); 18 | d.add_leaf("3"); 19 | let _l = d.enter_scoped(); 20 | d.add_leaf("3.1"); 21 | d.add_leaf("3.2"); 22 | d.peek_print(); 23 | assert_eq!( 24 | "\ 25 | 1 26 | ├╼ 1.1 27 | └╼ 1.2 28 | 2 29 | 3 30 | ├╼ 3.1 31 | └╼ 3.2", 32 | d.string() 33 | ); 34 | } 35 | 36 | #[test] 37 | fn test_branch2() { 38 | let d = TreeBuilder::new(); 39 | d.add_leaf("1"); 40 | { 41 | let _scope = d.enter_scoped(); 42 | d.add_leaf("1.1"); 43 | { 44 | let _scope = d.enter_scoped(); 45 | d.add_leaf("1.1.1"); 46 | } 47 | } 48 | 49 | d.add_leaf("2"); 50 | d.enter(); 51 | d.add_leaf("2.1"); 52 | d.enter(); 53 | d.add_leaf("2.1.1"); 54 | d.peek_print(); 55 | assert_eq!( 56 | "\ 57 | 1 58 | └╼ 1.1 59 | └╼ 1.1.1 60 | 2 61 | └╼ 2.1 62 | └╼ 2.1.1", 63 | d.string() 64 | ); 65 | } 66 | 67 | #[test] 68 | fn simple() { 69 | let d = TreeBuilder::new(); 70 | d.add_leaf("Hi"); 71 | assert_eq!("Hi", d.string()); 72 | } 73 | 74 | #[test] 75 | fn depth() { 76 | let d = TreeBuilder::new(); 77 | assert_eq!(0, d.depth()); 78 | d.add_leaf("Hi"); 79 | assert_eq!(0, d.depth()); 80 | let _b = d.add_branch("Hi"); 81 | assert_eq!(1, d.depth()); 82 | d.add_leaf("Hi"); 83 | assert_eq!(1, d.depth()); 84 | } 85 | 86 | #[test] 87 | fn indent() { 88 | let d = TreeBuilder::new(); 89 | d.add_leaf("1"); 90 | add_branch_to!(d); 91 | d.add_leaf("1.1"); 92 | { 93 | add_branch_to!(d); 94 | d.add_leaf("1.1.1"); 95 | } 96 | d.set_config_override(TreeConfig::new().indent(4)); 97 | d.peek_print(); 98 | assert_eq!( 99 | "\ 100 | 1 101 | └──╼ 1.1 102 | └──╼ 1.1.1", 103 | d.string() 104 | ); 105 | } 106 | 107 | #[test] 108 | fn macros() { 109 | let d = TreeBuilder::new(); 110 | add_leaf_to!(d, "1"); 111 | { 112 | add_branch_to!(d); 113 | add_leaf_to!(d, "1.1") 114 | } 115 | d.peek_print(); 116 | assert_eq!( 117 | "\ 118 | 1 119 | └╼ 1.1", 120 | d.string() 121 | ); 122 | } 123 | 124 | #[test] 125 | fn macros_with_fn() { 126 | let d = TreeBuilder::new(); 127 | let tree = || d.clone(); 128 | add_leaf_to!(tree(), "1"); 129 | { 130 | add_branch_to!(tree()); 131 | add_leaf_to!(tree(), "1.1") 132 | } 133 | tree().peek_print(); 134 | assert_eq!( 135 | "\ 136 | 1 137 | └╼ 1.1", 138 | d.string() 139 | ); 140 | } 141 | 142 | #[test] 143 | fn leaf_with_value() { 144 | let d = TreeBuilder::new(); 145 | let value = add_leaf_value_to!(d, 1); 146 | d.peek_print(); 147 | assert_eq!("1", d.string()); 148 | assert_eq!(1, value); 149 | } 150 | 151 | #[test] 152 | fn macros2() { 153 | let d = TreeBuilder::new(); 154 | add_branch_to!(d, "1"); 155 | add_leaf_to!(d, "1.1"); 156 | d.peek_print(); 157 | assert_eq!( 158 | "\ 159 | 1 160 | └╼ 1.1", 161 | d.string() 162 | ); 163 | } 164 | 165 | #[test] 166 | fn mid() { 167 | let d = TreeBuilder::new(); 168 | d.add_leaf(&format!("{}{}", "1", "0")); 169 | d.enter(); 170 | d.add_leaf("10.1"); 171 | d.add_leaf("10.2"); 172 | d.enter(); 173 | d.add_leaf("10.1.1"); 174 | d.add_leaf("10.1.2\nNext line"); 175 | d.exit(); 176 | d.add_leaf(&format!("10.3")); 177 | d.peek_print(); 178 | assert_eq!( 179 | "\ 180 | 10 181 | ├╼ 10.1 182 | ├╼ 10.2 183 | │ ├╼ 10.1.1 184 | │ └╼ 10.1.2 185 | │ Next line 186 | └╼ 10.3", 187 | d.string() 188 | ); 189 | } 190 | 191 | fn factors(x: usize) { 192 | add_branch!("{}", x); 193 | for i in 1..x { 194 | if x % i == 0 { 195 | factors(i); 196 | } 197 | } 198 | } 199 | 200 | #[test] 201 | fn recursive() { 202 | factors(6); 203 | default_tree().peek_print(); 204 | assert_eq!( 205 | "\ 206 | 6 207 | ├╼ 1 208 | ├╼ 2 209 | │ └╼ 1 210 | └╼ 3 211 | └╼ 1", 212 | default_tree().string() 213 | ); 214 | } 215 | 216 | fn a() { 217 | add_branch!("a"); 218 | b(); 219 | c(); 220 | } 221 | 222 | fn b() { 223 | add_branch!("b"); 224 | c(); 225 | } 226 | 227 | fn c() { 228 | add_branch!("c"); 229 | add_leaf!("Nothing to see here"); 230 | } 231 | 232 | #[test] 233 | fn nested() { 234 | a(); 235 | default_tree().peek_print(); 236 | assert_eq!( 237 | "\ 238 | a 239 | ├╼ b 240 | │ └╼ c 241 | │ └╼ Nothing to see here 242 | └╼ c 243 | └╼ Nothing to see here", 244 | default_tree().string() 245 | ); 246 | } 247 | 248 | #[test] 249 | fn disabled_output() { 250 | let tree = TreeBuilder::new(); 251 | tree.set_enabled(false); 252 | add_leaf_to!(tree, "Leaf"); 253 | tree.add_leaf("Leaf"); 254 | 255 | add_branch_to!(tree, "Branch"); 256 | tree.add_branch("Branch"); 257 | assert_eq!("", tree.string()); 258 | } 259 | 260 | #[test] 261 | fn enabled_output() { 262 | let tree = TreeBuilder::new(); 263 | tree.set_enabled(false); 264 | add_branch_to!(tree, "Ignored branch"); 265 | add_leaf_to!(tree, "Ignored leaf"); 266 | tree.set_enabled(true); 267 | add_leaf_to!(tree, "Leaf"); 268 | tree.add_leaf("Leaf"); 269 | 270 | add_branch_to!(tree, "Branch"); 271 | tree.add_branch("Branch"); 272 | assert_eq!( 273 | "Leaf 274 | Leaf 275 | Branch 276 | └╼ Branch", 277 | tree.string() 278 | ); 279 | } 280 | 281 | #[test] 282 | fn tree_by_name() { 283 | clear("A"); 284 | let b = tree("B"); 285 | b.clear(); 286 | { 287 | add_branch_to!("A", "1"); 288 | add_branch_to!(b, "3"); 289 | add_leaf_to!("A", "1.1"); 290 | add_leaf_to!("B", "3.1"); 291 | } 292 | add_leaf_to!("A", "2"); 293 | peek_print("A"); 294 | b.peek_print(); 295 | assert_eq!( 296 | "1 297 | └╼ 1.1 298 | 2", 299 | string("A") 300 | ); 301 | assert_eq!( 302 | "3 303 | └╼ 3.1", 304 | b.string() 305 | ); 306 | } 307 | 308 | #[test] 309 | fn tree_by_name_disabled() { 310 | let d = tree("D"); 311 | d.clear(); 312 | d.set_enabled(true); 313 | clear("C"); 314 | set_enabled("C", false); 315 | { 316 | add_branch_to!("C", "1"); 317 | set_enabled("C", true); 318 | add_branch_to!(d, "3"); 319 | add_leaf_to!("C", "1.1"); 320 | d.set_enabled(false); 321 | add_leaf_to!("D", "3.1"); 322 | } 323 | add_leaf_to!("C", "2"); 324 | peek_print("C"); 325 | d.peek_print(); 326 | assert_eq!( 327 | "1.1 328 | 2", 329 | string("C") 330 | ); 331 | assert_eq!("3", d.string()); 332 | } 333 | 334 | #[test] 335 | fn defer_write() { 336 | let tree = TreeBuilder::new(); 337 | { 338 | create_dir("test_out").ok(); 339 | remove_file("test_out/defer_write.txt").ok(); 340 | File::create("test_out/defer_write.txt").unwrap(); 341 | defer_write!(tree, "test_out/defer_write.txt"); 342 | tree.add_leaf("Branch"); 343 | assert_eq!(read_to_string("test_out/defer_write.txt").unwrap(), ""); 344 | assert_eq!(tree.peek_string(), "Branch"); 345 | } 346 | assert_eq!(tree.peek_string(), ""); 347 | assert_eq!( 348 | read_to_string("test_out/defer_write.txt").unwrap(), 349 | "Branch" 350 | ); 351 | } 352 | 353 | #[test] 354 | fn defer_peek_write() { 355 | let tree = TreeBuilder::new(); 356 | { 357 | create_dir("test_out").ok(); 358 | remove_file("test_out/defer_peek_write.txt").ok(); 359 | File::create("test_out/defer_peek_write.txt").unwrap(); 360 | defer_peek_write!(tree, "test_out/defer_peek_write.txt"); 361 | tree.add_leaf("Branch"); 362 | assert_eq!(read_to_string("test_out/defer_peek_write.txt").unwrap(), ""); 363 | assert_eq!(tree.peek_string(), "Branch"); 364 | } 365 | assert_eq!(tree.peek_string(), "Branch"); 366 | assert_eq!( 367 | read_to_string("test_out/defer_peek_write.txt").unwrap(), 368 | "Branch" 369 | ); 370 | } 371 | 372 | #[test] 373 | #[should_panic] 374 | #[allow(unreachable_code)] 375 | fn defer_peek_write_panic() { 376 | let tree = TreeBuilder::new(); 377 | { 378 | create_dir("test_out").ok(); 379 | remove_file("test_out/defer_peek_write_panic.txt").ok(); 380 | File::create("test_out/defer_peek_write_panic.txt").unwrap(); 381 | defer_peek_write!(tree, "test_out/defer_peek_write_panic.txt"); 382 | tree.add_leaf("This should be the only line in this file"); 383 | assert_eq!(read_to_string("test_out/defer_peek_write.txt").unwrap(), ""); 384 | assert_eq!( 385 | tree.peek_string(), 386 | "This should be the only line in this file" 387 | ); 388 | panic!(); 389 | tree.add_leaf("This line should not exist"); 390 | } 391 | } 392 | 393 | fn example_tree() -> TreeBuilder { 394 | let tree = TreeBuilder::new(); 395 | { 396 | add_branch_to!(tree, "1"); 397 | { 398 | add_branch_to!(tree, "1.1"); 399 | add_leaf_to!(tree, "1.1.1"); 400 | add_leaf_to!(tree, "1.1.2\nWith two\nextra lines"); 401 | add_leaf_to!(tree, "1.1.3"); 402 | } 403 | add_branch_to!(tree, "1.2"); 404 | add_leaf_to!(tree, "1.2.1"); 405 | } 406 | { 407 | add_branch_to!(tree, "2"); 408 | add_leaf_to!(tree, "2.1"); 409 | add_leaf_to!(tree, "2.2"); 410 | } 411 | add_leaf_to!(tree, "3"); 412 | tree 413 | } 414 | 415 | #[test] 416 | fn format_output() { 417 | let tree = example_tree(); 418 | tree.set_config_override( 419 | TreeConfig::new() 420 | .indent(8) 421 | .symbols(TreeSymbols { 422 | continued: "| |", 423 | join_first: "|A|", 424 | join_last: "|Z|", 425 | join_inner: "|N|", 426 | join_only: "|O|", 427 | branch: "123456[NOT SHOWN]", 428 | leaf: ")}>", 429 | multiline_first: Some(")}MULTI>"), 430 | multiline_continued: Some(".. CONTINUED: "), 431 | }) 432 | .show_first_level(), 433 | ); 434 | tree.peek_print(); 435 | assert_eq!( 436 | tree.string(), 437 | "\ 438 | |A|123456)}>1 439 | | | |A|123456)}>1.1 440 | | | | | |A|123456)}>1.1.1 441 | | | | | |N|123456)}MULTI>1.1.2 442 | | | | | | | .. CONTINUED: With two 443 | | | | | | | .. CONTINUED: extra lines 444 | | | | | |Z|123456)}>1.1.3 445 | | | |Z|123456)}>1.2 446 | | | |O|123456)}>1.2.1 447 | |N|123456)}>2 448 | | | |A|123456)}>2.1 449 | | | |Z|123456)}>2.2 450 | |Z|123456)}>3" 451 | ); 452 | } 453 | 454 | #[test] 455 | fn format_output_thick() { 456 | let tree = example_tree(); 457 | tree.set_config_override( 458 | TreeConfig::new() 459 | .symbols(TreeSymbols::with_thick()) 460 | .indent(4) 461 | .show_first_level(), 462 | ); 463 | tree.peek_print(); 464 | assert_eq!( 465 | tree.string(), 466 | "\ 467 | ┣━━╼ 1 468 | ┃ ┣━━╼ 1.1 469 | ┃ ┃ ┣━━╼ 1.1.1 470 | ┃ ┃ ┣━━╼ 1.1.2 471 | ┃ ┃ ┃ With two 472 | ┃ ┃ ┃ extra lines 473 | ┃ ┃ ┗━━╼ 1.1.3 474 | ┃ ┗━━╼ 1.2 475 | ┃ ┗━━╼ 1.2.1 476 | ┣━━╼ 2 477 | ┃ ┣━━╼ 2.1 478 | ┃ ┗━━╼ 2.2 479 | ┗━━╼ 3" 480 | ); 481 | } 482 | 483 | #[test] 484 | fn format_output_pipes() { 485 | let tree = example_tree(); 486 | tree.set_config_override( 487 | TreeConfig::new() 488 | .symbols(TreeSymbols::with_pipes()) 489 | .indent(3) 490 | .show_first_level(), 491 | ); 492 | tree.peek_print(); 493 | assert_eq!( 494 | tree.string(), 495 | "\ 496 | ╠═╼ 1 497 | ║ ╠═╼ 1.1 498 | ║ ║ ╠═╼ 1.1.1 499 | ║ ║ ╠═╼ 1.1.2 500 | ║ ║ ║ With two 501 | ║ ║ ║ extra lines 502 | ║ ║ ╚═╼ 1.1.3 503 | ║ ╚═╼ 1.2 504 | ║ ╚═╼ 1.2.1 505 | ╠═╼ 2 506 | ║ ╠═╼ 2.1 507 | ║ ╚═╼ 2.2 508 | ╚═╼ 3" 509 | ); 510 | } 511 | 512 | #[test] 513 | fn format_output_dashed() { 514 | let tree = example_tree(); 515 | tree.set_config_override( 516 | TreeConfig::new() 517 | .symbols(TreeSymbols::with_dashed().multiline_continued(" > ")) 518 | .indent(4) 519 | .show_first_level(), 520 | ); 521 | tree.peek_print(); 522 | assert_eq!( 523 | tree.string(), 524 | "\ 525 | ┊╌╌- 1 526 | ┊ ┊╌╌- 1.1 527 | ┊ ┊ ┊╌╌- 1.1.1 528 | ┊ ┊ ┊╌╌- 1.1.2 529 | ┊ ┊ ┊ > With two 530 | ┊ ┊ ┊ > extra lines 531 | ┊ ┊ '╌╌- 1.1.3 532 | ┊ '╌╌- 1.2 533 | ┊ '╌╌- 1.2.1 534 | ┊╌╌- 2 535 | ┊ ┊╌╌- 2.1 536 | ┊ '╌╌- 2.2 537 | '╌╌- 3" 538 | ); 539 | } 540 | 541 | #[test] 542 | fn format_output_rounded() { 543 | let tree = example_tree(); 544 | tree.set_config_override( 545 | TreeConfig::new() 546 | .symbols(TreeSymbols::with_rounded()) 547 | .indent(4), 548 | ); 549 | tree.peek_print(); 550 | assert_eq!( 551 | tree.string(), 552 | "\ 553 | 1 554 | ├──╼ 1.1 555 | │ ├──╼ 1.1.1 556 | │ ├──╼ 1.1.2 557 | │ │ With two 558 | │ │ extra lines 559 | │ ╰──╼ 1.1.3 560 | ╰──╼ 1.2 561 | ╰──╼ 1.2.1 562 | 2 563 | ├──╼ 2.1 564 | ╰──╼ 2.2 565 | 3" 566 | ); 567 | } 568 | 569 | async fn wait_a_bit(tree: TreeBuilder, index: usize) { 570 | tree.print(); 571 | add_branch_to!(tree, "inside async branch {}", index); 572 | tree.print(); 573 | add_leaf_to!(tree, "inside async leaf {}", index); 574 | tree.print(); 575 | } 576 | 577 | #[tokio::test] 578 | async fn async_barrier() { 579 | let tree = TreeBuilder::new(); 580 | defer_peek_print!(tree); 581 | add_branch_to!(tree, "root"); 582 | add_leaf_to!(tree, "before async"); 583 | 584 | let x2 = wait_a_bit(tree.clone(), 4); 585 | let x1 = wait_a_bit(tree.clone(), 5); 586 | let x3 = wait_a_bit(tree.clone(), 3); 587 | let x4 = wait_a_bit(tree.clone(), 2); 588 | let x5 = wait_a_bit(tree.clone(), 1); 589 | 590 | add_leaf_to!(tree, "before join async"); 591 | 592 | join5(x1, x2, x3, x4, x5).await; 593 | add_leaf_to!(tree, "after join async"); 594 | assert_eq!(tree.peek_string(), "after join async"); 595 | } 596 | } 597 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | use std::sync::{Arc, Mutex}; 2 | 3 | #[macro_use] 4 | pub mod default; 5 | mod internal; 6 | pub mod scoped_branch; 7 | 8 | pub mod defer; 9 | mod test; 10 | pub mod tree_config; 11 | 12 | pub use default::default_tree; 13 | use once_cell::sync::Lazy; 14 | use scoped_branch::ScopedBranch; 15 | use std::collections::BTreeMap; 16 | use std::fs::File; 17 | use std::io::Write; 18 | 19 | pub use crate::tree_config::*; 20 | 21 | /// Reference wrapper for `TreeBuilderBase` 22 | #[derive(Debug, Clone)] 23 | pub struct TreeBuilder(Arc>); 24 | 25 | impl TreeBuilder { 26 | /// Returns a new `TreeBuilder` with an empty `Tree`. 27 | /// 28 | /// # Example 29 | /// 30 | /// ``` 31 | /// use debug_tree::TreeBuilder; 32 | /// let tree = TreeBuilder::new(); 33 | /// ``` 34 | pub fn new() -> TreeBuilder { 35 | TreeBuilder { 36 | 0: Arc::new(Mutex::new(internal::TreeBuilderBase::new())), 37 | } 38 | } 39 | 40 | /// Set the configuration override for displaying trees 41 | /// 42 | /// # Example 43 | /// 44 | /// ``` 45 | /// use debug_tree::{TreeBuilder, add_branch_to, add_leaf_to, TreeSymbols, TreeConfig}; 46 | /// let tree = TreeBuilder::new(); 47 | /// { 48 | /// add_branch_to!(tree, "1"); 49 | /// { 50 | /// add_branch_to!(tree, "1.1"); 51 | /// add_leaf_to!(tree, "1.1.1"); 52 | /// add_leaf_to!(tree, "1.1.2"); 53 | /// } 54 | /// add_leaf_to!(tree, "1.2"); 55 | /// } 56 | /// add_leaf_to!(tree, "2"); 57 | /// tree.set_config_override(TreeConfig::new() 58 | /// .show_first_level() 59 | /// .symbols(TreeSymbols::with_rounded())); 60 | /// tree.peek_print(); 61 | /// assert_eq!("\ 62 | /// ├╼ 1 63 | /// │ ├╼ 1.1 64 | /// │ │ ├╼ 1.1.1 65 | /// │ │ ╰╼ 1.1.2 66 | /// │ ╰╼ 1.2 67 | /// ╰╼ 2" , &tree.string()); 68 | /// ``` 69 | pub fn set_config_override(&self, config: TreeConfig) { 70 | let mut lock = self.0.lock().unwrap(); 71 | lock.set_config_override(Some(config)) 72 | } 73 | 74 | /// Remove the configuration override 75 | /// The default configuration will be used instead 76 | pub fn remove_config_override(&self) { 77 | self.0.lock().unwrap().set_config_override(None); 78 | } 79 | 80 | /// Update the configuration override for displaying trees 81 | /// If an override doesn't yet exist, it is created. 82 | /// 83 | /// # Example 84 | /// 85 | /// ``` 86 | /// use debug_tree::{TreeBuilder, add_branch_to, add_leaf_to, TreeSymbols}; 87 | /// let tree = TreeBuilder::new(); 88 | /// { 89 | /// add_branch_to!(tree, "1"); 90 | /// { 91 | /// add_branch_to!(tree, "1.1"); 92 | /// add_leaf_to!(tree, "1.1.1"); 93 | /// add_leaf_to!(tree, "1.1.2"); 94 | /// } 95 | /// add_leaf_to!(tree, "1.2"); 96 | /// } 97 | /// add_leaf_to!(tree, "2"); 98 | /// tree.update_config_override(|x|{ 99 | /// x.indent = 3; 100 | /// x.symbols = TreeSymbols::with_rounded(); 101 | /// x.show_first_level = true; 102 | /// }); 103 | /// tree.peek_print(); 104 | /// assert_eq!("\ 105 | /// ├─╼ 1 106 | /// │ ├─╼ 1.1 107 | /// │ │ ├─╼ 1.1.1 108 | /// │ │ ╰─╼ 1.1.2 109 | /// │ ╰─╼ 1.2 110 | /// ╰─╼ 2" , &tree.string()); 111 | /// ``` 112 | pub fn update_config_override(&self, update: F) { 113 | let mut lock = self.0.lock().unwrap(); 114 | match lock.config_override_mut() { 115 | Some(x) => update(x), 116 | None => { 117 | let mut x = TreeConfig::default(); 118 | update(&mut x); 119 | lock.set_config_override(Some(x)); 120 | } 121 | } 122 | } 123 | 124 | /// Returns the optional configuration override. 125 | pub fn get_config_override(&self) -> Option { 126 | let lock = self.0.lock().unwrap(); 127 | lock.config_override().clone() 128 | } 129 | 130 | /// Returns whether a configuration override is set. 131 | pub fn has_config_override(&self) -> bool { 132 | let lock = self.0.lock().unwrap(); 133 | lock.config_override().is_some() 134 | } 135 | 136 | /// Adds a new branch with text, `text` and returns a `ScopedBranch`. 137 | /// When the returned `ScopedBranch` goes out of scope, (likely the end of the current block), 138 | /// or if its `release()` method is called, the tree will step back out of the added branch. 139 | /// 140 | /// # Arguments 141 | /// * `text` - A string slice to use as the newly added branch's text. 142 | /// 143 | /// # Examples 144 | /// 145 | /// Exiting branch when end of scope is reached. 146 | /// ``` 147 | /// use debug_tree::TreeBuilder; 148 | /// let tree = TreeBuilder::new(); 149 | /// { 150 | /// let _branch = tree.add_branch("Branch"); // _branch enters scope 151 | /// // tree is now pointed inside new branch. 152 | /// tree.add_leaf("Child of Branch"); 153 | /// // _branch leaves scope, tree moves up to parent branch. 154 | /// } 155 | /// tree.add_leaf("Sibling of Branch"); 156 | /// assert_eq!("\ 157 | /// Branch 158 | /// └╼ Child of Branch 159 | /// Sibling of Branch" , &tree.string()); 160 | /// ``` 161 | /// 162 | /// Using `release()` before out of scope. 163 | /// ``` 164 | /// use debug_tree::TreeBuilder; 165 | /// let tree = TreeBuilder::new(); 166 | /// { 167 | /// let mut branch = tree.add_branch("Branch"); // branch enters scope 168 | /// // tree is now pointed inside new branch. 169 | /// tree.add_leaf("Child of Branch"); 170 | /// branch.release(); 171 | /// tree.add_leaf("Sibling of Branch"); 172 | /// // branch leaves scope, but no effect because its `release()` method has already been called 173 | /// } 174 | /// assert_eq!("\ 175 | /// Branch 176 | /// └╼ Child of Branch 177 | /// Sibling of Branch", &tree.string()); 178 | /// ``` 179 | pub fn add_branch(&self, text: &str) -> ScopedBranch { 180 | self.add_leaf(text); 181 | ScopedBranch::new(self.clone()) 182 | } 183 | 184 | /// Adds a new branch with text, `text` and returns a `ScopedBranch`. 185 | /// When the returned `ScopedBranch` goes out of scope, (likely the end of the current block), 186 | /// or if its `release()` method is called, the tree tree will step back out of the added branch. 187 | /// 188 | /// # Arguments 189 | /// * `text` - A string slice to use as the newly added branch's text. 190 | /// 191 | /// # Examples 192 | /// 193 | /// Stepping out of branch when end of scope is reached. 194 | /// ``` 195 | /// use debug_tree::TreeBuilder; 196 | /// let tree = TreeBuilder::new(); 197 | /// { 198 | /// tree.add_leaf("Branch"); 199 | /// let _branch = tree.enter_scoped(); // _branch enters scope 200 | /// // tree is now pointed inside new branch. 201 | /// tree.add_leaf("Child of Branch"); 202 | /// // _branch leaves scope, tree moves up to parent branch. 203 | /// } 204 | /// tree.add_leaf("Sibling of Branch"); 205 | /// assert_eq!("\ 206 | /// Branch 207 | /// └╼ Child of Branch 208 | /// Sibling of Branch", &tree.string()); 209 | /// ``` 210 | /// 211 | /// Using `release()` before out of scope. 212 | /// ``` 213 | /// use debug_tree::TreeBuilder; 214 | /// let tree = TreeBuilder::new(); 215 | /// { 216 | /// tree.add_leaf("Branch"); 217 | /// let mut branch = tree.enter_scoped(); // branch enters scope 218 | /// // tree is now pointed inside new branch. 219 | /// tree.add_leaf("Child of Branch"); 220 | /// branch.release(); 221 | /// tree.add_leaf("Sibling of Branch"); 222 | /// // branch leaves scope, but no effect because its `release()` method has already been called 223 | /// } 224 | /// assert_eq!("\ 225 | /// Branch 226 | /// └╼ Child of Branch 227 | /// Sibling of Branch", &tree.string()); 228 | /// ``` 229 | pub fn enter_scoped(&self) -> ScopedBranch { 230 | if self.is_enabled() { 231 | ScopedBranch::new(self.clone()) 232 | } else { 233 | ScopedBranch::none() 234 | } 235 | } 236 | 237 | /// Adds a leaf to current branch with the given text, `text`. 238 | /// 239 | /// # Arguments 240 | /// * `text` - A string slice to use as the newly added leaf's text. 241 | /// 242 | /// # Example 243 | /// 244 | /// ``` 245 | /// use debug_tree::TreeBuilder; 246 | /// let tree = TreeBuilder::new(); 247 | /// tree.add_leaf("New leaf"); 248 | /// ``` 249 | pub fn add_leaf(&self, text: &str) { 250 | let mut x = self.0.lock().unwrap(); 251 | if x.is_enabled() { 252 | x.add_leaf(&text); 253 | } 254 | } 255 | 256 | /// Steps into a new child branch. 257 | /// Stepping out of the branch requires calling `exit()`. 258 | /// 259 | /// # Example 260 | /// ``` 261 | /// use debug_tree::TreeBuilder; 262 | /// let tree = TreeBuilder::new(); 263 | /// tree.add_leaf("Branch"); 264 | /// tree.enter(); 265 | /// tree.add_leaf("Child of Branch"); 266 | /// assert_eq!("\ 267 | /// Branch 268 | /// └╼ Child of Branch", &tree.string()); 269 | /// ``` 270 | pub fn enter(&self) { 271 | let mut x = self.0.lock().unwrap(); 272 | if x.is_enabled() { 273 | x.enter(); 274 | } 275 | } 276 | 277 | /// Exits the current branch, to the parent branch. 278 | /// If no parent branch exists, no action is taken 279 | /// 280 | /// # Example 281 | /// 282 | /// ``` 283 | /// use debug_tree::TreeBuilder; 284 | /// let tree = TreeBuilder::new(); 285 | /// tree.add_leaf("Branch"); 286 | /// tree.enter(); 287 | /// tree.add_leaf("Child of Branch"); 288 | /// tree.exit(); 289 | /// tree.add_leaf("Sibling of Branch"); 290 | /// assert_eq!("\ 291 | /// Branch 292 | /// └╼ Child of Branch 293 | /// Sibling of Branch", &tree.string()); 294 | /// ``` 295 | pub fn exit(&self) -> bool { 296 | let mut x = self.0.lock().unwrap(); 297 | if x.is_enabled() { 298 | x.exit() 299 | } else { 300 | false 301 | } 302 | } 303 | 304 | /// Returns the depth of the current branch 305 | /// The initial depth when no branches have been adeed is 0. 306 | /// 307 | /// # Example 308 | /// 309 | /// ``` 310 | /// use debug_tree::TreeBuilder; 311 | /// let tree = TreeBuilder::new(); 312 | /// assert_eq!(0, tree.depth()); 313 | /// let _b = tree.add_branch("Branch"); 314 | /// assert_eq!(1, tree.depth()); 315 | /// let _b = tree.add_branch("Child branch"); 316 | /// assert_eq!(2, tree.depth()); 317 | /// ``` 318 | pub fn depth(&self) -> usize { 319 | self.0.lock().unwrap().depth() 320 | } 321 | 322 | /// Prints the tree without clearing. 323 | /// 324 | /// # Example 325 | /// 326 | /// ``` 327 | /// use debug_tree::TreeBuilder; 328 | /// let tree = TreeBuilder::new(); 329 | /// tree.add_leaf("Leaf"); 330 | /// tree.peek_print(); 331 | /// // Leaf 332 | /// tree.peek_print(); 333 | /// // Leaf 334 | /// // Leaf 2 335 | /// ``` 336 | pub fn peek_print(&self) { 337 | self.0.lock().unwrap().peek_print(); 338 | } 339 | 340 | /// Prints the tree and then clears it. 341 | /// 342 | /// # Example 343 | /// 344 | /// ``` 345 | /// use debug_tree::TreeBuilder; 346 | /// let tree = TreeBuilder::new(); 347 | /// tree.add_leaf("Leaf"); 348 | /// tree.print(); 349 | /// // Leaf 350 | /// tree.add_leaf("Leaf 2"); 351 | /// tree.print(); 352 | /// // Leaf 2 353 | /// ``` 354 | pub fn print(&self) { 355 | self.0.lock().unwrap().print(); 356 | } 357 | 358 | /// Returns the tree as a string without clearing the tree. 359 | /// 360 | /// # Example 361 | /// 362 | /// ``` 363 | /// use debug_tree::TreeBuilder; 364 | /// let tree = TreeBuilder::new(); 365 | /// tree.add_leaf("Leaf"); 366 | /// assert_eq!("Leaf", tree.peek_string()); 367 | /// tree.add_leaf("Leaf 2"); 368 | /// assert_eq!("Leaf\nLeaf 2", tree.peek_string()); 369 | /// ``` 370 | pub fn peek_string(&self) -> String { 371 | self.0.lock().unwrap().peek_string() 372 | } 373 | 374 | /// Returns the tree as a string and clears the tree. 375 | /// 376 | /// # Example 377 | /// 378 | /// ``` 379 | /// use debug_tree::TreeBuilder; 380 | /// let tree = TreeBuilder::new(); 381 | /// tree.add_leaf("Leaf"); 382 | /// assert_eq!("Leaf", tree.string()); 383 | /// tree.add_leaf("Leaf 2"); 384 | /// assert_eq!("Leaf 2", tree.string()); 385 | /// ``` 386 | pub fn string(&self) -> String { 387 | self.0.lock().unwrap().string() 388 | } 389 | 390 | /// Writes the tree to file without clearing. 391 | /// 392 | /// # Example 393 | /// 394 | /// ``` 395 | /// use debug_tree::TreeBuilder; 396 | /// use std::fs::{read_to_string, create_dir}; 397 | /// use std::io::Read; 398 | /// let tree = TreeBuilder::new(); 399 | /// create_dir("test_out").ok(); 400 | /// tree.add_leaf("Leaf"); 401 | /// assert_eq!(tree.peek_string(), "Leaf"); 402 | /// tree.peek_write("test_out/peek_write.txt"); 403 | /// assert_eq!(read_to_string("test_out/peek_write.txt").unwrap(), "Leaf"); 404 | /// assert_eq!(tree.peek_string(), "Leaf"); 405 | /// ``` 406 | pub fn peek_write(&self, path: &str) -> std::io::Result<()> { 407 | let mut file = File::create(path)?; 408 | file.write_all(self.peek_string().as_bytes()) 409 | } 410 | 411 | /// Writes the tree to file without clearing. 412 | /// 413 | /// # Example 414 | /// 415 | /// ``` 416 | /// use debug_tree::TreeBuilder; 417 | /// use std::io::Read; 418 | /// use std::fs::{read_to_string, create_dir}; 419 | /// let tree = TreeBuilder::new(); 420 | /// create_dir("test_out").ok(); 421 | /// tree.add_leaf("Leaf"); 422 | /// assert_eq!(tree.peek_string(), "Leaf"); 423 | /// tree.write("test_out/write.txt"); 424 | /// assert_eq!(read_to_string("test_out/write.txt").unwrap(), "Leaf"); 425 | /// assert_eq!(tree.peek_string(), ""); 426 | /// ``` 427 | pub fn write(&self, path: &str) -> std::io::Result<()> { 428 | let mut file = File::create(path)?; 429 | file.write_all(self.string().as_bytes()) 430 | } 431 | 432 | /// Clears the tree. 433 | /// 434 | /// # Example 435 | /// 436 | /// ``` 437 | /// use debug_tree::TreeBuilder; 438 | /// let tree = TreeBuilder::new(); 439 | /// tree.add_leaf("Leaf"); 440 | /// assert_eq!("Leaf", tree.peek_string()); 441 | /// tree.clear(); 442 | /// assert_eq!("", tree.peek_string()); 443 | /// ``` 444 | pub fn clear(&self) { 445 | self.0.lock().unwrap().clear() 446 | } 447 | 448 | /// Sets the enabled state of the tree. 449 | /// 450 | /// If not enabled, the tree will not be modified by adding leaves or branches. 451 | /// Additionally, if called using the `add_`... macros, arguments will not be processed. 452 | /// This is particularly useful for suppressing output in production, with very little overhead. 453 | /// 454 | /// # Example 455 | /// ``` 456 | /// #[macro_use] 457 | /// use debug_tree::{TreeBuilder, add_leaf_to}; 458 | /// let mut tree = TreeBuilder::new(); 459 | /// tree.add_leaf("Leaf 1"); 460 | /// tree.set_enabled(false); 461 | /// add_leaf_to!(tree, "Leaf 2"); 462 | /// tree.set_enabled(true); 463 | /// add_leaf_to!(tree, "Leaf 3"); 464 | /// assert_eq!("Leaf 1\nLeaf 3", tree.peek_string()); 465 | /// ``` 466 | pub fn set_enabled(&self, enabled: bool) { 467 | self.0.lock().unwrap().set_enabled(enabled); 468 | } 469 | 470 | /// Returns the enabled state of the tree. 471 | /// 472 | /// # Example 473 | /// ``` 474 | /// use debug_tree::TreeBuilder; 475 | /// let mut tree = TreeBuilder::new(); 476 | /// assert_eq!(true, tree.is_enabled()); 477 | /// tree.set_enabled(false); 478 | /// assert_eq!(false, tree.is_enabled()); 479 | /// ``` 480 | pub fn is_enabled(&self) -> bool { 481 | self.0.lock().unwrap().is_enabled() 482 | } 483 | } 484 | 485 | pub trait AsTree { 486 | fn as_tree(&self) -> TreeBuilder; 487 | fn is_tree_enabled(&self) -> bool { 488 | self.as_tree().is_enabled() 489 | } 490 | } 491 | 492 | impl AsTree for TreeBuilder { 493 | fn as_tree(&self) -> TreeBuilder { 494 | self.clone() 495 | } 496 | } 497 | 498 | pub(crate) fn get_or_add_tree>(name: T) -> TreeBuilder { 499 | let mut map = TREE_MAP.lock().unwrap(); 500 | match map.get(name.as_ref()) { 501 | Some(x) => x.clone(), 502 | _ => { 503 | let val = TreeBuilder::new(); 504 | map.insert(name.as_ref().to_string(), val.clone()); 505 | val 506 | } 507 | } 508 | } 509 | 510 | pub(crate) fn get_tree>(name: T) -> Option { 511 | TREE_MAP.lock().unwrap().get(name.as_ref()).cloned() 512 | } 513 | 514 | type TreeMap = BTreeMap; 515 | 516 | static TREE_MAP: Lazy>> = 517 | Lazy::new(|| -> Arc> { Arc::new(Mutex::new(TreeMap::new())) }); 518 | 519 | /// Sets the enabled state of the tree. 520 | /// 521 | /// # Arguments 522 | /// * `name` - The tree name 523 | /// * `enabled` - The enabled state 524 | /// 525 | pub fn set_enabled>(name: T, enabled: bool) { 526 | let mut map = TREE_MAP.lock().unwrap(); 527 | match map.get_mut(name.as_ref()) { 528 | Some(x) => x.set_enabled(enabled), 529 | _ => { 530 | let tree = TreeBuilder::new(); 531 | tree.set_enabled(enabled); 532 | map.insert(name.as_ref().to_string(), tree); 533 | } 534 | } 535 | } 536 | 537 | impl> AsTree for T { 538 | fn as_tree(&self) -> TreeBuilder { 539 | get_or_add_tree(self) 540 | } 541 | /// Check if the named tree is enabled and exists 542 | /// This does not create a new tree if non-existent 543 | /// 544 | /// # Arguments 545 | /// * `tree_name` - The tree name 546 | /// 547 | fn is_tree_enabled(&self) -> bool { 548 | get_tree(self).map(|x| x.is_enabled()).unwrap_or(false) 549 | } 550 | } 551 | 552 | /// Returns the tree 553 | /// If there is no tree then one is created and then returned. 554 | pub fn tree(tree: T) -> TreeBuilder { 555 | tree.as_tree() 556 | } 557 | 558 | /// Returns the tree named `name` 559 | /// If there is no tree named `name` then one is created and then returned. 560 | pub fn is_tree_enabled(tree: &T) -> bool { 561 | tree.is_tree_enabled() 562 | } 563 | 564 | /// Calls [clear](TreeBuilder::clear) for the tree named `name` 565 | /// If there is no tree named `name` then one is created 566 | pub fn clear>(name: T) { 567 | name.as_tree().clear(); 568 | } 569 | 570 | /// Returns [string](TreeBuilder::string) for the tree named `name` 571 | /// If there is no tree named `name` then one is created 572 | pub fn string>(name: T) -> String { 573 | name.as_tree().string() 574 | } 575 | 576 | /// Returns [peek_string](TreeBuilder::peek_string) for the tree named `name` 577 | /// If there is no tree named `name` then one is created 578 | pub fn peek_string>(name: T) -> String { 579 | name.as_tree().peek_string() 580 | } 581 | 582 | /// Calls [print](TreeBuilder::print) for the tree named `name` 583 | /// If there is no tree named `name` then one is created 584 | pub fn print>(name: T) { 585 | name.as_tree().print(); 586 | } 587 | 588 | /// Calls [peek_print](TreeBuilder::peek_print) for the tree named `name` 589 | /// If there is no tree named `name` then one is created 590 | pub fn peek_print>(name: T) { 591 | name.as_tree().peek_print(); 592 | } 593 | 594 | /// Calls [write](TreeBuilder::write) for the tree named `name` 595 | /// If there is no tree named `name` then one is created 596 | pub fn write, P: AsRef>(name: T, path: P) -> std::io::Result<()> { 597 | name.as_tree().write(path.as_ref()) 598 | } 599 | 600 | /// Calls [peek_print](TreeBuilder::peek_print) for the tree named `name` 601 | /// If there is no tree named `name` then one is created 602 | pub fn peek_write, P: AsRef>(name: T, path: P) -> std::io::Result<()> { 603 | name.as_tree().peek_write(path.as_ref()) 604 | } 605 | 606 | /// Adds a leaf to given tree with the given text and formatting arguments 607 | /// 608 | /// # Arguments 609 | /// * `tree` - The tree that the leaf should be added to 610 | /// * `text...` - Formatted text arguments, as per `format!(...)`. 611 | /// 612 | /// # Example 613 | /// 614 | /// ``` 615 | /// #[macro_use] 616 | /// use debug_tree::{TreeBuilder, add_leaf_to}; 617 | /// fn main() { 618 | /// let tree = TreeBuilder::new(); 619 | /// add_leaf_to!(tree, "A {} leaf", "new"); 620 | /// assert_eq!("A new leaf", &tree.peek_string()); 621 | /// } 622 | /// ``` 623 | #[macro_export] 624 | macro_rules! add_leaf_to { 625 | ($tree:expr, $($arg:tt)*) => (if $crate::is_tree_enabled(&$tree) { 626 | use $crate::AsTree; 627 | $tree.as_tree().add_leaf(&format!($($arg)*)) 628 | }); 629 | } 630 | 631 | /// Adds a leaf to given tree with the given `value` argument 632 | /// 633 | /// # Arguments 634 | /// * `tree` - The tree that the leaf should be added to 635 | /// * `value` - An expression that implements the `Display` trait. 636 | /// 637 | /// # Example 638 | /// 639 | /// ``` 640 | /// #[macro_use] 641 | /// use debug_tree::{TreeBuilder, add_leaf_value_to}; 642 | /// fn main() { 643 | /// let tree = TreeBuilder::new(); 644 | /// let value = add_leaf_value_to!(tree, 5 * 4 * 3 * 2); 645 | /// assert_eq!(120, value); 646 | /// assert_eq!("120", &tree.peek_string()); 647 | /// } 648 | /// ``` 649 | #[macro_export] 650 | macro_rules! add_leaf_value_to { 651 | ($tree:expr, $value:expr) => {{ 652 | let v = $value; 653 | if $crate::is_tree_enabled(&$tree) { 654 | use $crate::AsTree; 655 | $tree.as_tree().add_leaf(&format!("{}", &v)); 656 | } 657 | v 658 | }}; 659 | } 660 | 661 | /// Adds a scoped branch to given tree with the given text and formatting arguments 662 | /// The branch will be exited at the end of the current block. 663 | /// 664 | /// # Arguments 665 | /// * `tree` - The tree that the leaf should be added to 666 | /// * `text...` - Formatted text arguments, as per `format!(...)`. 667 | /// 668 | /// # Example 669 | /// 670 | /// ``` 671 | /// #[macro_use] 672 | /// use debug_tree::{TreeBuilder, add_branch_to, add_leaf_to}; 673 | /// fn main() { 674 | /// let tree = TreeBuilder::new(); 675 | /// { 676 | /// add_branch_to!(tree, "New {}", "Branch"); // _branch enters scope 677 | /// // tree is now pointed inside new branch. 678 | /// add_leaf_to!(tree, "Child of {}", "Branch"); 679 | /// // Block ends, so tree exits the current branch. 680 | /// } 681 | /// add_leaf_to!(tree, "Sibling of {}", "Branch"); 682 | /// assert_eq!("\ 683 | /// New Branch 684 | /// └╼ Child of Branch 685 | /// Sibling of Branch" , &tree.string()); 686 | /// } 687 | /// ``` 688 | #[macro_export] 689 | macro_rules! add_branch_to { 690 | ($tree:expr) => { 691 | let _debug_tree_branch = if $crate::is_tree_enabled(&$tree) { 692 | use $crate::AsTree; 693 | $tree.as_tree().enter_scoped() 694 | } else { 695 | $crate::scoped_branch::ScopedBranch::none() 696 | }; 697 | }; 698 | ($tree:expr, $($arg:tt)*) => { 699 | let _debug_tree_branch = if $crate::is_tree_enabled(&$tree) { 700 | use $crate::AsTree; 701 | $tree.as_tree().add_branch(&format!($($arg)*)) 702 | } else { 703 | $crate::scoped_branch::ScopedBranch::none() 704 | }; 705 | }; 706 | } 707 | 708 | /// Calls `function` with argument, `tree`, at the end of the current scope 709 | /// The function will only be executed if the tree is enabled when this macro is called 710 | #[macro_export] 711 | macro_rules! defer { 712 | ($function:expr) => { 713 | let _debug_tree_defer = { 714 | use $crate::AsTree; 715 | if $crate::default::default_tree().is_enabled() { 716 | use $crate::AsTree; 717 | $crate::defer::DeferredFn::new($crate::default::default_tree(), $function) 718 | } else { 719 | $crate::defer::DeferredFn::none() 720 | } 721 | }; 722 | }; 723 | ($tree:expr, $function:expr) => { 724 | let _debug_tree_defer = { 725 | use $crate::AsTree; 726 | if $tree.as_tree().is_enabled() { 727 | $crate::defer::DeferredFn::new($tree.as_tree(), $function) 728 | } else { 729 | $crate::defer::DeferredFn::none() 730 | } 731 | }; 732 | }; 733 | } 734 | 735 | /// Calls [print](TreeBuilder::print) on `tree` at the end of the current scope. 736 | /// The function will only be executed if the tree is enabled when this macro is called 737 | #[macro_export] 738 | macro_rules! defer_print { 739 | () => { 740 | $crate::defer!(|x| { 741 | x.print(); 742 | }) 743 | }; 744 | ($tree:expr) => { 745 | $crate::defer!($tree, |x| { 746 | x.print(); 747 | }) 748 | }; 749 | } 750 | 751 | /// Calls [peek_print](TreeBuilder::peek_print) on `tree` at the end of the current scope. 752 | /// The function will only be executed if the tree is enabled when this macro is called 753 | #[macro_export] 754 | macro_rules! defer_peek_print { 755 | () => { 756 | $crate::defer!(|x| { 757 | x.peek_print(); 758 | }) 759 | }; 760 | ($tree:expr) => { 761 | $crate::defer!($tree, |x| { 762 | x.peek_print(); 763 | }) 764 | }; 765 | } 766 | 767 | /// Calls [write](TreeBuilder::write) on `tree` at the end of the current scope. 768 | /// The function will only be executed if the tree is enabled when this macro is called 769 | #[macro_export] 770 | macro_rules! defer_write { 771 | ($tree:expr, $path:expr) => { 772 | $crate::defer!($tree, |x| { 773 | if let Err(err) = x.write($path) { 774 | eprintln!("error during `defer_write`: {}", err); 775 | } 776 | }) 777 | }; 778 | ($path:expr) => { 779 | $crate::defer!(|x| { 780 | if let Err(err) = x.write($path) { 781 | eprintln!("error during `defer_write`: {}", err); 782 | } 783 | }) 784 | }; 785 | } 786 | 787 | /// Calls [peek_write](TreeBuilder::peek_write) on `tree` at the end of the current scope. 788 | /// The function will only be executed if the tree is enabled when this macro is called 789 | #[macro_export] 790 | macro_rules! defer_peek_write { 791 | ($tree:expr, $path:expr) => { 792 | $crate::defer!($tree, |x| { 793 | if let Err(err) = x.peek_write($path) { 794 | eprintln!("error during `defer_peek_write`: {}", err); 795 | } 796 | }) 797 | }; 798 | ($path:expr) => { 799 | $crate::defer!(|x| { 800 | if let Err(err) = x.peek_write($path) { 801 | eprintln!("error during `defer_peek_write`: {}", err); 802 | } 803 | }) 804 | }; 805 | } 806 | --------------------------------------------------------------------------------