summaryrefslogtreecommitdiffstats
path: root/src/output/tree.rs
diff options
context:
space:
mode:
Diffstat (limited to 'src/output/tree.rs')
-rw-r--r--src/output/tree.rs149
1 files changed, 149 insertions, 0 deletions
diff --git a/src/output/tree.rs b/src/output/tree.rs
index be3f550..1cac117 100644
--- a/src/output/tree.rs
+++ b/src/output/tree.rs
@@ -1,3 +1,41 @@
+//! 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 {
@@ -15,6 +53,9 @@ pub enum TreePart {
}
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 => "├──",
@@ -24,3 +65,111 @@ impl TreePart {
}
}
}
+
+
+/// 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 ]);
+ }
+}