parallel_disk_usage/visualizer/
tree.rs

1use super::{ChildPosition, Direction, Parenthood};
2use derive_more::{AsRef, Deref, Display, Into};
3use fmt_iter::FmtIter;
4use pipe_trait::Pipe;
5use std::fmt::{Display, Error, Formatter};
6use zero_copy_pads::Width;
7
8/// Determine 3 characters to use as skeletal component that connect a node
9/// to the rest of the tree.
10#[derive(Debug, Clone, Copy, PartialEq, Eq)]
11pub struct TreeSkeletalComponent {
12    /// Whether the node is the last child amongst its parent's `children`.
13    pub child_position: ChildPosition,
14    /// The direction of the visualization of the tree.
15    pub direction: Direction,
16    /// Whether the node has children.
17    pub parenthood: Parenthood,
18}
19
20/// String made by calling [`TreeSkeletalComponent::visualize`](TreeSkeletalComponent).
21#[derive(Debug, Clone, Copy, PartialEq, Eq, AsRef, Deref, Display, Into)]
22pub struct TreeSkeletalComponentVisualization(&'static str);
23
24impl TreeSkeletalComponent {
25    /// Determine 3 characters to use as skeletal component that connect a node
26    /// to the rest of the tree.
27    pub const fn visualize(self) -> TreeSkeletalComponentVisualization {
28        use ChildPosition::*;
29        use Direction::*;
30        use Parenthood::*;
31        let result = match (self.child_position, self.direction, self.parenthood) {
32            (Init, BottomUp, Parent) => "├─┴",
33            (Init, BottomUp, Childless) => "├──",
34            (Init, TopDown, Parent) => "├─┬",
35            (Init, TopDown, Childless) => "├──",
36            (Last, BottomUp, Parent) => "┌─┴",
37            (Last, BottomUp, Childless) => "┌──",
38            (Last, TopDown, Parent) => "└─┬",
39            (Last, TopDown, Childless) => "└──",
40        };
41        TreeSkeletalComponentVisualization(result)
42    }
43}
44
45impl Display for TreeSkeletalComponent {
46    fn fmt(&self, formatter: &mut Formatter<'_>) -> Result<(), Error> {
47        write!(formatter, "{}", self.visualize())
48    }
49}
50
51impl Width for TreeSkeletalComponent {
52    #[inline]
53    fn width(&self) -> usize {
54        self.visualize().width()
55    }
56}
57
58/// Horizontal slice of a tree of the height of exactly 1 line of text.
59#[derive(Debug, Clone, PartialEq, Eq)]
60pub struct TreeHorizontalSlice<Name: Width> {
61    pub(super) ancestor_relative_positions: Vec<ChildPosition>,
62    pub(super) skeletal_component: TreeSkeletalComponent,
63    pub(super) name: Name,
64}
65
66impl<Name: Width> TreeHorizontalSlice<Name> {
67    #[inline]
68    fn depth(&self) -> usize {
69        self.ancestor_relative_positions.len()
70    }
71
72    #[inline]
73    fn indent_width(&self) -> usize {
74        self.depth() * 2
75    }
76
77    #[inline]
78    fn required_width(&self) -> usize {
79        self.indent_width() + self.skeletal_component.width()
80    }
81
82    #[inline]
83    fn indent(&self) -> impl Display + '_ {
84        self.ancestor_relative_positions
85            .iter()
86            .map(|position| match position {
87                ChildPosition::Init => "│ ",
88                ChildPosition::Last => "  ",
89            })
90            .pipe(FmtIter::from)
91    }
92}
93
94impl<Name: Width> Display for TreeHorizontalSlice<Name> {
95    fn fmt(&self, formatter: &mut Formatter<'_>) -> Result<(), Error> {
96        write!(
97            formatter,
98            "{}{}{}",
99            self.indent(),
100            self.skeletal_component,
101            &self.name,
102        )
103    }
104}
105
106impl<Name: Width> Width for TreeHorizontalSlice<Name> {
107    #[inline]
108    fn width(&self) -> usize {
109        self.required_width() + self.name.width()
110    }
111}
112
113impl TreeHorizontalSlice<String> {
114    /// Truncate the name to fit specified `max_width`.
115    ///
116    /// * If `max_width` is already sufficient, do nothing other than return `Ok(())`.
117    /// * If `max_width` is insufficient even for the required part, return `Err(N)`
118    ///   where `N` is the required width.
119    /// * If `max_width` is sufficient for the required part but insufficient for the
120    ///   name, truncate and add `"..."` to the name.
121    pub fn truncate(&mut self, max_width: usize) -> Result<(), usize> {
122        if self.width() <= max_width {
123            return Ok(());
124        }
125
126        let min_width = self.required_width() + "...".len();
127        if min_width >= max_width {
128            return Err(min_width);
129        }
130
131        self.name.truncate(max_width - min_width);
132        self.name += "...";
133        Ok(())
134    }
135}