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::{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    fn width(&self) -> usize {
53        self.visualize().width()
54    }
55}
56
57/// Horizontal slice of a tree of the height of exactly 1 line of text.
58#[derive(Debug, Clone, PartialEq, Eq)]
59pub struct TreeHorizontalSlice<Name: Width> {
60    pub(super) ancestor_relative_positions: Vec<ChildPosition>,
61    pub(super) skeletal_component: TreeSkeletalComponent,
62    pub(super) name: Name,
63}
64
65impl<Name: Width> TreeHorizontalSlice<Name> {
66    #[inline]
67    fn depth(&self) -> usize {
68        self.ancestor_relative_positions.len()
69    }
70
71    #[inline]
72    fn indent_width(&self) -> usize {
73        self.depth() * 2
74    }
75
76    #[inline]
77    fn required_width(&self) -> usize {
78        self.indent_width() + self.skeletal_component.width()
79    }
80
81    #[inline]
82    fn indent(&self) -> impl Display + '_ {
83        self.ancestor_relative_positions
84            .iter()
85            .map(|position| match position {
86                ChildPosition::Init => "│ ",
87                ChildPosition::Last => "  ",
88            })
89            .pipe(FmtIter::from)
90    }
91}
92
93impl<Name: Width> Display for TreeHorizontalSlice<Name> {
94    fn fmt(&self, formatter: &mut Formatter<'_>) -> Result<(), Error> {
95        write!(
96            formatter,
97            "{}{}{}",
98            self.indent(),
99            self.skeletal_component,
100            &self.name,
101        )
102    }
103}
104
105impl<Name: Width> Width for TreeHorizontalSlice<Name> {
106    fn width(&self) -> usize {
107        self.required_width() + self.name.width()
108    }
109}
110
111impl TreeHorizontalSlice<String> {
112    /// Truncate the name to fit specified `max_width`.
113    ///
114    /// * If `max_width` is already sufficient, do nothing other than return `Ok(())`.
115    /// * If `max_width` is insufficient even for the required part, return `Err(N)`
116    ///   where `N` is the required width.
117    /// * If `max_width` is sufficient for the required part but insufficient for the
118    ///   name, truncate and add `"..."` to the name.
119    pub fn truncate(&mut self, max_width: usize) -> Result<(), usize> {
120        if self.width() <= max_width {
121            return Ok(());
122        }
123
124        let min_width = self.required_width() + "...".len();
125        if min_width >= max_width {
126            return Err(min_width);
127        }
128
129        self.name.truncate(max_width - min_width);
130        self.name += "...";
131        Ok(())
132    }
133}