summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMartin Geisler <mgeisler@google.com>2024-03-19 12:19:30 +0100
committerMartin Geisler <mgeisler@google.com>2024-03-26 18:02:13 +0100
commit9f6ccd869b3a86f9243d5b225a183fb84bc183e0 (patch)
tree71caddf2114036698ed360a6999b394aa058de9e
parentaa31e376b2032cee14c07c43ebcbcb352d35c6dc (diff)
downloaddebug_tree-9f6ccd869b3a86f9243d5b225a183fb84bc183e0.tar.gz
Import 'debug_tree' crateupstream
Request Document: go/android-rust-importing-crates For CL Reviewers: go/android3p#cl-review For Build Team: go/ab-third-party-imports Bug: http://b/328410074 Test: m libdebug_tree Change-Id: I4283f42b85c79fe280d2eebe2be8f8283b6abe64 Change-Id: I537f1f12b44c56a83aa973b440271b19ca2bb40d
-rw-r--r--.cargo_vcs_info.json5
-rw-r--r--.gitignore7
-rw-r--r--.md-inc.toml2
-rw-r--r--Android.bp40
-rw-r--r--Cargo.toml33
-rw-r--r--LICENSE19
-rw-r--r--METADATA20
-rw-r--r--MODULE_LICENSE_MIT0
-rw-r--r--OWNERS2
-rw-r--r--README.md284
-rw-r--r--cargo_embargo.json4
-rw-r--r--doc/build/LICENSE.adoc19
-rw-r--r--doc/build/asciidoc-coalescer.rb66
-rw-r--r--doc/readme_template.adoc127
-rw-r--r--examples/fibonacci.rs18
-rw-r--r--examples/multi_line.rs8
-rw-r--r--examples/multiple_trees.rs22
-rw-r--r--examples/nested.rs19
-rw-r--r--examples/no_macros.rs18
-rw-r--r--examples/out/fibonacci.txt8
-rw-r--r--examples/out/multi_line.txt5
-rw-r--r--examples/out/multiple_trees_A.txt5
-rw-r--r--examples/out/multiple_trees_B.txt7
-rw-r--r--examples/out/nested.txt6
-rw-r--r--examples/out/no_macros.txt3
-rw-r--r--examples/out/panic.txt6
-rw-r--r--examples/panic.rs20
-rw-r--r--src/default.rs167
-rw-r--r--src/defer.rs46
-rw-r--r--src/internal.rs281
-rw-r--r--src/lib.rs805
-rw-r--r--src/scoped_branch.rs26
-rw-r--r--src/test.rs596
-rw-r--r--src/tree_config.rs224
34 files changed, 2918 insertions, 0 deletions
diff --git a/.cargo_vcs_info.json b/.cargo_vcs_info.json
new file mode 100644
index 0000000..27bd387
--- /dev/null
+++ b/.cargo_vcs_info.json
@@ -0,0 +1,5 @@
+{
+ "git": {
+ "sha1": "5b709de2d8872102b20b566c408d31d0662d7a9f"
+ }
+}
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..d4fe7bd
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,7 @@
+/target
+**/*.rs.bk
+.idea
+*.lock
+test_out
+
+.DS_Store
diff --git a/.md-inc.toml b/.md-inc.toml
new file mode 100644
index 0000000..57b0690
--- /dev/null
+++ b/.md-inc.toml
@@ -0,0 +1,2 @@
+base_dir = "examples"
+files = ["README.md"] \ No newline at end of file
diff --git a/Android.bp b/Android.bp
new file mode 100644
index 0000000..ed34a1a
--- /dev/null
+++ b/Android.bp
@@ -0,0 +1,40 @@
+// This file is generated by cargo_embargo.
+// Do not modify this file as changes will be overridden on upgrade.
+
+// TODO: Add license.
+rust_test {
+ name: "debug_tree_test_src_lib",
+ host_supported: true,
+ crate_name: "debug_tree",
+ cargo_env_compat: true,
+ cargo_pkg_version: "0.4.0",
+ srcs: ["src/lib.rs"],
+ test_suites: ["general-tests"],
+ auto_gen_config: true,
+ test_options: {
+ unit_test: true,
+ },
+ edition: "2018",
+ rustlibs: [
+ "libfutures",
+ "libonce_cell",
+ "libtokio",
+ ],
+}
+
+rust_library {
+ name: "libdebug_tree",
+ host_supported: true,
+ crate_name: "debug_tree",
+ cargo_env_compat: true,
+ cargo_pkg_version: "0.4.0",
+ srcs: ["src/lib.rs"],
+ edition: "2018",
+ rustlibs: ["libonce_cell"],
+ apex_available: [
+ "//apex_available:platform",
+ "//apex_available:anyapex",
+ ],
+ product_available: true,
+ vendor_available: true,
+}
diff --git a/Cargo.toml b/Cargo.toml
new file mode 100644
index 0000000..d6c6559
--- /dev/null
+++ b/Cargo.toml
@@ -0,0 +1,33 @@
+# THIS FILE IS AUTOMATICALLY GENERATED BY CARGO
+#
+# When uploading crates to the registry Cargo will automatically
+# "normalize" Cargo.toml files for maximal compatibility
+# with all versions of Cargo and also rewrite `path` dependencies
+# to registry (e.g., crates.io) dependencies
+#
+# If you believe there's an error in this file please file an
+# issue against the rust-lang/cargo repository. If you're
+# editing this file be aware that the upstream Cargo.toml
+# will likely look very different (and much more reasonable)
+
+[package]
+edition = "2018"
+name = "debug_tree"
+version = "0.4.0"
+authors = ["Marty Papamanolis <marty@mindpipess.com>"]
+description = "Build a tree one element at a time and output it as a pretty string."
+readme = "README.md"
+license = "MIT"
+repository = "https://github.com/martypapa/debug-tree"
+
+[lib]
+name = "debug_tree"
+crate-type = ["lib"]
+[dependencies.once_cell]
+version = "1"
+[dev-dependencies.futures]
+version = "0.3.4"
+
+[dev-dependencies.tokio]
+version = "0.2.9"
+features = ["macros", "fs"]
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..9cf1062
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,19 @@
+MIT License
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/METADATA b/METADATA
new file mode 100644
index 0000000..6537ff9
--- /dev/null
+++ b/METADATA
@@ -0,0 +1,20 @@
+name: "debug_tree"
+description: "Build a tree one element at a time and output it as a pretty string."
+third_party {
+ identifier {
+ type: "crates.io"
+ value: "debug_tree"
+ }
+ identifier {
+ type: "Archive"
+ value: "https://static.crates.io/crates/debug_tree/debug_tree-0.4.0.crate"
+ primary_source: true
+ }
+ version: "0.4.0"
+ license_type: NOTICE
+ last_upgrade_date {
+ year: 2024
+ month: 3
+ day: 21
+ }
+}
diff --git a/MODULE_LICENSE_MIT b/MODULE_LICENSE_MIT
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/MODULE_LICENSE_MIT
diff --git a/OWNERS b/OWNERS
new file mode 100644
index 0000000..48bea6e
--- /dev/null
+++ b/OWNERS
@@ -0,0 +1,2 @@
+# Bug component: 688011
+include platform/prebuilts/rust:main:/OWNERS
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..84511aa
--- /dev/null
+++ b/README.md
@@ -0,0 +1,284 @@
+# Debug Tree
+
+This library allows you to build a tree one element at a time and output it as a pretty string.
+
+The tree can easily be output to a `String`, `stdout` or a file.
+
+This is particularly convenient for generating clean output from nested and recursive functions.
+
+* [Recursive Fibonacci Example](#recursive-fibonacci-example)
+* [Overview](#overview)
+* [More Examples](#more-examples)
+ * [Multiple Tagged Trees](#multiple-tagged-trees)
+ * [Nested Functions](#nested-functions)
+ * [Panic](#panics)
+ * [Without Macros](#without-macros)
+
+
+## Recursive Fibonacci Example
+
+Using the `add_branch!()` macro at the start of the `factors()` function, you can generate an entire call tree, with minimal effort.
+
+<!--{ fibonacci.rs | code: rust }-->
+```rust
+use debug_tree::*;
+
+fn factors(x: usize) {
+ add_branch!("{}", x); // <~ THE MAGIC LINE
+ for i in 1..x {
+ if x % i == 0 {
+ factors(i);
+ }
+ }
+}
+
+fn main() {
+ // output to file at the end of this block
+ defer_write!("examples/out/fibonacci.txt");
+ add_branch!("A Fibonacci Tree");
+ factors(6);
+ add_leaf!("That's All Folks!");
+}
+```
+<!--{ end }-->
+
+<!--{ out/fibonacci.txt | code }-->
+```
+A Fibonacci Tree
+├╼ 6
+│ ├╼ 1
+│ ├╼ 2
+│ │ └╼ 1
+│ └╼ 3
+│ └╼ 1
+└╼ That's All Folks!
+```
+<!--{ end }-->
+
+## Overview
+
+- Add a branch
+ - `add_branch!("Hello, {}", "World")`
+ - The branch will exit at the end of the current block
+
+- Add a leaf
+ - `add_leaf!("I am a {}", "leaf")`
+ - Added to the current scoped branch
+
+- Print a tree, or write it to file at the end of a block
+ - `defer_print!()`
+ - `defer_write!("filename.txt")`
+ - The tree will be empty after these calls
+ - To prevent clearing, use `defer_peek_print!` and `defer_peek_write!`
+
+
+- Handle multiple trees using named trees
+ - `add_branch_to!("A", "I'm a branch on tree 'A'")`
+ - `add_leaf_to!("A", "I'm a leaf on tree 'A'")`
+ - `defer_print!("A")`
+ - `defer_write!("A", "filename.txt")`
+
+- Get a named tree
+ - `tree("TREE_NAME")`
+
+- Retrieve the pretty-string from a tree
+ - `tree("TREE_NAME").string()`
+
+
+- Usage across threads
+ - `default_tree()` is local to each thread
+ - Named trees are shared between threads
+
+## More Examples
+
+### Multiple Tagged Trees
+
+If you need multiple, separated trees you can use a name tag.
+
+<!--{ multiple_trees.rs | code: rust }-->
+```rust
+use debug_tree::*;
+
+fn populate(tree_name: &str, n_children: usize) {
+ add_branch_to!(tree_name, "{} TREE", tree_name);
+ for _ in 0..n_children {
+ populate(tree_name, n_children / 2);
+ }
+}
+fn main() {
+ // Override tree config (just for "B")
+ let b_tree = tree("B");
+ b_tree.set_config_override(
+ TreeConfig::new()
+ .indent(4)
+ .symbols(TreeSymbols::with_rounded().leaf("> ")),
+ );
+ defer_write!(b_tree, "examples/out/multiple_trees_B.txt");
+ defer_write!("A", "examples/out/multiple_trees_A.txt");
+
+ populate("A", 2);
+ populate("B", 3);
+}
+```
+<!--{ end }-->
+<!--{ out/multiple_trees_A.txt | code }-->
+```
+A TREE
+├╼ A TREE
+│ └╼ A TREE
+└╼ A TREE
+ └╼ A TREE
+```
+<!--{ end }-->
+<!--{ out/multiple_trees_B.txt | code }-->
+```
+B TREE
+├──> B TREE
+│ ╰──> B TREE
+├──> B TREE
+│ ╰──> B TREE
+╰──> B TREE
+ ╰──> B TREE
+```
+<!--{ end }-->
+
+### Nested Functions
+
+Branches also make nested function calls a lot easier to follow.
+
+<!--{ nested.rs | code: rust }-->
+```rust
+use debug_tree::*;
+fn a() {
+ add_branch!("a");
+ b();
+ c();
+}
+fn b() {
+ add_branch!("b");
+ c();
+}
+fn c() {
+ add_branch!("c");
+ add_leaf!("Nothing to see here");
+}
+
+fn main() {
+ defer_write!("examples/out/nested.txt");
+ a();
+}
+```
+<!--{ end }-->
+<!--{ out/nested.txt | code }-->
+```
+a
+├╼ b
+│ └╼ c
+│ └╼ Nothing to see here
+└╼ c
+ └╼ Nothing to see here
+```
+<!--{ end }-->
+
+### Line Breaks
+
+Newlines in multi-line strings are automatically indented.
+
+<!--{ multi_line.rs | code: rust }-->
+```rust
+use debug_tree::*;
+fn main() {
+ // output to file at the end of this block
+ defer_write!("examples/out/multi_line.txt");
+ add_branch!("1");
+ add_leaf!("1.1\nAnother line...\n... and one more line");
+ add_leaf!("1.2");
+}
+```
+<!--{ end }-->
+
+<!--{ out/multi_line.txt | code }-->
+```
+1
+├╼ 1.1
+│ Another line...
+│ ... and one more line
+└╼ 1.2
+```
+<!--{ end }-->
+
+### Panics
+Even if there is a panic, the tree is not lost!
+The `defer_` functions were introduced to allow the tree
+to be printed our written to file in the case of a `panic!` or early return.
+
+<!--{ panic.rs | code: rust }-->
+```rust
+use debug_tree::*;
+
+fn i_will_panic() {
+ add_branch!("Here are my last words");
+ add_leaf!("Stay calm, and try not to panic");
+ panic!("I told you so...")
+}
+
+fn main() {
+ // output to file at the end of this block
+ defer_write!("examples/out/panic.txt");
+ // print at the end of this block
+ {
+ add_branch!("By using the 'defer_' functions");
+ add_branch!("Output will still be generated");
+ add_branch!("Otherwise you might lose your valuable tree!");
+ }
+ add_branch!("Now for something crazy...");
+ i_will_panic();
+}
+```
+<!--{ end }-->
+
+<!--{ out/panic.txt | code }-->
+```
+By using the 'defer_' functions
+└╼ Output will still be generated
+ └╼ Otherwise you might lose your valuable tree!
+Now for something crazy...
+└╼ Here are my last words
+ └╼ Stay calm, and try not to panic
+```
+<!--{ end }-->
+
+
+### Without Macros
+
+If you prefer not using macros, you can construct `TreeBuilder`s manually.
+
+<!--{ no_macros.rs | code: rust }-->
+```rust
+use debug_tree::TreeBuilder;
+
+fn main() {
+ // Make a new tree.
+ let tree = TreeBuilder::new();
+
+ // Add a scoped branch. The next item added will belong to the branch.
+ let mut branch = tree.add_branch("1 Branch");
+
+ // Add a leaf to the current branch
+ tree.add_leaf("1.1 Child");
+
+ // Leave scope early
+ branch.release();
+ tree.add_leaf("2 Sibling");
+ // output to file
+ tree.write("examples/out/no_macros.txt").ok(); // Write and flush.
+}
+```
+<!--{ end }-->
+<!--{ out/no_macros.txt | code }-->
+```
+1 Branch
+└╼ 1.1 Child
+2 Sibling
+```
+<!--{ end }--> \ No newline at end of file
diff --git a/cargo_embargo.json b/cargo_embargo.json
new file mode 100644
index 0000000..c8842d1
--- /dev/null
+++ b/cargo_embargo.json
@@ -0,0 +1,4 @@
+{
+ "run_cargo": false,
+ "tests": true
+}
diff --git a/doc/build/LICENSE.adoc b/doc/build/LICENSE.adoc
new file mode 100644
index 0000000..1d67901
--- /dev/null
+++ b/doc/build/LICENSE.adoc
@@ -0,0 +1,19 @@
+Copyright (C) 2014-2019 The Asciidoctor Project
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE. \ No newline at end of file
diff --git a/doc/build/asciidoc-coalescer.rb b/doc/build/asciidoc-coalescer.rb
new file mode 100644
index 0000000..a4f9968
--- /dev/null
+++ b/doc/build/asciidoc-coalescer.rb
@@ -0,0 +1,66 @@
+#!/usr/bin/env ruby
+
+# This script coalesces the AsciiDoc content from a document master into a
+# single output file. It does so by resolving all preprocessor directives in
+# the document, and in any files which are included. The resolving of include
+# directives is likely of most interest to users of this script.
+#
+# This script works by using Asciidoctor's PreprocessorReader to read and
+# resolve all the lines in the specified input file. The script then writes the
+# result to the output.
+#
+# The script only recognizes attributes passed in as options or those defined
+# in the document header. It does not currently process attributes defined in
+# other, arbitrary locations within the document.
+#
+# You can find a similar extension written against AsciidoctorJ here:
+# https://github.com/hibernate/hibernate-asciidoctor-extensions/blob/master/src/main/java/org/hibernate/infra/asciidoctor/extensions/savepreprocessed/SavePreprocessedOutputPreprocessor.java
+
+# TODO
+# - add cli option to write attributes passed to cli to header of document
+# - escape all preprocessor directives after lines are processed (these are preprocessor directives that were escaped in the input)
+# - wrap in a custom converter so it can be used as an extension
+
+require 'asciidoctor'
+require 'optparse'
+
+options = { attributes: [], output: '-' }
+OptionParser.new do |opts|
+ opts.banner = 'Usage: ruby asciidoc-coalescer.rb [OPTIONS] FILE'
+ opts.on('-a', '--attribute key[=value]', 'A document attribute to set in the form of key[=value]') do |a|
+ options[:attributes] << a
+ end
+ opts.on('-o', '--output FILE', 'Write output to FILE instead of stdout.') do |o|
+ options[:output] = o
+ end
+end.parse!
+
+unless (source_file = ARGV.shift)
+ warn 'Please specify an AsciiDoc source file to coalesce.'
+ exit 1
+end
+
+unless (output_file = options[:output]) == '-'
+ if (output_file = File.expand_path output_file) == (File.expand_path source_file)
+ warn 'Source and output cannot be the same file.'
+ exit 1
+ end
+end
+
+# NOTE first, resolve attributes defined at the end of the document header
+# QUESTION can we do this in a single load?
+doc = Asciidoctor.load_file source_file, safe: :unsafe, header_only: true, attributes: options[:attributes]
+# NOTE quick and dirty way to get the attributes set or unset by the document header
+header_attr_names = (doc.instance_variable_get :@attributes_modified).to_a
+header_attr_names.each {|k| doc.attributes[%(#{k}!)] = '' unless doc.attr? k }
+
+doc = Asciidoctor.load_file source_file, safe: :unsafe, parse: false, attributes: doc.attributes
+# FIXME also escape ifdef, ifndef, ifeval and endif directives
+# FIXME do this more carefully by reading line by line; if input differs by output by leading backslash, restore original line
+lines = doc.reader.read.gsub(/^include::(?=.*\[\]$)/m, '\\include::')
+
+if output_file == '-'
+ puts lines
+else
+ File.open(output_file, 'w') {|f| f.write lines }
+end \ No newline at end of file
diff --git a/doc/readme_template.adoc b/doc/readme_template.adoc
new file mode 100644
index 0000000..ec7cabb
--- /dev/null
+++ b/doc/readme_template.adoc
@@ -0,0 +1,127 @@
+:examples: ../examples/
+
+= Debug Tree
+
+This library allows you to build a tree one element at a time and output it as a pretty string.
+
+The tree can easily be output to a `String`, `stdout` or a file.
+
+This is particularly convenient for generating clean output from nested and recursive functions.
+
+
+:toc:
+
+== Recursive Fibonacci Example
+
+Using the `add_branch!()` macro at the start of the `factors()` function, you can generate an entire call tree, with minimal effort.
+
+[source,rust]
+----
+include::{examples}fibonacci.rs[]
+----
+
+----
+include::{examples}out/fibonacci.txt[]
+----
+== Overview
+
+* Add a branch
+- `add_branch!("Hello, {}", "World")`
+- The branch will exit at the end of the current block
+
+* Add a leaf
+- `add_leaf!("I am a {}", "leaf")`
+- Added to the current scoped branch
+
+* Print a tree, or write it to file at the end of a block
+- `defer_print!()`
+- `defer_write!("filename.txt")`
+- The tree will be empty after these calls
+- To prevent clearing, use `defer_peek_print!` and `defer_peek_write!`
+
+* Get the trees pretty-string
+-
+
+* Handle multiple trees using named trees
+- `add_branch_to!("A", "I'm a branch on tree 'A'")`
+- `add_leaf_to!("A", "I'm a leaf on tree 'A'")`
+- `defer_print!("A")`
+- `defer_write!("A", "filename.txt")`
+
+* Get a named tree
+- `tree("TREE_NAME")`
+
+* Retrieve the pretty-string from a tree
+- `tree("TREE_NAME").string()`
+
+
+* Usage across threads
+- `default_tree()` is local to each thread
+- Named trees are shared between threads
+
+== More Examples
+
+=== Multiple Tagged Trees
+
+If you need multiple, separated trees you can use a name tag.
+
+[source,rust]
+----
+include::{examples}multiple_trees.rs[]
+----
+----
+include::{examples}out/multiple_trees_A.txt[]
+----
+----
+include::{examples}out/multiple_trees_B.txt[]
+----
+
+=== Nested Functions
+
+Branches also make nested function calls a lot easier to follow.
+
+[source,rust]
+----
+include::{examples}nested.rs[]
+----
+----
+include::{examples}out/nested.txt[]
+----
+
+=== Line Breaks
+
+Newlines in multi-line strings are automatically indented.
+
+[source,rust]
+----
+include::{examples}multi_line.rs[]
+----
+----
+include::{examples}out/multi_line.txt[]
+----
+
+=== Panics
+Even if there is a panic, the tree is not lost!
+The `defer_` functions were introduced to allow the tree
+to be printed our written to file in the case of a `panic!` or early return.
+
+[source,rust]
+----
+include::{examples}panic.rs[]
+----
+----
+include::{examples}out/panic.txt[]
+----
+
+
+=== Without Macros
+
+If you prefer not using macros, you can construct `TreeBuilder`s manually.
+
+[source,rust]
+----
+include::{examples}no_macros.rs[]
+----
+----
+include::{examples}out/no_macros.txt[]
+----
diff --git a/examples/fibonacci.rs b/examples/fibonacci.rs
new file mode 100644
index 0000000..1095592
--- /dev/null
+++ b/examples/fibonacci.rs
@@ -0,0 +1,18 @@
+use debug_tree::*;
+
+fn factors(x: usize) {
+ add_branch!("{}", x); // <~ THE MAGIC LINE
+ for i in 1..x {
+ if x % i == 0 {
+ factors(i);
+ }
+ }
+}
+
+fn main() {
+ // output to file at the end of this block
+ defer_write!("examples/out/fibonacci.txt");
+ add_branch!("A Fibonacci Tree");
+ factors(6);
+ add_leaf!("That's All Folks!");
+}
diff --git a/examples/multi_line.rs b/examples/multi_line.rs
new file mode 100644
index 0000000..fbe9c6c
--- /dev/null
+++ b/examples/multi_line.rs
@@ -0,0 +1,8 @@
+use debug_tree::*;
+fn main() {
+ // output to file at the end of this block
+ defer_write!("examples/out/multi_line.txt");
+ add_branch!("1");
+ add_leaf!("1.1\nAnother line...\n... and one more line");
+ add_leaf!("1.2");
+}
diff --git a/examples/multiple_trees.rs b/examples/multiple_trees.rs
new file mode 100644
index 0000000..01a4941
--- /dev/null
+++ b/examples/multiple_trees.rs
@@ -0,0 +1,22 @@
+use debug_tree::*;
+
+fn populate(tree_name: &str, n_children: usize) {
+ add_branch_to!(tree_name, "{} TREE", tree_name);
+ for _ in 0..n_children {
+ populate(tree_name, n_children / 2);
+ }
+}
+fn main() {
+ // Override tree config (just for "B")
+ let b_tree = tree("B");
+ b_tree.set_config_override(
+ TreeConfig::new()
+ .indent(4)
+ .symbols(TreeSymbols::with_rounded().leaf("> ")),
+ );
+ defer_write!(b_tree, "examples/out/multiple_trees_B.txt");
+ defer_write!("A", "examples/out/multiple_trees_A.txt");
+
+ populate("A", 2);
+ populate("B", 3);
+}
diff --git a/examples/nested.rs b/examples/nested.rs
new file mode 100644
index 0000000..5315be5
--- /dev/null
+++ b/examples/nested.rs
@@ -0,0 +1,19 @@
+use debug_tree::*;
+fn a() {
+ add_branch!("a");
+ b();
+ c();
+}
+fn b() {
+ add_branch!("b");
+ c();
+}
+fn c() {
+ add_branch!("c");
+ add_leaf!("Nothing to see here");
+}
+
+fn main() {
+ defer_write!("examples/out/nested.txt");
+ a();
+}
diff --git a/examples/no_macros.rs b/examples/no_macros.rs
new file mode 100644
index 0000000..1891bf4
--- /dev/null
+++ b/examples/no_macros.rs
@@ -0,0 +1,18 @@
+use debug_tree::TreeBuilder;
+
+fn main() {
+ // Make a new tree.
+ let tree = TreeBuilder::new();
+
+ // Add a scoped branch. The next item added will belong to the branch.
+ let mut branch = tree.add_branch("1 Branch");
+
+ // Add a leaf to the current branch
+ tree.add_leaf("1.1 Child");
+
+ // Leave scope early
+ branch.release();
+ tree.add_leaf("2 Sibling");
+ // output to file
+ tree.write("examples/out/no_macros.txt").ok(); // Write and flush.
+}
diff --git a/examples/out/fibonacci.txt b/examples/out/fibonacci.txt
new file mode 100644
index 0000000..40725b8
--- /dev/null
+++ b/examples/out/fibonacci.txt
@@ -0,0 +1,8 @@
+A Fibonacci Tree
+├╼ 6
+│ ├╼ 1
+│ ├╼ 2
+│ │ └╼ 1
+│ └╼ 3
+│ └╼ 1
+└╼ That's All Folks! \ No newline at end of file
diff --git a/examples/out/multi_line.txt b/examples/out/multi_line.txt
new file mode 100644
index 0000000..93e17ff
--- /dev/null
+++ b/examples/out/multi_line.txt
@@ -0,0 +1,5 @@
+1
+├╼ 1.1
+│ Another line...
+│ ... and one more line
+└╼ 1.2 \ No newline at end of file
diff --git a/examples/out/multiple_trees_A.txt b/examples/out/multiple_trees_A.txt
new file mode 100644
index 0000000..d4b76d4
--- /dev/null
+++ b/examples/out/multiple_trees_A.txt
@@ -0,0 +1,5 @@
+A TREE
+├╼ A TREE
+│ └╼ A TREE
+└╼ A TREE
+ └╼ A TREE \ No newline at end of file
diff --git a/examples/out/multiple_trees_B.txt b/examples/out/multiple_trees_B.txt
new file mode 100644
index 0000000..b2cce80
--- /dev/null
+++ b/examples/out/multiple_trees_B.txt
@@ -0,0 +1,7 @@
+B TREE
+├──> B TREE
+│ ╰──> B TREE
+├──> B TREE
+│ ╰──> B TREE
+╰──> B TREE
+ ╰──> B TREE \ No newline at end of file
diff --git a/examples/out/nested.txt b/examples/out/nested.txt
new file mode 100644
index 0000000..f629559
--- /dev/null
+++ b/examples/out/nested.txt
@@ -0,0 +1,6 @@
+a
+├╼ b
+│ └╼ c
+│ └╼ Nothing to see here
+└╼ c
+ └╼ Nothing to see here \ No newline at end of file
diff --git a/examples/out/no_macros.txt b/examples/out/no_macros.txt
new file mode 100644
index 0000000..9ee6b47
--- /dev/null
+++ b/examples/out/no_macros.txt
@@ -0,0 +1,3 @@
+1 Branch
+└╼ 1.1 Child
+2 Sibling \ No newline at end of file
diff --git a/examples/out/panic.txt b/examples/out/panic.txt
new file mode 100644
index 0000000..3e5b29e
--- /dev/null
+++ b/examples/out/panic.txt
@@ -0,0 +1,6 @@
+By using the 'defer_' functions
+└╼ Output will still be generated
+ └╼ Otherwise you might lose your valuable tree!
+Now for something crazy...
+└╼ Here are my last words
+ └╼ Stay calm, and try not to panic \ No newline at end of file
diff --git a/examples/panic.rs b/examples/panic.rs
new file mode 100644
index 0000000..e1a9563
--- /dev/null
+++ b/examples/panic.rs
@@ -0,0 +1,20 @@
+use debug_tree::*;
+
+fn i_will_panic() {
+ add_branch!("Here are my last words");
+ add_leaf!("Stay calm, and try not to panic");
+ panic!("I told you so...")
+}
+
+fn main() {
+ // output to file at the end of this block
+ defer_write!("examples/out/panic.txt");
+ // print at the end of this block
+ {
+ add_branch!("By using the 'defer_' functions");
+ add_branch!("Output will still be generated");
+ add_branch!("Otherwise you might lose your valuable tree!");
+ }
+ add_branch!("Now for something crazy...");
+ i_will_panic();
+}
diff --git a/src/default.rs b/src/default.rs
new file mode 100644
index 0000000..75efbb8
--- /dev/null
+++ b/src/default.rs
@@ -0,0 +1,167 @@
+use crate::TreeBuilder;
+
+/// Returns the default tree for the current thread
+///
+/// # Example
+///
+/// ```
+/// use debug_tree::default_tree;
+/// default_tree().add_leaf("A new leaf");
+/// assert_eq!("A new leaf", default_tree().peek_string());
+/// ```
+pub fn default_tree() -> TreeBuilder {
+ thread_local! {
+ static DEFAULT_BUILDER: TreeBuilder = TreeBuilder::new();
+ }
+ DEFAULT_BUILDER.with(|f| f.clone())
+}
+
+/// Adds a leaf to the default tree with the given text and formatting arguments
+///
+/// # Arguments
+/// * `text...` - Formatted text arguments, as per `format!(...)`.
+///
+/// # Example
+///
+/// ```
+/// #[macro_use]
+/// use debug_tree::{default_tree, add_leaf};
+/// fn main() {
+/// add_leaf!("A {} leaf", "new");
+/// assert_eq!("A new leaf", &default_tree().peek_string());
+/// }
+/// ```
+#[macro_export]
+macro_rules! add_leaf {
+ ($($arg:tt)*) => {
+ if $crate::default::default_tree().is_enabled() {
+ $crate::default::default_tree().add_leaf(&format!($($arg)*))
+ }
+ };
+ }
+
+/// Adds the value as a leaf to the default tree.
+///
+/// Returns the given `value` argument.
+///
+/// # Arguments
+/// * `value` - An expression that implements the `Display` trait.
+///
+/// # Example
+///
+/// ```
+/// #[macro_use]
+/// use debug_tree::{default_tree, add_leaf_value};
+/// fn main() {
+/// let value = add_leaf_value!(10);
+/// assert_eq!("10", &default_tree().string());
+/// assert_eq!(10, value);
+/// }
+/// ```
+#[macro_export]
+macro_rules! add_leaf_value {
+ ($value:expr) => {{
+ let v = $value;
+ if $crate::default::default_tree().is_enabled() {
+ $crate::default::default_tree().add_leaf(&format!("{}", &v));
+ }
+ v
+ }};
+}
+
+/// Adds a scoped branch to the default tree with the given text and formatting arguments
+/// The branch will be exited at the end of the current block.
+///
+/// # Arguments
+/// * `text...` - Formatted text arguments, as per `format!(...)`.
+///
+/// # Example
+///
+/// ```
+/// #[macro_use]
+/// use debug_tree::{default_tree, add_branch, add_leaf};
+/// fn main() {
+/// {
+/// add_branch!("New {}", "Branch"); // _branch enters scope
+/// // tree is now pointed inside new branch.
+/// add_leaf!("Child of {}", "Branch");
+/// // Block ends, so tree exits the current branch.
+/// }
+/// add_leaf!("Sibling of {}", "Branch");
+/// assert_eq!("\
+/// New Branch
+/// └╼ Child of Branch
+/// Sibling of Branch" , &default_tree().string());
+/// }
+/// ```
+#[macro_export]
+macro_rules! add_branch {
+ () => {
+ let _debug_tree_branch = if $crate::default::default_tree().is_enabled() {
+ $crate::default::default_tree().enter_scoped()
+ } else {
+ $crate::scoped_branch::ScopedBranch::none()
+ };
+ };
+ ($($arg:tt)*) => {
+ let _debug_tree_branch = if $crate::default::default_tree().is_enabled() {
+ $crate::default::default_tree().add_branch(&format!($($arg)*))
+ } else {
+ $crate::scoped_branch::ScopedBranch::none()
+ };
+ };
+
+}
+
+#[cfg(test)]
+mod test {
+ use crate::default_tree;
+ use crate::*;
+
+ #[test]
+ fn unnamed_branch() {
+ add_leaf!("1");
+ add_branch!();
+ add_leaf!("1.1");
+ {
+ add_branch!();
+ add_leaf!("1.1.1");
+ }
+ add_leaf!("1.2");
+ default_tree().peek_print();
+ assert_eq!(
+ "\
+1
+├╼ 1.1
+│ └╼ 1.1.1
+└╼ 1.2",
+ default_tree().string()
+ );
+ }
+ #[test]
+ fn named_branch() {
+ add_branch!("11");
+ {
+ add_branch!("11.1");
+ add_leaf!("11.1.1");
+ }
+ add_leaf!("11.2");
+ default_tree().peek_print();
+ assert_eq!(
+ "\
+11
+├╼ 11.1
+│ └╼ 11.1.1
+└╼ 11.2",
+ default_tree().string()
+ );
+ }
+
+ #[test]
+ fn leaf_with_value() {
+ let value = add_leaf_value!(10);
+ default_tree().peek_print();
+ assert_eq!("10", default_tree().string());
+ assert_eq!(10, value);
+ }
+}
diff --git a/src/defer.rs b/src/defer.rs
new file mode 100644
index 0000000..2158a1d
--- /dev/null
+++ b/src/defer.rs
@@ -0,0 +1,46 @@
+use crate::TreeBuilder;
+
+/// A deferred function called with an argument, `TreeBuilder`
+pub struct DeferredFn<F: Fn(TreeBuilder) -> ()> {
+ tree: Option<TreeBuilder>,
+ action: Option<F>,
+}
+
+impl<F> DeferredFn<F>
+where
+ F: Fn(TreeBuilder) -> (),
+{
+ /// Create a new deferred function based on `tree`
+ pub fn new(tree: TreeBuilder, action: F) -> Self {
+ DeferredFn {
+ tree: Some(tree),
+ action: Some(action),
+ }
+ }
+ /// Create an empty deferred function
+ /// This does nothing when scope ends
+ pub fn none() -> Self {
+ DeferredFn {
+ tree: None,
+ action: None,
+ }
+ }
+
+ /// Disables the deferred function
+ /// This prevents the function from executing when the scope ends
+ pub fn cancel(&mut self) {
+ self.tree = None;
+ self.action = None;
+ }
+}
+
+impl<F> Drop for DeferredFn<F>
+where
+ F: Fn(TreeBuilder) -> (),
+{
+ fn drop(&mut self) {
+ if let (Some(x), Some(action)) = (&self.tree, &self.action) {
+ action(x.clone());
+ }
+ }
+}
diff --git a/src/internal.rs b/src/internal.rs
new file mode 100644
index 0000000..d8bf0a9
--- /dev/null
+++ b/src/internal.rs
@@ -0,0 +1,281 @@
+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")
+ }
+}
diff --git a/src/lib.rs b/src/lib.rs
new file mode 100644
index 0000000..3da09a2
--- /dev/null
+++ b/src/lib.rs
@@ -0,0 +1,805 @@
+use std::sync::{Arc, Mutex};
+
+#[macro_use]
+pub mod default;
+mod internal;
+pub mod scoped_branch;
+
+pub mod defer;
+mod test;
+pub mod tree_config;
+
+pub use default::default_tree;
+use once_cell::sync::Lazy;
+use scoped_branch::ScopedBranch;
+use std::collections::BTreeMap;
+use std::fs::File;
+use std::io::Write;
+
+pub use crate::tree_config::*;
+
+/// Reference wrapper for `TreeBuilderBase`
+#[derive(Debug, Clone)]
+pub struct TreeBuilder(Arc<Mutex<internal::TreeBuilderBase>>);
+
+impl TreeBuilder {
+ /// Returns a new `TreeBuilder` with an empty `Tree`.
+ ///
+ /// # Example
+ ///
+ /// ```
+ /// use debug_tree::TreeBuilder;
+ /// let tree = TreeBuilder::new();
+ /// ```
+ pub fn new() -> TreeBuilder {
+ TreeBuilder {
+ 0: Arc::new(Mutex::new(internal::TreeBuilderBase::new())),
+ }
+ }
+
+ /// Set the configuration override for displaying trees
+ ///
+ /// # Example
+ ///
+ /// ```
+ /// use debug_tree::{TreeBuilder, add_branch_to, add_leaf_to, TreeSymbols, TreeConfig};
+ /// let tree = TreeBuilder::new();
+ /// {
+ /// add_branch_to!(tree, "1");
+ /// {
+ /// add_branch_to!(tree, "1.1");
+ /// add_leaf_to!(tree, "1.1.1");
+ /// add_leaf_to!(tree, "1.1.2");
+ /// }
+ /// add_leaf_to!(tree, "1.2");
+ /// }
+ /// add_leaf_to!(tree, "2");
+ /// tree.set_config_override(TreeConfig::new()
+ /// .show_first_level()
+ /// .symbols(TreeSymbols::with_rounded()));
+ /// tree.peek_print();
+ /// assert_eq!("\
+ /// ├╼ 1
+ /// │ ├╼ 1.1
+ /// │ │ ├╼ 1.1.1
+ /// │ │ ╰╼ 1.1.2
+ /// │ ╰╼ 1.2
+ /// ╰╼ 2" , &tree.string());
+ /// ```
+ pub fn set_config_override(&self, config: TreeConfig) {
+ let mut lock = self.0.lock().unwrap();
+ lock.set_config_override(Some(config))
+ }
+
+ /// Remove the configuration override
+ /// The default configuration will be used instead
+ pub fn remove_config_override(&self) {
+ self.0.lock().unwrap().set_config_override(None);
+ }
+
+ /// Update the configuration override for displaying trees
+ /// If an override doesn't yet exist, it is created.
+ ///
+ /// # Example
+ ///
+ /// ```
+ /// use debug_tree::{TreeBuilder, add_branch_to, add_leaf_to, TreeSymbols};
+ /// let tree = TreeBuilder::new();
+ /// {
+ /// add_branch_to!(tree, "1");
+ /// {
+ /// add_branch_to!(tree, "1.1");
+ /// add_leaf_to!(tree, "1.1.1");
+ /// add_leaf_to!(tree, "1.1.2");
+ /// }
+ /// add_leaf_to!(tree, "1.2");
+ /// }
+ /// add_leaf_to!(tree, "2");
+ /// tree.update_config_override(|x|{
+ /// x.indent = 3;
+ /// x.symbols = TreeSymbols::with_rounded();
+ /// x.show_first_level = true;
+ /// });
+ /// tree.peek_print();
+ /// assert_eq!("\
+ /// ├─╼ 1
+ /// │ ├─╼ 1.1
+ /// │ │ ├─╼ 1.1.1
+ /// │ │ ╰─╼ 1.1.2
+ /// │ ╰─╼ 1.2
+ /// ╰─╼ 2" , &tree.string());
+ /// ```
+ pub fn update_config_override<F: Fn(&mut TreeConfig)>(&self, update: F) {
+ let mut lock = self.0.lock().unwrap();
+ match lock.config_override_mut() {
+ Some(x) => update(x),
+ None => {
+ let mut x = TreeConfig::default();
+ update(&mut x);
+ lock.set_config_override(Some(x));
+ }
+ }
+ }
+
+ /// Returns the optional configuration override.
+ pub fn get_config_override(&self) -> Option<TreeConfig> {
+ let lock = self.0.lock().unwrap();
+ lock.config_override().clone()
+ }
+
+ /// Returns whether a configuration override is set.
+ pub fn has_config_override(&self) -> bool {
+ let lock = self.0.lock().unwrap();
+ lock.config_override().is_some()
+ }
+
+ /// Adds a new branch with text, `text` and returns a `ScopedBranch`.
+ /// When the returned `ScopedBranch` goes out of scope, (likely the end of the current block),
+ /// or if its `release()` method is called, the tree will step back out of the added branch.
+ ///
+ /// # Arguments
+ /// * `text` - A string slice to use as the newly added branch's text.
+ ///
+ /// # Examples
+ ///
+ /// Exiting branch when end of scope is reached.
+ /// ```
+ /// use debug_tree::TreeBuilder;
+ /// let tree = TreeBuilder::new();
+ /// {
+ /// let _branch = tree.add_branch("Branch"); // _branch enters scope
+ /// // tree is now pointed inside new branch.
+ /// tree.add_leaf("Child of Branch");
+ /// // _branch leaves scope, tree moves up to parent branch.
+ /// }
+ /// tree.add_leaf("Sibling of Branch");
+ /// assert_eq!("\
+ /// Branch
+ /// └╼ Child of Branch
+ /// Sibling of Branch" , &tree.string());
+ /// ```
+ ///
+ /// Using `release()` before out of scope.
+ /// ```
+ /// use debug_tree::TreeBuilder;
+ /// let tree = TreeBuilder::new();
+ /// {
+ /// let mut branch = tree.add_branch("Branch"); // branch enters scope
+ /// // tree is now pointed inside new branch.
+ /// tree.add_leaf("Child of Branch");
+ /// branch.release();
+ /// tree.add_leaf("Sibling of Branch");
+ /// // branch leaves scope, but no effect because its `release()` method has already been called
+ /// }
+ /// assert_eq!("\
+ /// Branch
+ /// └╼ Child of Branch
+ /// Sibling of Branch", &tree.string());
+ /// ```
+ pub fn add_branch(&self, text: &str) -> ScopedBranch {
+ self.add_leaf(text);
+ ScopedBranch::new(self.clone())
+ }
+
+ /// Adds a new branch with text, `text` and returns a `ScopedBranch`.
+ /// When the returned `ScopedBranch` goes out of scope, (likely the end of the current block),
+ /// or if its `release()` method is called, the tree tree will step back out of the added branch.
+ ///
+ /// # Arguments
+ /// * `text` - A string slice to use as the newly added branch's text.
+ ///
+ /// # Examples
+ ///
+ /// Stepping out of branch when end of scope is reached.
+ /// ```
+ /// use debug_tree::TreeBuilder;
+ /// let tree = TreeBuilder::new();
+ /// {
+ /// tree.add_leaf("Branch");
+ /// let _branch = tree.enter_scoped(); // _branch enters scope
+ /// // tree is now pointed inside new branch.
+ /// tree.add_leaf("Child of Branch");
+ /// // _branch leaves scope, tree moves up to parent branch.
+ /// }
+ /// tree.add_leaf("Sibling of Branch");
+ /// assert_eq!("\
+ /// Branch
+ /// └╼ Child of Branch
+ /// Sibling of Branch", &tree.string());
+ /// ```
+ ///
+ /// Using `release()` before out of scope.
+ /// ```
+ /// use debug_tree::TreeBuilder;
+ /// let tree = TreeBuilder::new();
+ /// {
+ /// tree.add_leaf("Branch");
+ /// let mut branch = tree.enter_scoped(); // branch enters scope
+ /// // tree is now pointed inside new branch.
+ /// tree.add_leaf("Child of Branch");
+ /// branch.release();
+ /// tree.add_leaf("Sibling of Branch");
+ /// // branch leaves scope, but no effect because its `release()` method has already been called
+ /// }
+ /// assert_eq!("\
+ /// Branch
+ /// └╼ Child of Branch
+ /// Sibling of Branch", &tree.string());
+ /// ```
+ pub fn enter_scoped(&self) -> ScopedBranch {
+ if self.is_enabled() {
+ ScopedBranch::new(self.clone())
+ } else {
+ ScopedBranch::none()
+ }
+ }
+
+ /// Adds a leaf to current branch with the given text, `text`.
+ ///
+ /// # Arguments
+ /// * `text` - A string slice to use as the newly added leaf's text.
+ ///
+ /// # Example
+ ///
+ /// ```
+ /// use debug_tree::TreeBuilder;
+ /// let tree = TreeBuilder::new();
+ /// tree.add_leaf("New leaf");
+ /// ```
+ pub fn add_leaf(&self, text: &str) {
+ let mut x = self.0.lock().unwrap();
+ if x.is_enabled() {
+ x.add_leaf(&text);
+ }
+ }
+
+ /// Steps into a new child branch.
+ /// Stepping out of the branch requires calling `exit()`.
+ ///
+ /// # Example
+ /// ```
+ /// use debug_tree::TreeBuilder;
+ /// let tree = TreeBuilder::new();
+ /// tree.add_leaf("Branch");
+ /// tree.enter();
+ /// tree.add_leaf("Child of Branch");
+ /// assert_eq!("\
+ /// Branch
+ /// └╼ Child of Branch", &tree.string());
+ /// ```
+ pub fn enter(&self) {
+ let mut x = self.0.lock().unwrap();
+ if x.is_enabled() {
+ x.enter();
+ }
+ }
+
+ /// Exits the current branch, to the parent branch.
+ /// If no parent branch exists, no action is taken
+ ///
+ /// # Example
+ ///
+ /// ```
+ /// use debug_tree::TreeBuilder;
+ /// let tree = TreeBuilder::new();
+ /// tree.add_leaf("Branch");
+ /// tree.enter();
+ /// tree.add_leaf("Child of Branch");
+ /// tree.exit();
+ /// tree.add_leaf("Sibling of Branch");
+ /// assert_eq!("\
+ /// Branch
+ /// └╼ Child of Branch
+ /// Sibling of Branch", &tree.string());
+ /// ```
+ pub fn exit(&self) -> bool {
+ let mut x = self.0.lock().unwrap();
+ if x.is_enabled() {
+ x.exit()
+ } else {
+ false
+ }
+ }
+
+ /// Returns the depth of the current branch
+ /// The initial depth when no branches have been adeed is 0.
+ ///
+ /// # Example
+ ///
+ /// ```
+ /// use debug_tree::TreeBuilder;
+ /// let tree = TreeBuilder::new();
+ /// assert_eq!(0, tree.depth());
+ /// let _b = tree.add_branch("Branch");
+ /// assert_eq!(1, tree.depth());
+ /// let _b = tree.add_branch("Child branch");
+ /// assert_eq!(2, tree.depth());
+ /// ```
+ pub fn depth(&self) -> usize {
+ self.0.lock().unwrap().depth()
+ }
+
+ /// Prints the tree without clearing.
+ ///
+ /// # Example
+ ///
+ /// ```
+ /// use debug_tree::TreeBuilder;
+ /// let tree = TreeBuilder::new();
+ /// tree.add_leaf("Leaf");
+ /// tree.peek_print();
+ /// // Leaf
+ /// tree.peek_print();
+ /// // Leaf
+ /// // Leaf 2
+ /// ```
+ pub fn peek_print(&self) {
+ self.0.lock().unwrap().peek_print();
+ }
+
+ /// Prints the tree and then clears it.
+ ///
+ /// # Example
+ ///
+ /// ```
+ /// use debug_tree::TreeBuilder;
+ /// let tree = TreeBuilder::new();
+ /// tree.add_leaf("Leaf");
+ /// tree.print();
+ /// // Leaf
+ /// tree.add_leaf("Leaf 2");
+ /// tree.print();
+ /// // Leaf 2
+ /// ```
+ pub fn print(&self) {
+ self.0.lock().unwrap().print();
+ }
+
+ /// Returns the tree as a string without clearing the tree.
+ ///
+ /// # Example
+ ///
+ /// ```
+ /// use debug_tree::TreeBuilder;
+ /// let tree = TreeBuilder::new();
+ /// tree.add_leaf("Leaf");
+ /// assert_eq!("Leaf", tree.peek_string());
+ /// tree.add_leaf("Leaf 2");
+ /// assert_eq!("Leaf\nLeaf 2", tree.peek_string());
+ /// ```
+ pub fn peek_string(&self) -> String {
+ self.0.lock().unwrap().peek_string()
+ }
+
+ /// Returns the tree as a string and clears the tree.
+ ///
+ /// # Example
+ ///
+ /// ```
+ /// use debug_tree::TreeBuilder;
+ /// let tree = TreeBuilder::new();
+ /// tree.add_leaf("Leaf");
+ /// assert_eq!("Leaf", tree.string());
+ /// tree.add_leaf("Leaf 2");
+ /// assert_eq!("Leaf 2", tree.string());
+ /// ```
+ pub fn string(&self) -> String {
+ self.0.lock().unwrap().string()
+ }
+
+ /// Writes the tree to file without clearing.
+ ///
+ /// # Example
+ ///
+ /// ```
+ /// use debug_tree::TreeBuilder;
+ /// use std::fs::{read_to_string, create_dir};
+ /// use std::io::Read;
+ /// let tree = TreeBuilder::new();
+ /// create_dir("test_out").ok();
+ /// tree.add_leaf("Leaf");
+ /// assert_eq!(tree.peek_string(), "Leaf");
+ /// tree.peek_write("test_out/peek_write.txt");
+ /// assert_eq!(read_to_string("test_out/peek_write.txt").unwrap(), "Leaf");
+ /// assert_eq!(tree.peek_string(), "Leaf");
+ /// ```
+ pub fn peek_write(&self, path: &str) -> std::io::Result<()> {
+ let mut file = File::create(path)?;
+ file.write_all(self.peek_string().as_bytes())
+ }
+
+ /// Writes the tree to file without clearing.
+ ///
+ /// # Example
+ ///
+ /// ```
+ /// use debug_tree::TreeBuilder;
+ /// use std::io::Read;
+ /// use std::fs::{read_to_string, create_dir};
+ /// let tree = TreeBuilder::new();
+ /// create_dir("test_out").ok();
+ /// tree.add_leaf("Leaf");
+ /// assert_eq!(tree.peek_string(), "Leaf");
+ /// tree.write("test_out/write.txt");
+ /// assert_eq!(read_to_string("test_out/write.txt").unwrap(), "Leaf");
+ /// assert_eq!(tree.peek_string(), "");
+ /// ```
+ pub fn write(&self, path: &str) -> std::io::Result<()> {
+ let mut file = File::create(path)?;
+ file.write_all(self.string().as_bytes())
+ }
+
+ /// Clears the tree.
+ ///
+ /// # Example
+ ///
+ /// ```
+ /// use debug_tree::TreeBuilder;
+ /// let tree = TreeBuilder::new();
+ /// tree.add_leaf("Leaf");
+ /// assert_eq!("Leaf", tree.peek_string());
+ /// tree.clear();
+ /// assert_eq!("", tree.peek_string());
+ /// ```
+ pub fn clear(&self) {
+ self.0.lock().unwrap().clear()
+ }
+
+ /// Sets the enabled state of the tree.
+ ///
+ /// If not enabled, the tree will not be modified by adding leaves or branches.
+ /// Additionally, if called using the `add_`... macros, arguments will not be processed.
+ /// This is particularly useful for suppressing output in production, with very little overhead.
+ ///
+ /// # Example
+ /// ```
+ /// #[macro_use]
+ /// use debug_tree::{TreeBuilder, add_leaf_to};
+ /// let mut tree = TreeBuilder::new();
+ /// tree.add_leaf("Leaf 1");
+ /// tree.set_enabled(false);
+ /// add_leaf_to!(tree, "Leaf 2");
+ /// tree.set_enabled(true);
+ /// add_leaf_to!(tree, "Leaf 3");
+ /// assert_eq!("Leaf 1\nLeaf 3", tree.peek_string());
+ /// ```
+ pub fn set_enabled(&self, enabled: bool) {
+ self.0.lock().unwrap().set_enabled(enabled);
+ }
+
+ /// Returns the enabled state of the tree.
+ ///
+ /// # Example
+ /// ```
+ /// use debug_tree::TreeBuilder;
+ /// let mut tree = TreeBuilder::new();
+ /// assert_eq!(true, tree.is_enabled());
+ /// tree.set_enabled(false);
+ /// assert_eq!(false, tree.is_enabled());
+ /// ```
+ pub fn is_enabled(&self) -> bool {
+ self.0.lock().unwrap().is_enabled()
+ }
+}
+
+pub trait AsTree {
+ fn as_tree(&self) -> TreeBuilder;
+ fn is_tree_enabled(&self) -> bool {
+ self.as_tree().is_enabled()
+ }
+}
+
+impl AsTree for TreeBuilder {
+ fn as_tree(&self) -> TreeBuilder {
+ self.clone()
+ }
+}
+
+pub(crate) fn get_or_add_tree<T: AsRef<str>>(name: T) -> TreeBuilder {
+ let mut map = TREE_MAP.lock().unwrap();
+ match map.get(name.as_ref()) {
+ Some(x) => x.clone(),
+ _ => {
+ let val = TreeBuilder::new();
+ map.insert(name.as_ref().to_string(), val.clone());
+ val
+ }
+ }
+}
+
+pub(crate) fn get_tree<T: AsRef<str>>(name: T) -> Option<TreeBuilder> {
+ TREE_MAP.lock().unwrap().get(name.as_ref()).cloned()
+}
+
+type TreeMap = BTreeMap<String, TreeBuilder>;
+
+static TREE_MAP: Lazy<Arc<Mutex<TreeMap>>> =
+ Lazy::new(|| -> Arc<Mutex<TreeMap>> { Arc::new(Mutex::new(TreeMap::new())) });
+
+/// Sets the enabled state of the tree.
+///
+/// # Arguments
+/// * `name` - The tree name
+/// * `enabled` - The enabled state
+///
+pub fn set_enabled<T: AsRef<str>>(name: T, enabled: bool) {
+ let mut map = TREE_MAP.lock().unwrap();
+ match map.get_mut(name.as_ref()) {
+ Some(x) => x.set_enabled(enabled),
+ _ => {
+ let tree = TreeBuilder::new();
+ tree.set_enabled(enabled);
+ map.insert(name.as_ref().to_string(), tree);
+ }
+ }
+}
+
+impl<T: AsRef<str>> AsTree for T {
+ fn as_tree(&self) -> TreeBuilder {
+ get_or_add_tree(self)
+ }
+ /// Check if the named tree is enabled and exists
+ /// This does not create a new tree if non-existent
+ ///
+ /// # Arguments
+ /// * `tree_name` - The tree name
+ ///
+ fn is_tree_enabled(&self) -> bool {
+ get_tree(self).map(|x| x.is_enabled()).unwrap_or(false)
+ }
+}
+
+/// Returns the tree
+/// If there is no tree then one is created and then returned.
+pub fn tree<T: AsTree>(tree: T) -> TreeBuilder {
+ tree.as_tree()
+}
+
+/// Returns the tree named `name`
+/// If there is no tree named `name` then one is created and then returned.
+pub fn is_tree_enabled<T: AsTree>(tree: &T) -> bool {
+ tree.is_tree_enabled()
+}
+
+/// Calls [clear](TreeBuilder::clear) for the tree named `name`
+/// If there is no tree named `name` then one is created
+pub fn clear<T: AsRef<str>>(name: T) {
+ name.as_tree().clear();
+}
+
+/// Returns [string](TreeBuilder::string) for the tree named `name`
+/// If there is no tree named `name` then one is created
+pub fn string<T: AsRef<str>>(name: T) -> String {
+ name.as_tree().string()
+}
+
+/// Returns [peek_string](TreeBuilder::peek_string) for the tree named `name`
+/// If there is no tree named `name` then one is created
+pub fn peek_string<T: AsRef<str>>(name: T) -> String {
+ name.as_tree().peek_string()
+}
+
+/// Calls [print](TreeBuilder::print) for the tree named `name`
+/// If there is no tree named `name` then one is created
+pub fn print<T: AsRef<str>>(name: T) {
+ name.as_tree().print();
+}
+
+/// Calls [peek_print](TreeBuilder::peek_print) for the tree named `name`
+/// If there is no tree named `name` then one is created
+pub fn peek_print<T: AsRef<str>>(name: T) {
+ name.as_tree().peek_print();
+}
+
+/// Calls [write](TreeBuilder::write) for the tree named `name`
+/// If there is no tree named `name` then one is created
+pub fn write<T: AsRef<str>, P: AsRef<str>>(name: T, path: P) -> std::io::Result<()> {
+ name.as_tree().write(path.as_ref())
+}
+
+/// Calls [peek_print](TreeBuilder::peek_print) for the tree named `name`
+/// If there is no tree named `name` then one is created
+pub fn peek_write<T: AsRef<str>, P: AsRef<str>>(name: T, path: P) -> std::io::Result<()> {
+ name.as_tree().peek_write(path.as_ref())
+}
+
+/// Adds a leaf to given tree with the given text and formatting arguments
+///
+/// # Arguments
+/// * `tree` - The tree that the leaf should be added to
+/// * `text...` - Formatted text arguments, as per `format!(...)`.
+///
+/// # Example
+///
+/// ```
+/// #[macro_use]
+/// use debug_tree::{TreeBuilder, add_leaf_to};
+/// fn main() {
+/// let tree = TreeBuilder::new();
+/// add_leaf_to!(tree, "A {} leaf", "new");
+/// assert_eq!("A new leaf", &tree.peek_string());
+/// }
+/// ```
+#[macro_export]
+macro_rules! add_leaf_to {
+ ($tree:expr, $($arg:tt)*) => (if $crate::is_tree_enabled(&$tree) {
+ use $crate::AsTree;
+ $tree.as_tree().add_leaf(&format!($($arg)*))
+ });
+}
+
+/// Adds a leaf to given tree with the given `value` argument
+///
+/// # Arguments
+/// * `tree` - The tree that the leaf should be added to
+/// * `value` - An expression that implements the `Display` trait.
+///
+/// # Example
+///
+/// ```
+/// #[macro_use]
+/// use debug_tree::{TreeBuilder, add_leaf_value_to};
+/// fn main() {
+/// let tree = TreeBuilder::new();
+/// let value = add_leaf_value_to!(tree, 5 * 4 * 3 * 2);
+/// assert_eq!(120, value);
+/// assert_eq!("120", &tree.peek_string());
+/// }
+/// ```
+#[macro_export]
+macro_rules! add_leaf_value_to {
+ ($tree:expr, $value:expr) => {{
+ let v = $value;
+ if $crate::is_tree_enabled(&$tree) {
+ use $crate::AsTree;
+ $tree.as_tree().add_leaf(&format!("{}", &v));
+ }
+ v
+ }};
+}
+
+/// Adds a scoped branch to given tree with the given text and formatting arguments
+/// The branch will be exited at the end of the current block.
+///
+/// # Arguments
+/// * `tree` - The tree that the leaf should be added to
+/// * `text...` - Formatted text arguments, as per `format!(...)`.
+///
+/// # Example
+///
+/// ```
+/// #[macro_use]
+/// use debug_tree::{TreeBuilder, add_branch_to, add_leaf_to};
+/// fn main() {
+/// let tree = TreeBuilder::new();
+/// {
+/// add_branch_to!(tree, "New {}", "Branch"); // _branch enters scope
+/// // tree is now pointed inside new branch.
+/// add_leaf_to!(tree, "Child of {}", "Branch");
+/// // Block ends, so tree exits the current branch.
+/// }
+/// add_leaf_to!(tree, "Sibling of {}", "Branch");
+/// assert_eq!("\
+/// New Branch
+/// └╼ Child of Branch
+/// Sibling of Branch" , &tree.string());
+/// }
+/// ```
+#[macro_export]
+macro_rules! add_branch_to {
+ ($tree:expr) => {
+ let _debug_tree_branch = if $crate::is_tree_enabled(&$tree) {
+ use $crate::AsTree;
+ $tree.as_tree().enter_scoped()
+ } else {
+ $crate::scoped_branch::ScopedBranch::none()
+ };
+ };
+ ($tree:expr, $($arg:tt)*) => {
+ let _debug_tree_branch = if $crate::is_tree_enabled(&$tree) {
+ use $crate::AsTree;
+ $tree.as_tree().add_branch(&format!($($arg)*))
+ } else {
+ $crate::scoped_branch::ScopedBranch::none()
+ };
+ };
+}
+
+/// Calls `function` with argument, `tree`, at the end of the current scope
+/// The function will only be executed if the tree is enabled when this macro is called
+#[macro_export]
+macro_rules! defer {
+ ($function:expr) => {
+ let _debug_tree_defer = {
+ use $crate::AsTree;
+ if $crate::default::default_tree().is_enabled() {
+ use $crate::AsTree;
+ $crate::defer::DeferredFn::new($crate::default::default_tree(), $function)
+ } else {
+ $crate::defer::DeferredFn::none()
+ }
+ };
+ };
+ ($tree:expr, $function:expr) => {
+ let _debug_tree_defer = {
+ use $crate::AsTree;
+ if $tree.as_tree().is_enabled() {
+ $crate::defer::DeferredFn::new($tree.as_tree(), $function)
+ } else {
+ $crate::defer::DeferredFn::none()
+ }
+ };
+ };
+}
+
+/// Calls [print](TreeBuilder::print) on `tree` at the end of the current scope.
+/// The function will only be executed if the tree is enabled when this macro is called
+#[macro_export]
+macro_rules! defer_print {
+ () => {
+ $crate::defer!(|x| {
+ x.print();
+ })
+ };
+ ($tree:expr) => {
+ $crate::defer!($tree, |x| {
+ x.print();
+ })
+ };
+}
+
+/// Calls [peek_print](TreeBuilder::peek_print) on `tree` at the end of the current scope.
+/// The function will only be executed if the tree is enabled when this macro is called
+#[macro_export]
+macro_rules! defer_peek_print {
+ () => {
+ $crate::defer!(|x| {
+ x.peek_print();
+ })
+ };
+ ($tree:expr) => {
+ $crate::defer!($tree, |x| {
+ x.peek_print();
+ })
+ };
+}
+
+/// Calls [write](TreeBuilder::write) on `tree` at the end of the current scope.
+/// The function will only be executed if the tree is enabled when this macro is called
+#[macro_export]
+macro_rules! defer_write {
+ ($tree:expr, $path:expr) => {
+ $crate::defer!($tree, |x| {
+ if let Err(err) = x.write($path) {
+ eprintln!("error during `defer_write`: {}", err);
+ }
+ })
+ };
+ ($path:expr) => {
+ $crate::defer!(|x| {
+ if let Err(err) = x.write($path) {
+ eprintln!("error during `defer_write`: {}", err);
+ }
+ })
+ };
+}
+
+/// Calls [peek_write](TreeBuilder::peek_write) on `tree` at the end of the current scope.
+/// The function will only be executed if the tree is enabled when this macro is called
+#[macro_export]
+macro_rules! defer_peek_write {
+ ($tree:expr, $path:expr) => {
+ $crate::defer!($tree, |x| {
+ if let Err(err) = x.peek_write($path) {
+ eprintln!("error during `defer_peek_write`: {}", err);
+ }
+ })
+ };
+ ($path:expr) => {
+ $crate::defer!(|x| {
+ if let Err(err) = x.peek_write($path) {
+ eprintln!("error during `defer_peek_write`: {}", err);
+ }
+ })
+ };
+}
diff --git a/src/scoped_branch.rs b/src/scoped_branch.rs
new file mode 100644
index 0000000..63aadb4
--- /dev/null
+++ b/src/scoped_branch.rs
@@ -0,0 +1,26 @@
+use crate::TreeBuilder;
+
+pub struct ScopedBranch {
+ state: Option<TreeBuilder>,
+}
+
+impl ScopedBranch {
+ pub fn new(state: TreeBuilder) -> ScopedBranch {
+ state.enter();
+ ScopedBranch { state: Some(state) }
+ }
+ pub fn none() -> ScopedBranch {
+ ScopedBranch { state: None }
+ }
+ pub fn release(&mut self) {
+ if let Some(x) = &self.state {
+ x.exit();
+ }
+ self.state = None;
+ }
+}
+impl Drop for ScopedBranch {
+ fn drop(&mut self) {
+ self.release();
+ }
+}
diff --git a/src/test.rs b/src/test.rs
new file mode 100644
index 0000000..40887ce
--- /dev/null
+++ b/src/test.rs
@@ -0,0 +1,596 @@
+#[cfg(test)]
+mod test {
+ use crate::*;
+ use futures::future::join5;
+ use std::cmp::{max, min};
+ use std::fs::{create_dir, read_to_string, remove_file};
+
+ #[test]
+ fn test_branch() {
+ let d: TreeBuilder = TreeBuilder::new();
+ d.add_leaf("1");
+ {
+ let _l = d.enter_scoped();
+ d.add_leaf("1.1");
+ d.add_leaf("1.2");
+ }
+ d.add_leaf("2");
+ d.add_leaf("3");
+ let _l = d.enter_scoped();
+ d.add_leaf("3.1");
+ d.add_leaf("3.2");
+ d.peek_print();
+ assert_eq!(
+ "\
+1
+├╼ 1.1
+└╼ 1.2
+2
+3
+├╼ 3.1
+└╼ 3.2",
+ d.string()
+ );
+ }
+
+ #[test]
+ fn test_branch2() {
+ let d = TreeBuilder::new();
+ d.add_leaf("1");
+ {
+ let _scope = d.enter_scoped();
+ d.add_leaf("1.1");
+ {
+ let _scope = d.enter_scoped();
+ d.add_leaf("1.1.1");
+ }
+ }
+
+ d.add_leaf("2");
+ d.enter();
+ d.add_leaf("2.1");
+ d.enter();
+ d.add_leaf("2.1.1");
+ d.peek_print();
+ assert_eq!(
+ "\
+1
+└╼ 1.1
+ └╼ 1.1.1
+2
+└╼ 2.1
+ └╼ 2.1.1",
+ d.string()
+ );
+ }
+
+ #[test]
+ fn simple() {
+ let d = TreeBuilder::new();
+ d.add_leaf("Hi");
+ assert_eq!("Hi", d.string());
+ }
+
+ #[test]
+ fn depth() {
+ let d = TreeBuilder::new();
+ assert_eq!(0, d.depth());
+ d.add_leaf("Hi");
+ assert_eq!(0, d.depth());
+ let _b = d.add_branch("Hi");
+ assert_eq!(1, d.depth());
+ d.add_leaf("Hi");
+ assert_eq!(1, d.depth());
+ }
+
+ #[test]
+ fn indent() {
+ let d = TreeBuilder::new();
+ d.add_leaf("1");
+ add_branch_to!(d);
+ d.add_leaf("1.1");
+ {
+ add_branch_to!(d);
+ d.add_leaf("1.1.1");
+ }
+ d.set_config_override(TreeConfig::new().indent(4));
+ d.peek_print();
+ assert_eq!(
+ "\
+1
+└──╼ 1.1
+ └──╼ 1.1.1",
+ d.string()
+ );
+ }
+
+ #[test]
+ fn macros() {
+ let d = TreeBuilder::new();
+ add_leaf_to!(d, "1");
+ {
+ add_branch_to!(d);
+ add_leaf_to!(d, "1.1")
+ }
+ d.peek_print();
+ assert_eq!(
+ "\
+1
+└╼ 1.1",
+ d.string()
+ );
+ }
+
+ #[test]
+ fn macros_with_fn() {
+ let d = TreeBuilder::new();
+ let tree = || d.clone();
+ add_leaf_to!(tree(), "1");
+ {
+ add_branch_to!(tree());
+ add_leaf_to!(tree(), "1.1")
+ }
+ tree().peek_print();
+ assert_eq!(
+ "\
+1
+└╼ 1.1",
+ d.string()
+ );
+ }
+
+ #[test]
+ fn leaf_with_value() {
+ let d = TreeBuilder::new();
+ let value = add_leaf_value_to!(d, 1);
+ d.peek_print();
+ assert_eq!("1", d.string());
+ assert_eq!(1, value);
+ }
+
+ #[test]
+ fn macros2() {
+ let d = TreeBuilder::new();
+ add_branch_to!(d, "1");
+ add_leaf_to!(d, "1.1");
+ d.peek_print();
+ assert_eq!(
+ "\
+1
+└╼ 1.1",
+ d.string()
+ );
+ }
+
+ #[test]
+ fn mid() {
+ let d = TreeBuilder::new();
+ d.add_leaf(&format!("{}{}", "1", "0"));
+ d.enter();
+ d.add_leaf("10.1");
+ d.add_leaf("10.2");
+ d.enter();
+ d.add_leaf("10.1.1");
+ d.add_leaf("10.1.2\nNext line");
+ d.exit();
+ d.add_leaf(&format!("10.3"));
+ d.peek_print();
+ assert_eq!(
+ "\
+10
+├╼ 10.1
+├╼ 10.2
+│ ├╼ 10.1.1
+│ └╼ 10.1.2
+│ Next line
+└╼ 10.3",
+ d.string()
+ );
+ }
+
+ fn factors(x: usize) {
+ add_branch!("{}", x);
+ for i in 1..x {
+ if x % i == 0 {
+ factors(i);
+ }
+ }
+ }
+
+ #[test]
+ fn recursive() {
+ factors(6);
+ default_tree().peek_print();
+ assert_eq!(
+ "\
+6
+├╼ 1
+├╼ 2
+│ └╼ 1
+└╼ 3
+ └╼ 1",
+ default_tree().string()
+ );
+ }
+
+ fn a() {
+ add_branch!("a");
+ b();
+ c();
+ }
+
+ fn b() {
+ add_branch!("b");
+ c();
+ }
+
+ fn c() {
+ add_branch!("c");
+ add_leaf!("Nothing to see here");
+ }
+
+ #[test]
+ fn nested() {
+ a();
+ default_tree().peek_print();
+ assert_eq!(
+ "\
+a
+├╼ b
+│ └╼ c
+│ └╼ Nothing to see here
+└╼ c
+ └╼ Nothing to see here",
+ default_tree().string()
+ );
+ }
+
+ #[test]
+ fn disabled_output() {
+ let tree = TreeBuilder::new();
+ tree.set_enabled(false);
+ add_leaf_to!(tree, "Leaf");
+ tree.add_leaf("Leaf");
+
+ add_branch_to!(tree, "Branch");
+ tree.add_branch("Branch");
+ assert_eq!("", tree.string());
+ }
+
+ #[test]
+ fn enabled_output() {
+ let tree = TreeBuilder::new();
+ tree.set_enabled(false);
+ add_branch_to!(tree, "Ignored branch");
+ add_leaf_to!(tree, "Ignored leaf");
+ tree.set_enabled(true);
+ add_leaf_to!(tree, "Leaf");
+ tree.add_leaf("Leaf");
+
+ add_branch_to!(tree, "Branch");
+ tree.add_branch("Branch");
+ assert_eq!(
+ "Leaf
+Leaf
+Branch
+└╼ Branch",
+ tree.string()
+ );
+ }
+
+ #[test]
+ fn tree_by_name() {
+ clear("A");
+ let b = tree("B");
+ b.clear();
+ {
+ add_branch_to!("A", "1");
+ add_branch_to!(b, "3");
+ add_leaf_to!("A", "1.1");
+ add_leaf_to!("B", "3.1");
+ }
+ add_leaf_to!("A", "2");
+ peek_print("A");
+ b.peek_print();
+ assert_eq!(
+ "1
+└╼ 1.1
+2",
+ string("A")
+ );
+ assert_eq!(
+ "3
+└╼ 3.1",
+ b.string()
+ );
+ }
+
+ #[test]
+ fn tree_by_name_disabled() {
+ let d = tree("D");
+ d.clear();
+ d.set_enabled(true);
+ clear("C");
+ set_enabled("C", false);
+ {
+ add_branch_to!("C", "1");
+ set_enabled("C", true);
+ add_branch_to!(d, "3");
+ add_leaf_to!("C", "1.1");
+ d.set_enabled(false);
+ add_leaf_to!("D", "3.1");
+ }
+ add_leaf_to!("C", "2");
+ peek_print("C");
+ d.peek_print();
+ assert_eq!(
+ "1.1
+2",
+ string("C")
+ );
+ assert_eq!("3", d.string());
+ }
+
+ #[test]
+ fn defer_write() {
+ let tree = TreeBuilder::new();
+ {
+ create_dir("test_out").ok();
+ remove_file("test_out/defer_write.txt").ok();
+ File::create("test_out/defer_write.txt").unwrap();
+ defer_write!(tree, "test_out/defer_write.txt");
+ tree.add_leaf("Branch");
+ assert_eq!(read_to_string("test_out/defer_write.txt").unwrap(), "");
+ assert_eq!(tree.peek_string(), "Branch");
+ }
+ assert_eq!(tree.peek_string(), "");
+ assert_eq!(
+ read_to_string("test_out/defer_write.txt").unwrap(),
+ "Branch"
+ );
+ }
+
+ #[test]
+ fn defer_peek_write() {
+ let tree = TreeBuilder::new();
+ {
+ create_dir("test_out").ok();
+ remove_file("test_out/defer_peek_write.txt").ok();
+ File::create("test_out/defer_peek_write.txt").unwrap();
+ defer_peek_write!(tree, "test_out/defer_peek_write.txt");
+ tree.add_leaf("Branch");
+ assert_eq!(read_to_string("test_out/defer_peek_write.txt").unwrap(), "");
+ assert_eq!(tree.peek_string(), "Branch");
+ }
+ assert_eq!(tree.peek_string(), "Branch");
+ assert_eq!(
+ read_to_string("test_out/defer_peek_write.txt").unwrap(),
+ "Branch"
+ );
+ }
+
+ #[test]
+ #[should_panic]
+ #[allow(unreachable_code)]
+ fn defer_peek_write_panic() {
+ let tree = TreeBuilder::new();
+ {
+ create_dir("test_out").ok();
+ remove_file("test_out/defer_peek_write_panic.txt").ok();
+ File::create("test_out/defer_peek_write_panic.txt").unwrap();
+ defer_peek_write!(tree, "test_out/defer_peek_write_panic.txt");
+ tree.add_leaf("This should be the only line in this file");
+ assert_eq!(read_to_string("test_out/defer_peek_write.txt").unwrap(), "");
+ assert_eq!(
+ tree.peek_string(),
+ "This should be the only line in this file"
+ );
+ panic!();
+ tree.add_leaf("This line should not exist");
+ }
+ }
+
+ fn example_tree() -> TreeBuilder {
+ let tree = TreeBuilder::new();
+ {
+ add_branch_to!(tree, "1");
+ {
+ add_branch_to!(tree, "1.1");
+ add_leaf_to!(tree, "1.1.1");
+ add_leaf_to!(tree, "1.1.2\nWith two\nextra lines");
+ add_leaf_to!(tree, "1.1.3");
+ }
+ add_branch_to!(tree, "1.2");
+ add_leaf_to!(tree, "1.2.1");
+ }
+ {
+ add_branch_to!(tree, "2");
+ add_leaf_to!(tree, "2.1");
+ add_leaf_to!(tree, "2.2");
+ }
+ add_leaf_to!(tree, "3");
+ tree
+ }
+
+ #[test]
+ fn format_output() {
+ let tree = example_tree();
+ tree.set_config_override(
+ TreeConfig::new()
+ .indent(8)
+ .symbols(TreeSymbols {
+ continued: "| |",
+ join_first: "|A|",
+ join_last: "|Z|",
+ join_inner: "|N|",
+ join_only: "|O|",
+ branch: "123456[NOT SHOWN]",
+ leaf: ")}>",
+ multiline_first: Some(")}MULTI>"),
+ multiline_continued: Some(".. CONTINUED: "),
+ })
+ .show_first_level(),
+ );
+ tree.peek_print();
+ assert_eq!(
+ tree.string(),
+ "\
+|A|123456)}>1
+| | |A|123456)}>1.1
+| | | | |A|123456)}>1.1.1
+| | | | |N|123456)}MULTI>1.1.2
+| | | | | | .. CONTINUED: With two
+| | | | | | .. CONTINUED: extra lines
+| | | | |Z|123456)}>1.1.3
+| | |Z|123456)}>1.2
+| | |O|123456)}>1.2.1
+|N|123456)}>2
+| | |A|123456)}>2.1
+| | |Z|123456)}>2.2
+|Z|123456)}>3"
+ );
+ }
+
+ #[test]
+ fn format_output_thick() {
+ let tree = example_tree();
+ tree.set_config_override(
+ TreeConfig::new()
+ .symbols(TreeSymbols::with_thick())
+ .indent(4)
+ .show_first_level(),
+ );
+ tree.peek_print();
+ assert_eq!(
+ tree.string(),
+ "\
+┣━━╼ 1
+┃ ┣━━╼ 1.1
+┃ ┃ ┣━━╼ 1.1.1
+┃ ┃ ┣━━╼ 1.1.2
+┃ ┃ ┃ With two
+┃ ┃ ┃ extra lines
+┃ ┃ ┗━━╼ 1.1.3
+┃ ┗━━╼ 1.2
+┃ ┗━━╼ 1.2.1
+┣━━╼ 2
+┃ ┣━━╼ 2.1
+┃ ┗━━╼ 2.2
+┗━━╼ 3"
+ );
+ }
+
+ #[test]
+ fn format_output_pipes() {
+ let tree = example_tree();
+ tree.set_config_override(
+ TreeConfig::new()
+ .symbols(TreeSymbols::with_pipes())
+ .indent(3)
+ .show_first_level(),
+ );
+ tree.peek_print();
+ assert_eq!(
+ tree.string(),
+ "\
+╠═╼ 1
+║ ╠═╼ 1.1
+║ ║ ╠═╼ 1.1.1
+║ ║ ╠═╼ 1.1.2
+║ ║ ║ With two
+║ ║ ║ extra lines
+║ ║ ╚═╼ 1.1.3
+║ ╚═╼ 1.2
+║ ╚═╼ 1.2.1
+╠═╼ 2
+║ ╠═╼ 2.1
+║ ╚═╼ 2.2
+╚═╼ 3"
+ );
+ }
+
+ #[test]
+ fn format_output_dashed() {
+ let tree = example_tree();
+ tree.set_config_override(
+ TreeConfig::new()
+ .symbols(TreeSymbols::with_dashed().multiline_continued(" > "))
+ .indent(4)
+ .show_first_level(),
+ );
+ tree.peek_print();
+ assert_eq!(
+ tree.string(),
+ "\
+┊╌╌- 1
+┊ ┊╌╌- 1.1
+┊ ┊ ┊╌╌- 1.1.1
+┊ ┊ ┊╌╌- 1.1.2
+┊ ┊ ┊ > With two
+┊ ┊ ┊ > extra lines
+┊ ┊ '╌╌- 1.1.3
+┊ '╌╌- 1.2
+┊ '╌╌- 1.2.1
+┊╌╌- 2
+┊ ┊╌╌- 2.1
+┊ '╌╌- 2.2
+'╌╌- 3"
+ );
+ }
+
+ #[test]
+ fn format_output_rounded() {
+ let tree = example_tree();
+ tree.set_config_override(
+ TreeConfig::new()
+ .symbols(TreeSymbols::with_rounded())
+ .indent(4),
+ );
+ tree.peek_print();
+ assert_eq!(
+ tree.string(),
+ "\
+1
+├──╼ 1.1
+│ ├──╼ 1.1.1
+│ ├──╼ 1.1.2
+│ │ With two
+│ │ extra lines
+│ ╰──╼ 1.1.3
+╰──╼ 1.2
+ ╰──╼ 1.2.1
+2
+├──╼ 2.1
+╰──╼ 2.2
+3"
+ );
+ }
+
+ async fn wait_a_bit(tree: TreeBuilder, index: usize) {
+ tree.print();
+ add_branch_to!(tree, "inside async branch {}", index);
+ tree.print();
+ add_leaf_to!(tree, "inside async leaf {}", index);
+ tree.print();
+ }
+
+ #[tokio::test]
+ async fn async_barrier() {
+ let tree = TreeBuilder::new();
+ defer_peek_print!(tree);
+ add_branch_to!(tree, "root");
+ add_leaf_to!(tree, "before async");
+
+ let x2 = wait_a_bit(tree.clone(), 4);
+ let x1 = wait_a_bit(tree.clone(), 5);
+ let x3 = wait_a_bit(tree.clone(), 3);
+ let x4 = wait_a_bit(tree.clone(), 2);
+ let x5 = wait_a_bit(tree.clone(), 1);
+
+ add_leaf_to!(tree, "before join async");
+
+ join5(x1, x2, x3, x4, x5).await;
+ add_leaf_to!(tree, "after join async");
+ assert_eq!(tree.peek_string(), "after join async");
+ }
+}
diff --git a/src/tree_config.rs b/src/tree_config.rs
new file mode 100644
index 0000000..ec534a3
--- /dev/null
+++ b/src/tree_config.rs
@@ -0,0 +1,224 @@
+use once_cell::sync::Lazy;
+use std::sync::{Arc, Mutex};
+
+#[derive(Debug, Clone)]
+pub struct TreeSymbols {
+ /// A vertical base of the tree (│)
+ pub continued: &'static str,
+
+ /// Symbol for joining the first branch in a group (├)
+ pub join_first: &'static str,
+
+ /// Symbol for joining the last branch in a group (└)
+ pub join_last: &'static str,
+
+ /// Symbol for joining a branch that is not first or last in its group (├)
+ pub join_inner: &'static str,
+
+ /// Symbol for joining a branch if it is the only one in its group (├)
+ pub join_only: &'static str,
+
+ /// A repeated branch token (─)
+ pub branch: &'static str,
+
+ /// End of a leaf (╼)
+ pub leaf: &'static str,
+
+ pub multiline_first: Option<&'static str>,
+ pub multiline_continued: Option<&'static str>,
+}
+
+#[derive(Debug, Clone)]
+pub struct TreeConfig {
+ pub symbols: TreeSymbols,
+
+ /// Aside from the first branch, `indent` is equal to the number of spaces a child branch is
+ /// shifted from its parent.
+ pub indent: usize,
+
+ pub show_first_level: bool,
+}
+impl TreeSymbols {
+ pub fn new() -> Self {
+ Self {
+ continued: "│",
+ join_first: "├",
+ join_inner: "├",
+ join_last: "└",
+ join_only: "└",
+ branch: "─",
+ leaf: "╼ ",
+ multiline_first: None,
+ multiline_continued: None,
+ }
+ }
+ pub fn with_pipes() -> Self {
+ Self {
+ continued: "║",
+ join_first: "╠",
+ join_inner: "╠",
+ join_last: "╚",
+ join_only: "╚",
+ branch: "═",
+ leaf: "╼ ",
+ multiline_first: None,
+ multiline_continued: None,
+ }
+ }
+ pub fn with_thick() -> Self {
+ Self {
+ continued: "┃",
+ join_first: "┣",
+ join_inner: "┣",
+ join_last: "┗",
+ join_only: "┗",
+ branch: "━",
+ leaf: "╼ ",
+ multiline_first: None,
+ multiline_continued: None,
+ }
+ }
+ pub fn with_rounded() -> Self {
+ Self {
+ continued: "│",
+ join_first: "├",
+ join_inner: "├",
+ join_last: "╰",
+ join_only: "╰",
+ branch: "─",
+ leaf: "╼ ",
+ multiline_first: None,
+ multiline_continued: None,
+ }
+ }
+ pub fn with_dashed() -> Self {
+ Self {
+ continued: "┊",
+ join_first: "┊",
+ join_inner: "┊",
+ join_last: "'",
+ join_only: "'",
+ branch: "╌",
+ leaf: "- ",
+ multiline_first: None,
+ multiline_continued: None,
+ }
+ }
+
+ pub fn continued(mut self, sym: &'static str) -> Self {
+ self.continued = sym;
+ self
+ }
+ pub fn join_first(mut self, sym: &'static str) -> Self {
+ self.join_first = sym;
+ self
+ }
+ pub fn join_inner(mut self, sym: &'static str) -> Self {
+ self.join_inner = sym;
+ self
+ }
+ pub fn join_last(mut self, sym: &'static str) -> Self {
+ self.join_last = sym;
+ self
+ }
+ pub fn join_only(mut self, sym: &'static str) -> Self {
+ self.join_only = sym;
+ self
+ }
+
+ pub fn branch(mut self, sym: &'static str) -> Self {
+ self.branch = sym;
+ self
+ }
+ pub fn leaf(mut self, sym: &'static str) -> Self {
+ self.leaf = sym;
+ self
+ }
+ pub fn multiline_first(mut self, sym: &'static str) -> Self {
+ self.multiline_first = Some(sym);
+ self
+ }
+ pub fn multiline_continued(mut self, sym: &'static str) -> Self {
+ self.multiline_continued = Some(sym);
+ self
+ }
+}
+
+impl TreeConfig {
+ pub fn new() -> Self {
+ Self {
+ symbols: TreeSymbols::new(),
+ indent: 2,
+ show_first_level: false,
+ }
+ }
+ pub fn with_symbols(symbols: TreeSymbols) -> Self {
+ Self {
+ symbols,
+ indent: 2,
+ show_first_level: false,
+ }
+ }
+ pub fn indent(mut self, x: usize) -> Self {
+ self.indent = x;
+ self
+ }
+ pub fn show_first_level(mut self) -> Self {
+ self.show_first_level = true;
+ self
+ }
+ pub fn hide_first_level(mut self) -> Self {
+ self.show_first_level = false;
+ self
+ }
+ pub fn symbols(mut self, x: TreeSymbols) -> Self {
+ self.symbols = x;
+ self
+ }
+}
+
+impl Default for TreeSymbols {
+ fn default() -> Self {
+ tree_config_symbols()
+ }
+}
+impl Default for TreeConfig {
+ fn default() -> Self {
+ tree_config()
+ }
+}
+
+static DEFAULT_CONFIG: Lazy<Arc<Mutex<TreeConfig>>> =
+ Lazy::new(|| -> Arc<Mutex<TreeConfig>> { Arc::new(Mutex::new(TreeConfig::new())) });
+
+/// Set the default tree config
+pub fn set_tree_config(x: TreeConfig) {
+ *DEFAULT_CONFIG.lock().unwrap() = x;
+}
+
+/// The default tree config
+pub fn tree_config() -> TreeConfig {
+ DEFAULT_CONFIG.lock().unwrap().clone()
+}
+
+/// Set the default tree symbols config
+pub fn set_tree_config_symbols(x: TreeSymbols) {
+ DEFAULT_CONFIG.lock().unwrap().symbols = x;
+}
+
+/// The default tree symbols config
+pub fn tree_config_symbols() -> TreeSymbols {
+ DEFAULT_CONFIG.lock().unwrap().symbols.clone()
+}
+
+/// The default tree symbols config
+pub fn update_tree_config<F: FnMut(&mut TreeConfig)>(mut update: F) {
+ let mut x = DEFAULT_CONFIG.lock().unwrap();
+ update(&mut x);
+}
+
+/// The default tree symbols config
+pub fn update_tree_config_symbols<F: FnMut(&mut TreeSymbols)>(mut update: F) {
+ let mut x = DEFAULT_CONFIG.lock().unwrap();
+ update(&mut x.symbols);
+}