summaryrefslogtreecommitdiffstats
path: root/src/output/tree.rs
blob: 1cac117556c934e52ff9cf95310d60c4643347ff (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
//! Tree structures, such as `├──` or `└──`, used in a tree view.
//!
//! ## Constructing Tree Views
//!
//! When using the `--tree` argument, instead of a vector of cells, each row
//! has a `depth` field that indicates how far deep in the tree it is: the top
//! level has depth 0, its children have depth 1, and *their* children have
//! depth 2, and so on.
//!
//! On top of this, it also has a `last` field that specifies whether this is
//! the last row of this particular consecutive set of rows. This doesn’t
//! affect the file’s information; it’s just used to display a different set of
//! Unicode tree characters! The resulting table looks like this:
//!
//!     ┌───────┬───────┬───────────────────────┐
//!     │ Depth │ Last  │ Output                │
//!     ├───────┼───────┼───────────────────────┤
//!     │     0 │       │ documents             │
//!     │     1 │ false │ ├── this_file.txt     │
//!     │     1 │ false │ ├── that_file.txt     │
//!     │     1 │ false │ ├── features          │
//!     │     2 │ false │ │  ├── feature_1.rs   │
//!     │     2 │ false │ │  ├── feature_2.rs   │
//!     │     2 │ true  │ │  └── feature_3.rs   │
//!     │     1 │ true  │ └── pictures          │
//!     │     2 │ false │    ├── garden.jpg     │
//!     │     2 │ false │    ├── flowers.jpg    │
//!     │     2 │ false │    ├── library.png    │
//!     │     2 │ true  │    └── space.tiff     │
//!     └───────┴───────┴───────────────────────┘
//!
//! Creating the table like this means that each file has to be tested to see
//! if it’s the last one in the group. This is usually done by putting all the
//! files in a vector beforehand, getting its length, then comparing the index
//! of each file to see if it’s the last one. (As some files may not be
//! successfully `stat`ted, we don’t know how many files are going to exist in
//! each directory)

#[derive(PartialEq, Debug, Clone)]
pub enum TreePart {

    /// Rightmost column, *not* the last in the directory.
    Edge,

    /// Not the rightmost column, and the directory has not finished yet.
    Line,

    /// Rightmost column, and the last in the directory.
    Corner,

    /// Not the rightmost column, and the directory *has* finished.
    Blank,
}

impl TreePart {

    /// Turn this tree part into ASCII-licious box drawing characters!
    /// (Warning: not actually ASCII)
    pub fn ascii_art(&self) -> &'static str {
        match *self {
            TreePart::Edge    => "├──",
            TreePart::Line    => "│  ",
            TreePart::Corner  => "└──",
            TreePart::Blank   => "   ",
        }
    }
}


/// A **tree trunk** builds up arrays of tree parts over multiple depths.
#[derive(Debug, Default)]
pub struct TreeTrunk {

    /// A stack tracks which tree characters should be printed. It’s
    /// necessary to maintain information about the previously-printed
    /// lines, as the output will change based on any previous entries.
    stack: Vec<TreePart>,

    /// A tuple for the last ‘depth’ and ‘last’ parameters that are passed in.
    last_params: Option<(usize, bool)>,
}

impl TreeTrunk {

    /// Calculates the tree parts for an entry at the given depth and
    /// last-ness. The depth is used to determine where in the stack the tree
    /// part should be inserted, and the last-ness is used to determine which
    /// type of tree part to insert.
    ///
    /// This takes a `&mut self` because the results of each file are stored
    /// and used in future rows.
    pub fn new_row(&mut self, depth: usize, last: bool) -> &[TreePart] {

        // If this isn’t our first iteration, then update the tree parts thus
        // far to account for there being another row after it.
        if let Some((last_depth, last_last)) = self.last_params {
            self.stack[last_depth] = if last_last { TreePart::Blank } else { TreePart::Line };
        }

        // Make sure the stack has enough space, then add or modify another
        // part into it.
        self.stack.resize(depth + 1, TreePart::Edge);
        self.stack[depth] = if last { TreePart::Corner } else { TreePart::Edge };
        self.last_params = Some((depth, last));

        // Return the tree parts as a slice of the stack.
        //
        // Ignoring the first component is specific to exa: when a user prints
        // a tree view for multiple directories, we don’t want there to be a
        // ‘zeroth level’ connecting the initial directories. Otherwise, not
        // only are unrelated directories seemingly connected to each other,
        // but the tree part of the first row doesn’t connect to anything:
        //
        // with [0..]             with [1..]
        // ==========             ==========
        // ├──folder              folder
        // │  └──file             └──file
        // └──folder              folder
        //    └──file             └──file
        &self.stack[1..]
    }
}


#[cfg(test)]
mod test {
    use super::*;

    #[test]
    fn empty_at_first() {
        let mut tt = TreeTrunk::default();
        assert_eq!(tt.new_row(0, true), &[]);
    }

    #[test]
    fn one_child() {
        let mut tt = TreeTrunk::default();
        assert_eq!(tt.new_row(0, true), &[]);
        assert_eq!(tt.new_row(1, true), &[ TreePart::Corner ]);
    }

    #[test]
    fn two_children() {
        let mut tt = TreeTrunk::default();
        assert_eq!(tt.new_row(0, true), &[]);
        assert_eq!(tt.new_row(1, false), &[ TreePart::Edge ]);
        assert_eq!(tt.new_row(1, true),  &[ TreePart::Corner ]);
    }

    #[test]
    fn two_times_two_children() {
        let mut tt = TreeTrunk::default();
        assert_eq!(tt.new_row(0, false), &[]);
        assert_eq!(tt.new_row(1, false), &[ TreePart::Edge ]);
        assert_eq!(tt.new_row(1, true),  &[ TreePart::Corner ]);

        assert_eq!(tt.new_row(0, true), &[]);
        assert_eq!(tt.new_row(1, false), &[ TreePart::Edge ]);
        assert_eq!(tt.new_row(1, true),  &[ TreePart::Corner ]);
    }

    #[test]
    fn two_times_two_nested_children() {
        let mut tt = TreeTrunk::default();
        assert_eq!(tt.new_row(0, true), &[]);

        assert_eq!(tt.new_row(1, false), &[ TreePart::Edge ]);
        assert_eq!(tt.new_row(2, false), &[ TreePart::Line, TreePart::Edge ]);
        assert_eq!(tt.new_row(2, true),  &[ TreePart::Line, TreePart::Corner ]);

        assert_eq!(tt.new_row(1, true),  &[ TreePart::Corner ]);
        assert_eq!(tt.new_row(2, false), &[ TreePart::Blank, TreePart::Edge ]);
        assert_eq!(tt.new_row(2, true),  &[ TreePart::Blank, TreePart::Corner ]);
    }
}