diff options
-rw-r--r-- | src/output/details.rs | 58 | ||||
-rw-r--r-- | src/output/tree.rs | 149 |
2 files changed, 153 insertions, 54 deletions
diff --git a/src/output/details.rs b/src/output/details.rs index cb9e475..1d59acd 100644 --- a/src/output/details.rs +++ b/src/output/details.rs @@ -56,46 +56,6 @@ //! can be displayed, in order to make sure that every column is wide enough. //! //! -//! ## 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) -//! -//! These rows have a `None` value for their vector of cells, instead of a `Some` -//! vector containing any. It's possible to have *both* a vector of cells and -//! depth and last flags when the user specifies `--tree` *and* `--long`. -//! -//! //! ## Extended Attributes and Errors //! //! Finally, files' extended attributes and any errors that occur while statting @@ -136,7 +96,7 @@ use options::{FileFilter, RecurseOptions}; use output::colours::Colours; use output::column::{Alignment, Column, Columns, SizeFormat}; use output::cell::{TextCell, DisplayWidth}; -use output::tree::TreePart; +use output::tree::TreeTrunk; use super::filename; @@ -673,7 +633,7 @@ impl<U> Table<U> where U: Users { /// Render the table as a vector of Cells, to be displayed on standard output. pub fn print_table(self) -> Vec<TextCell> { - let mut stack = Vec::new(); + let mut tree_trunk = TreeTrunk::default(); let mut cells = Vec::new(); // Work out the list of column widths by finding the longest cell for @@ -706,20 +666,10 @@ impl<U> Table<U> where U: Users { let mut filename = TextCell::default(); - // 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 whether the - // *previous* entry was the last in its directory. - stack.resize(row.depth + 1, TreePart::Edge); - - stack[row.depth] = if row.last { TreePart::Corner } else { TreePart::Edge }; - - for i in 1 .. row.depth + 1 { - filename.push(self.colours.punctuation.paint(stack[i].ascii_art()), 4); + for tree_part in tree_trunk.new_row(row.depth, row.last) { + filename.push(self.colours.punctuation.paint(tree_part.ascii_art()), 4); } - stack[row.depth] = if row.last { TreePart::Blank } else { TreePart::Line }; - // If any tree characters have been printed, then add an extra // space, which makes the output look much better. if row.depth != 0 { 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 ]); + } +} |