pax_global_header00006660000000000000000000000064137232650500014515gustar00rootroot0000000000000052 comment=2f47f5fb50ab9c83e0c3b549e8beedeb4fa34316 rust-text_trees-0.1.2/000077500000000000000000000000001372326505000146765ustar00rootroot00000000000000rust-text_trees-0.1.2/.github/000077500000000000000000000000001372326505000162365ustar00rootroot00000000000000rust-text_trees-0.1.2/.github/workflows/000077500000000000000000000000001372326505000202735ustar00rootroot00000000000000rust-text_trees-0.1.2/.github/workflows/rust.yml000066400000000000000000000006161372326505000220160ustar00rootroot00000000000000name: Rust on: push: branches: [ master ] pull_request: branches: [ master ] env: CARGO_TERM_COLOR: always jobs: build: runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 - name: Build run: cargo build --verbose - name: Run tests run: cargo test --verbose - name: Documentation run: cargo doc --verbose --all-features --no-deps rust-text_trees-0.1.2/.gitignore000066400000000000000000000010021372326505000166570ustar00rootroot00000000000000## ------------------------------------------------------------------------------------------------ # Files created by development tools /.idea/ *.iml **/*.rs~ **/*.md~ ## ------------------------------------------------------------------------------------------------ # Files created by cargo (build, fmt, make, etc.) /.cargo /target /docs Cargo.lock **/*.rs.bk ## ------------------------------------------------------------------------------------------------ # Files required for CI integration (Travis) /ci rust-text_trees-0.1.2/Cargo.toml000066400000000000000000000007601372326505000166310ustar00rootroot00000000000000[package] name = "text_trees" description = "Simple textual output for tree-like structures." version = "0.1.2" authors = ["Simon Johnston "] edition = "2018" documentation = "https://docs.rs/text_trees/" repository = "https://github.com/johnstonskj/rust-text_trees.git" license = "MIT" readme = "README.md" publish = true [package.metadata.docs.rs] targets = ["x86_64-unknown-linux-gnu"] all-features = true [dependencies] [dev-dependencies] pretty_assertions = "0.6.1"rust-text_trees-0.1.2/LICENSE000066400000000000000000000020571372326505000157070ustar00rootroot00000000000000MIT License Copyright (c) 2019 Simon Johnston 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. rust-text_trees-0.1.2/README.md000066400000000000000000000067071372326505000161670ustar00rootroot00000000000000# Crate text_trees Simple textual output for tree-like structures. ![MIT License](https://img.shields.io/badge/license-mit-118811.svg) ![Minimum Rust Version](https://img.shields.io/badge/Min%20Rust-1.40-green.svg) ![Rust](https://github.com/johnstonskj/rust-text_trees/workflows/Rust/badge.svg) [![GitHub stars](https://img.shields.io/github/stars/johnstonskj/rust-text_trees.svg)](https://github.com/johnstonskj/rust-text_trees/stargazers) [![crates.io](https://img.shields.io/crates/v/text_trees.svg)](https://crates.io/crates/text_trees) [![docs.rs](https://docs.rs/text_trees/badge.svg)](https://docs.rs/text_trees) This crate is another that will output a tree structure in text. Similar to the existing [ascii_tree](https://crates.io/crates/ascii_tree) crate, however it is more flexible in its formatting options. # Example The following creates a `StringTreeNode` using a combination of `with_child_nodes` and `with_children` that demonstrates the structure of the tree well. ```rust use text_trees::StringTreeNode; fn make_tree() -> StringTreeNode { StringTreeNode::with_child_nodes( "root".to_string(), vec![ "Uncle".into(), StringTreeNode::with_child_nodes( "Parent".to_string(), vec![ StringTreeNode::with_children( "Child 1".to_string(), vec!["Grand Child 1".into()].into_iter(), ), StringTreeNode::with_child_nodes( "Child 2".to_string(), vec![StringTreeNode::with_child_nodes( "Grand Child 2".to_string(), vec![StringTreeNode::with_children( "Great Grand Child 2".to_string(), vec!["Great Great Grand Child 2".to_string()].into_iter(), )] .into_iter(), )] .into_iter(), ), ] .into_iter(), ), StringTreeNode::with_children( "Aunt".to_string(), vec!["Child 3".to_string()].into_iter(), ), ] .into_iter(), ) } ``` The tree implements `Display` and therefore provides a `to_string` method. It also has a `to_string_with_format` method that allows for customization of the output format. Finally, it has two _write_ methods that take implementations of `std::io::Write` and will serialize accordingly. ```rust use text_trees::{FormatCharacters, TreeFormatting, TreeNode}; fn ascii_tree(tree: TreeNode) { let result = tree.to_string_with_format( &TreeFormatting::dir_tree(FormatCharacters::ascii()) ); assert!(result.is_ok()); // ... do something else } ``` This results in a textual representation of the tree as follows. ```text root +-- Uncle +-- Parent | +-- Child 1 | | '-- Grand Child 1 | '-- Child 2 | '-- Grand Child 2 | '-- Great Grand Child 2 | '-- Great Great Grand Child 2 '-- Aunt '-- Child 3 ``` # Changes **Version 0.1.2** * Documentation changes only. **Version 0.1.1** * Bug in top-down, bottom-anchored, tree with missing spacing. * Updated all examples to match the tree output changes. * Added `tls` tree-ls example. **Version 0.1.0** * Initial version, supports only _directory_ style trees. # TODO TBDrust-text_trees-0.1.2/examples/000077500000000000000000000000001372326505000165145ustar00rootroot00000000000000rust-text_trees-0.1.2/examples/tls.rs000066400000000000000000000044061372326505000176700ustar00rootroot00000000000000use std::fmt::{Display, Formatter}; use std::path::PathBuf; use std::{env, fs}; use text_trees::TreeNode; const P_HOME: &str = "🏠"; const P_FOLDER: &str = "📁"; const P_FILE: &str = "📄"; const P_LINK: &str = "🔗"; const P_GONE: &str = "☠️"; struct FSEntry(PathBuf); type FSTreeNode = TreeNode; fn main() { let fs_tree = make_dir_tree(PathBuf::from(".")); fs_tree.write(&mut std::io::stdout()).unwrap(); } fn make_dir_tree(path: PathBuf) -> FSTreeNode { let mut current_node = FSTreeNode::new(path.clone().into()); if path.is_dir() { for entry in fs::read_dir(path).unwrap() { let entry = entry.unwrap(); let node = make_dir_tree(entry.path()); current_node.push_node(node); } } current_node } impl From for FSEntry { fn from(v: PathBuf) -> Self { Self(v) } } impl Display for FSEntry { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { write!( f, "{}", if self.0.is_file() { let metadata = fs::symlink_metadata(&self.0).unwrap(); let file_type = metadata.file_type(); format!( "{} {}", if file_type.is_symlink() { P_LINK } else { P_FILE }, self.0.file_name().unwrap().to_string_lossy() ) } else if self.0.is_dir() { let home = env::var("HOME").unwrap(); let canonical_path = self.0.canonicalize().unwrap(); format!( "{} {}", if canonical_path == PathBuf::from(home) { P_HOME } else { P_FOLDER }, match self.0.file_name() { None => self.0.to_string_lossy(), Some(path) => path.to_string_lossy(), } ) } else { format!( "{} {}", P_GONE, self.0.file_name().unwrap().to_string_lossy() ) } ) } } rust-text_trees-0.1.2/src/000077500000000000000000000000001372326505000154655ustar00rootroot00000000000000rust-text_trees-0.1.2/src/lib.rs000066400000000000000000000642311372326505000166070ustar00rootroot00000000000000/*! Simple textual output for tree-like structures. This crate is another that will output a tree structure in text. Similar to the existing [ascii_tree](https://crates.io/crates/ascii_tree) crate, however it is more flexible in its formatting options. This crate provides a generic [`TreeNode`](struct.TreeNode.html) so that items are stored in the tree as long as the item implements `std::fmt::Display`. Commonly however a tree is generated separately from the data it represents and so the simple [`StringTreeNode`](type.StringTreeNode.html) allows construction of trees with string data throughout. The output trees are generated by writing to an implementation of `std::io::Write` and helper functions are provided that write to, and return, a `String`. The goal is to make not only the writing of the tree easy but the construction should support not only creating a tree as a stand-alone structure or as a representation of another structure. So, where options _may_ be provided these option structures implement `Default` and methods that do, and do not, take options are provided. Additionally, implementations of `From` are provided to simplify the creation of the tree itself. # Example The following example constructs a tree using the `StringTreeNode` type and a combination of [`with_child_nodes`](struct.TreeNode.html#method.with_child_nodes) and [`with_children`](struct.TreeNode.html#method.with_children). This demonstrates the structure of the tree well. For a more complete example, see the included [tls](https://github.com/johnstonskj/rust-text_trees/blob/master/examples/tls.rs) _tree ls_ source. ```rust use text_trees::StringTreeNode; fn make_tree() -> StringTreeNode { StringTreeNode::with_child_nodes( "root".to_string(), vec![ "Uncle".into(), StringTreeNode::with_child_nodes( "Parent".to_string(), vec![ StringTreeNode::with_children( "Child 1".to_string(), vec!["Grand Child 1".into()].into_iter(), ), StringTreeNode::with_child_nodes( "Child 2".to_string(), vec![StringTreeNode::with_child_nodes( "Grand Child 2".to_string(), vec![StringTreeNode::with_children( "Great Grand Child 2".to_string(), vec!["Great Great Grand Child 2".to_string()].into_iter(), )] .into_iter(), )] .into_iter(), ), ] .into_iter(), ), StringTreeNode::with_children( "Aunt".to_string(), vec!["Child 3".to_string()].into_iter(), ), ] .into_iter(), ) } ``` The tree implements `Display` and therefore provides a `to_string` method. It also has a `to_string_with_format` method that allows for customization of the output format. Finally, it has two _write_ methods that take implementations of `std::io::Write` and will serialize accordingly. ```rust use text_trees::{FormatCharacters, TreeFormatting, TreeNode}; fn ascii_tree(tree: TreeNode) { let result = tree.to_string_with_format( &TreeFormatting::dir_tree(FormatCharacters::ascii()) ); assert!(result.is_ok()); // ... do something else } ``` This results in a textual representation of the tree as follows. ```text root +-- Uncle +-- Parent | +-- Child 1 | | '-- Grand Child 1 | '-- Child 2 | '-- Grand Child 2 | '-- Great Grand Child 2 | '-- Great Great Grand Child 2 '-- Aunt '-- Child 3 ``` # Formatting Options The format of the output tree is specified by the structure [`TreeFormatting`](struct.TreeFormatting.html) with a simple format available using the [`dir_tree`](struct.TreeFormatting.html#method.dir_tree) and [`dir_tree_left`](struct.TreeFormatting.html#method.dir_tree_left) associated functions. You may also override the default characters used to draw the tree lines with the [`FormatCharacters`](struct.FormatCharacters.html) structure. Two common sets can be made using the [`ascii`](struct.FormatCharacters.html#method.ascii) or [`box_chars`](struct.FormatCharacters.html#method.box_chars) associated functions. The following sections demonstrate how different combinations of values for the `TreeFormatting` and `FormtCharacters` structures will affect the output. | Option | Setting | |-----------------|-----------| | Character Set | [`ASCII`](struct.FormatCharacters.html#method.ascii) | | Orientation | [`TopDown`](enum.TreeOrientation.html#variant.TopDown) | | Anchor Position | [`Below`](enum.AnchorPosition.html#variant.Below) | | Prefix String | `None` | ```text root +-- Uncle +-- Parent | +-- Child 1 | | '-- Grand Child 1 | '-- Child 2 | '-- Grand Child 2 | '-- Great Grand Child 2 | '-- Great Great Grand Child 2 '-- Aunt '-- Child 3 ``` | Option | Setting | |-----------------|-----------| | Character Set | [`ASCII`](struct.FormatCharacters.html#method.ascii) | | Orientation | [`TopDown`](enum.TreeOrientation.html#variant.TopDown) | | Anchor Position | [`Left`](enum.AnchorPosition.html#variant.Left) | | Prefix String | `None` | ```text + root +--- Uncle +--, Parent | +--, Child 1 | | '--- Grand Child 1 | '--, Child 2 | '--, Grand Child 2 | '--, Great Grand Child 2 | '--- Great Great Grand Child 2 '--, Aunt '--- Child 3 ``` | Option | Setting | |-----------------|-----------| | Character Set | [`Box char`](struct.FormatCharacters.html#method.box_chars) | | Orientation | [`TopDown`](enum.TreeOrientation.html#variant.TopDown) | | Anchor Position | [`Below`](enum.AnchorPosition.html#variant.Below) | | Prefix String | `None` | ```text root ├── Uncle ├── Parent │ ├── Child 1 │ │ └── Grand Child 1 │ └── Child 2 │ └── Grand Child 2 │ └── Great Grand Child 2 │ └── Great Great Grand Child 2 └── Aunt └── Child 3 ``` | Option | Setting | |-----------------|-----------| | Character Set | [`Box char`](struct.FormatCharacters.html#method.box_chars) | | Orientation | [`TopDown`](enum.TreeOrientation.html#variant.TopDown) | | Anchor Position | [`Left`](enum.AnchorPosition.html#variant.Left) | | Prefix String | `None` | ```text ┌ root ├─── Uncle ├──┬ Parent │ ├──┬ Child 1 │ │ └─── Grand Child 1 │ └──┬ Child 2 │ └──┬ Grand Child 2 │ └──┬ Great Grand Child 2 │ └─── Great Great Grand Child 2 └──┬ Aunt └─── Child 3 ``` The following example overrides the basic values of the box character formatting characters to allow for visualization of the spaces generated. The horizontal spaces are shown as "#" and the label spacing is shown as ".". | Option | Setting | |-----------------|-----------| | Character Set | Custom | | Orientation | [`TopDown`](enum.TreeOrientation.html#variant.TopDown) | | Anchor Position | [`Left`](enum.AnchorPosition.html#variant.Left) | | Prefix String | `">> "` | ```text >> ┌..root >> ├──────..Uncle >> ├─────┬..Parent >> │#####├─────┬..Child 1 >> │#####│#####└──────..Grand Child 1 >> │#####└─────┬..Child 2 >> │###########└─────┬..Grand Child 2 >> │#################└─────┬..Great Grand Child 2 >> │#######################└──────..Great Great Grand Child 2 >> └─────┬..Aunt >> ######└──────..Child 3 */ #![warn( // ---------- Stylistic future_incompatible, nonstandard_style, rust_2018_idioms, trivial_casts, trivial_numeric_casts, // ---------- Public missing_debug_implementations, missing_docs, unreachable_pub, // ---------- Unsafe unsafe_code, // ---------- Unused unused_extern_crates, unused_import_braces, unused_qualifications, unused_results, )] use std::fmt::{Display, Formatter}; use std::io::Result; use std::io::Write; // ------------------------------------------------------------------------------------------------ // Public Types // ------------------------------------------------------------------------------------------------ /// /// This denotes the orientation of the tree as it is written. /// #[derive(Clone, Debug, PartialEq)] pub enum TreeOrientation { /// This writes a tree with the root node at the top-left corner and the tree expanding /// out to the right and down. This is often called a _directory tree_ as it is useful in /// showing file-system hierarchy. /// /// # Example /// /// ```text /// root /// +-- Uncle /// +-- Parent /// | +-- Child 1 /// | | '-- Grand Child 1 /// | '-- Child 2 /// | '-- Grand Child 2 /// | '-- Great Grand Child 2 /// | '-- Great Great Grand Child 2 /// '-- Aunt /// '-- Child 3 /// ``` /// TopDown, } /// /// Denotes the position where the generated tree lines are anchored to the label text. #[derive(Clone, Debug, PartialEq)] pub enum AnchorPosition { /// The line is anchored below the first letter of the label. /// /// # Example /// /// ```text /// parent_node /// '-- child_node /// ``` Below, /// The line is anchored to the left of the label, and the label spacing prefix. /// /// # Example /// /// ```text /// + parent_node /// '--- child_node /// ``` Left, } /// /// This structure collects together all the formatting options that control how the tree is /// output. /// #[derive(Clone, Debug)] pub struct TreeFormatting { /// A prefix string written before every line. While no validation is performed on this value, /// if newline (or other formatting) characters are included the tree is likely to appear /// disjointed. pub prefix_str: Option, /// The orientation to write the tree. pub orientation: TreeOrientation, /// The line anchor position. pub anchor: AnchorPosition, /// The set of characters to use when line formatting. pub chars: FormatCharacters, } /// /// Contains the set of characters, and counts, to use when line formatting. /// #[derive(Clone, Debug)] pub struct FormatCharacters { /// This character is used to connect the root of the tree when line anchors are on the left. /// ASCII value `'+'`, box character value `'┌'`. pub down_facing_angle: char, /// This character is used to connect non-root parents in the tree when line anchors are on the left. /// ASCII value `','`, box character value `'┬'`. pub down_facing_tee: char, /// This character is used as the vertical connector between parent and child nodes. /// ASCII value `'|'`, box character value `'│'`. pub vertical_line: char, /// This character is used as the horizontal connector to node labels. /// ASCII value `'-'`, box character value `'─'`. pub horizontal_line: char, /// The character to use instead of `horizontal_line` where lines are not present. /// ASCII value `' '`, box character value `' '`. pub horizontal_space: char, /// The number of `horizontal_line`, or `horizontal_space` characters connecting lines to labels. pub horizontal_line_count: usize, /// This character is used to connect non-terminal child nodes. /// ASCII value `'+'`, box character value `'├'`. pub right_facing_tee: char, /// This character is used to connect terminal child nodes. /// ASCII value `'\''`, box character value `'└'`. pub right_facing_angle: char, /// This character is used as the spacing between the lines of the tree and the labels of each node. /// ASCII value `' '`, box character value `' '`. pub label_space_char: char, /// The number of `label_space_char` characters between the lines of the tree and the labels of each node. /// ASCII value `''`, box character value `''`. pub label_space_count: usize, } /// /// Denotes a node in the tree, and any node can be the root of a tree when output. The generic /// parameter `T` must implement `Display` which is used to generate the label for each node in /// the output. /// /// Note that `From` is implemented allowing a nice short-cut for node creation, and `From<&T>` /// is also implemented for types that also implement `Clone`. /// #[derive(Clone, Debug)] pub struct TreeNode where T: Display, { data: T, children: Vec>, } /// /// A common type where the only data is the node's label as a `String`. /// /// Note that `From<&str> is implemented for `TreeNode`. /// pub type StringTreeNode = TreeNode; // ------------------------------------------------------------------------------------------------ // Implementations // ------------------------------------------------------------------------------------------------ impl Default for TreeFormatting { fn default() -> Self { Self::dir_tree(Default::default()) } } impl TreeFormatting { /// Construct the common options for a directory tree using the provided format characters. pub fn dir_tree(chars: FormatCharacters) -> Self { Self { prefix_str: None, orientation: TreeOrientation::TopDown, anchor: AnchorPosition::Below, chars, } } /// Construct the common options for a directory tree using the provided format characters. /// Additionally, the value for `prefix_str` will be used for each output line. pub fn dir_tree_with_prefix(chars: FormatCharacters, prefix_str: String) -> Self { Self { prefix_str: Some(prefix_str), orientation: TreeOrientation::TopDown, anchor: AnchorPosition::Below, chars, } } /// Construct the common options for a directory tree, with lines anchored to the left, using /// the provided format characters. pub fn dir_tree_left(chars: FormatCharacters) -> Self { Self { prefix_str: None, orientation: TreeOrientation::TopDown, anchor: AnchorPosition::Left, chars, } } /// Construct the common options for a directory tree, with lines anchored to the left, using /// the provided format characters. Additionally, the value for `prefix_str` will be used for /// each output line. pub fn dir_tree_left_with_prefix(chars: FormatCharacters, prefix_str: String) -> Self { Self { prefix_str: Some(prefix_str), orientation: TreeOrientation::TopDown, anchor: AnchorPosition::Left, chars, } } #[inline] pub(crate) fn just_space(&self) -> String { format!( "{}{}", self.chars.just_space(), if self.anchor == AnchorPosition::Below { self.chars.horizontal_space.to_string() } else { String::new() } ) } #[inline] pub(crate) fn bar_and_space(&self) -> String { format!( "{}{}", self.chars.bar_and_space(), if self.anchor == AnchorPosition::Below { self.chars.horizontal_space.to_string() } else { String::new() } ) } #[inline] pub(crate) fn tee(&self, has_children: bool) -> String { format!( "{}{}{}{}", self.chars.right_facing_tee, self.chars.horizontal_line(), if self.anchor == AnchorPosition::Below { String::new() } else if has_children { self.chars.down_facing_tee.to_string() } else { self.chars.horizontal_line.to_string() }, self.chars.label_space() ) } #[inline] pub(crate) fn angle(&self, has_children: bool) -> String { format!( "{}{}{}{}", self.chars.right_facing_angle, self.chars.horizontal_line(), if self.anchor == AnchorPosition::Below { String::new() } else if has_children { self.chars.down_facing_tee.to_string() } else { self.chars.horizontal_line.to_string() }, self.chars.label_space(), ) } } // ------------------------------------------------------------------------------------------------ impl Default for FormatCharacters { fn default() -> Self { Self::ascii() } } impl FormatCharacters { /// The set of commonly used ASCII characters used for tree formatting. pub fn ascii() -> Self { Self { down_facing_angle: '+', down_facing_tee: ',', vertical_line: '|', horizontal_line: '-', horizontal_space: ' ', horizontal_line_count: 2, right_facing_tee: '+', right_facing_angle: '\'', label_space_char: ' ', label_space_count: 1, } } /// The set of commonly used line drawing characters used for tree formatting. pub fn box_chars() -> Self { Self { down_facing_angle: '┌', down_facing_tee: '┬', vertical_line: '│', horizontal_line: '─', horizontal_space: ' ', horizontal_line_count: 2, right_facing_tee: '├', right_facing_angle: '└', label_space_char: ' ', label_space_count: 1, } } #[inline] pub(crate) fn just_space(&self) -> String { format!("{}{}", self.horizontal_space, self.horizontal_space(),) } #[inline] pub(crate) fn bar_and_space(&self) -> String { format!("{}{}", self.vertical_line, self.horizontal_space(),) } #[inline] pub(crate) fn horizontal_line(&self) -> String { char_repeat(self.horizontal_line, self.horizontal_line_count) } #[inline] pub(crate) fn horizontal_space(&self) -> String { char_repeat(self.horizontal_space, self.horizontal_line_count) } #[inline] pub(crate) fn label_space(&self) -> String { char_repeat(self.label_space_char, self.label_space_count) } } // ------------------------------------------------------------------------------------------------ impl Display for TreeNode where T: Display, { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { write!( f, "{}", self.to_string_with_format(&Default::default()).unwrap() ) } } impl TreeNode where T: Display, { /// Construct a new tree node with the provided data value. pub fn new(data: T) -> Self { Self { data, children: Default::default(), } } /// Construct a new tree node with the provided data value and an iterator that provides /// child data items. pub fn with_children(data: T, children: impl Iterator) -> Self where T: Sized, { Self::with_child_nodes(data, children.map(TreeNode::new)) } /// Construct a new tree node with the provided data value and an iterator that provides /// pre-constructed `TreeNode` values as child nodes. pub fn with_child_nodes(data: T, children: impl Iterator>) -> Self where T: Sized, { Self { data, children: children.collect(), } } /// Return a reference to the data item for this node. pub fn data(&self) -> &T { &self.data } /// Return the label for this node. pub fn label(&self) -> String { self.data.to_string() } /// Returns `true` if this node has child nodes, else `false`. pub fn has_children(&self) -> bool { !self.children.is_empty() } /// Returns an iterator that will return all the child nodes. pub fn children(&self) -> impl Iterator> { self.children.iter() } /// Push a new data item into the list of children. pub fn push(&mut self, data: T) { self.push_node(TreeNode { data, children: Default::default(), }) } /// Push a new pre-constructed `TreeNode` into the list of children. pub fn push_node(&mut self, child: TreeNode) { self.children.push(child) } /// Extend the list of children with each data item from the provided iterator. pub fn extend(&mut self, children: impl Iterator) { self.children.extend(children.map(TreeNode::new)) } /// /// Return a string containing the generated tree text formatted according to the provided /// format settings. /// /// _Note_: in effect `Display::fmt` calls this method with default formatting. /// pub fn to_string_with_format(&self, format: &TreeFormatting) -> Result { use std::io::Cursor; let mut buffer = Cursor::new(Vec::new()); self.write_with_format(&mut buffer, format)?; Ok(String::from_utf8(buffer.into_inner()).unwrap()) } /// Write this tree to the provided implementation of `std::io::Write` with default formatting. pub fn write(&self, to_writer: &mut impl Write) -> Result<()> where T: Display, { self.write_with_format( to_writer, &TreeFormatting::dir_tree(FormatCharacters::ascii()), ) } /// Write this tree to the provided implementation of `std::io::Write` with the provided /// format settings. pub fn write_with_format( &self, to_writer: &mut impl Write, format: &TreeFormatting, ) -> Result<()> where T: Display, { write_tree_inner(self, to_writer, format, Default::default()) } } // ------------------------------------------------------------------------------------------------ impl From for TreeNode where T: Display, { fn from(v: T) -> Self { Self { data: v, children: Default::default(), } } } impl From<&T> for TreeNode where T: Display + Clone, { fn from(v: &T) -> Self { Self::from(v.clone()) } } // ------------------------------------------------------------------------------------------------ impl PartialEq for TreeNode where T: Display + PartialEq, { fn eq(&self, other: &Self) -> bool { self.data == other.data && self.children == other.children } } // ------------------------------------------------------------------------------------------------ impl From<&str> for TreeNode { fn from(v: &str) -> Self { Self { data: v.to_string(), children: Default::default(), } } } // ------------------------------------------------------------------------------------------------ // Private Functions // ------------------------------------------------------------------------------------------------ fn write_tree_inner( node: &TreeNode, w: &mut impl Write, format: &TreeFormatting, remaining_children_stack: Vec, ) -> Result<()> where T: Display, { // Write any requested prefix if let Some(prefix_str) = &format.prefix_str { write!(w, "{}", prefix_str)?; } if !(format.anchor == AnchorPosition::Below) && remaining_children_stack.is_empty() { write!( w, "{}{}", format.chars.down_facing_angle, char_repeat( format.chars.label_space_char, format.chars.label_space_count ) )?; } // Write the leading structures let stack_depth = remaining_children_stack.len(); for (row, remaining_children) in remaining_children_stack.iter().enumerate() { write!( w, "{}", match (*remaining_children, row == (stack_depth - 1)) { (1, true) => format.angle(node.has_children()), (1, false) => format.just_space(), (_, true) => format.tee(node.has_children()), (_, false) => format.bar_and_space(), } )?; } // Write the node label, and any children (recursively) if node.has_children() { writeln!(w, "{}", node.label())?; let mut d = node.children.len(); for child in &node.children { let mut new_child_stack = remaining_children_stack.clone(); new_child_stack.push(d); d -= 1; write_tree_inner(child, w, format, new_child_stack)?; } } else { writeln!(w, "{}", node.label())?; } // All done :) Ok(()) } #[inline] fn char_repeat(c: char, n: usize) -> String { c.to_string().as_str().repeat(n) } // ------------------------------------------------------------------------------------------------ // Unit Tests // ------------------------------------------------------------------------------------------------ #[cfg(test)] mod tests { use super::*; #[test] fn test_node_new() { let node = TreeNode::new(String::from("hello")); assert_eq!( node, TreeNode { data: "hello".to_string(), children: vec![] } ); } #[test] fn test_node_with_children() { let node = TreeNode::with_children(String::from("hello"), vec!["world".into()].into_iter()); assert_eq!( node, TreeNode { data: "hello".to_string(), children: vec![TreeNode { data: "world".to_string(), children: vec![] }] } ); } #[test] fn test_node_from_string() { let node: TreeNode = String::from("hello").into(); assert_eq!( node, TreeNode { data: "hello".to_string(), children: vec![] } ); } } rust-text_trees-0.1.2/tests/000077500000000000000000000000001372326505000160405ustar00rootroot00000000000000rust-text_trees-0.1.2/tests/test_top_down_trees.rs000066400000000000000000000117271372326505000225100ustar00rootroot00000000000000use text_trees::*; fn make_tree() -> StringTreeNode { StringTreeNode::with_child_nodes( "root".to_string(), vec![ "Uncle".into(), StringTreeNode::with_child_nodes( "Parent".to_string(), vec![ StringTreeNode::with_children( "Child 1".to_string(), vec!["Grand Child 1".into()].into_iter(), ), StringTreeNode::with_child_nodes( "Child 2".to_string(), vec![StringTreeNode::with_child_nodes( "Grand Child 2".to_string(), vec![StringTreeNode::with_children( "Great Grand Child 2".to_string(), vec!["Great Great Grand Child 2".to_string()].into_iter(), )] .into_iter(), )] .into_iter(), ), ] .into_iter(), ), StringTreeNode::with_children( "Aunt".to_string(), vec!["Child 3".to_string()].into_iter(), ), ] .into_iter(), ) } #[test] fn test_ascii_below_tree() { let tree = make_tree(); let result = tree.to_string_with_format(&TreeFormatting::dir_tree(FormatCharacters::ascii())); assert!(result.is_ok()); let result = result.unwrap(); println!("{}", result); assert_eq!( result, r#"root +-- Uncle +-- Parent | +-- Child 1 | | '-- Grand Child 1 | '-- Child 2 | '-- Grand Child 2 | '-- Great Grand Child 2 | '-- Great Great Grand Child 2 '-- Aunt '-- Child 3 "# .to_string() ); } #[test] fn test_box_char_below_tree() { let tree = make_tree(); let result = tree.to_string_with_format(&TreeFormatting::dir_tree(FormatCharacters::box_chars())); assert!(result.is_ok()); let result = result.unwrap(); println!("{}", result); assert_eq!( result, r#"root ├── Uncle ├── Parent │ ├── Child 1 │ │ └── Grand Child 1 │ └── Child 2 │ └── Grand Child 2 │ └── Great Grand Child 2 │ └── Great Great Grand Child 2 └── Aunt └── Child 3 "# .to_string() ); } #[test] fn test_ascii_side_tree() { let tree = make_tree(); let result = tree.to_string_with_format(&TreeFormatting::dir_tree_left(FormatCharacters::ascii())); assert!(result.is_ok()); let result = result.unwrap(); println!("{}", result); assert_eq!( result, r#"+ root +--- Uncle +--, Parent | +--, Child 1 | | '--- Grand Child 1 | '--, Child 2 | '--, Grand Child 2 | '--, Great Grand Child 2 | '--- Great Great Grand Child 2 '--, Aunt '--- Child 3 "# .to_string() ); } #[test] fn test_box_char_side_tree() { let tree = make_tree(); let result = tree.to_string_with_format(&TreeFormatting::dir_tree_left(FormatCharacters::box_chars())); assert!(result.is_ok()); let result = result.unwrap(); println!("{}", result); assert_eq!( result, r#"┌ root ├─── Uncle ├──┬ Parent │ ├──┬ Child 1 │ │ └─── Grand Child 1 │ └──┬ Child 2 │ └──┬ Grand Child 2 │ └──┬ Great Grand Child 2 │ └─── Great Great Grand Child 2 └──┬ Aunt └─── Child 3 "# .to_string() ); } #[test] fn test_spacing_in_tree() { let tree = make_tree(); let format = TreeFormatting { prefix_str: Some(".. ".to_string()), orientation: TreeOrientation::TopDown, anchor: AnchorPosition::Left, chars: FormatCharacters { down_facing_angle: '┌', down_facing_tee: '┬', vertical_line: '│', horizontal_line: '─', horizontal_space: '#', horizontal_line_count: 5, right_facing_tee: '├', right_facing_angle: '└', label_space_char: '.', label_space_count: 2, }, }; let result = tree.to_string_with_format(&format); assert!(result.is_ok()); let result = result.unwrap(); println!("{}", result); assert_eq!( result, r#".. ┌..root .. ├──────..Uncle .. ├─────┬..Parent .. │#####├─────┬..Child 1 .. │#####│#####└──────..Grand Child 1 .. │#####└─────┬..Child 2 .. │###########└─────┬..Grand Child 2 .. │#################└─────┬..Great Grand Child 2 .. │#######################└──────..Great Great Grand Child 2 .. └─────┬..Aunt .. ######└──────..Child 3 "# .to_string() ); }