diff options
author | Benjamin Sago <ogham@bsago.me> | 2015-12-22 15:44:51 +1100 |
---|---|---|
committer | Benjamin Sago <ogham@bsago.me> | 2015-12-22 15:44:51 +1100 |
commit | d009ba5938f7d7b8fac84a63545f7b871a9c79d1 (patch) | |
tree | e6a723775c5205347feaf0dc3106d32300c1ff27 | |
parent | 54319a685e61f86fc918f892cefe18b5014524e8 (diff) |
Move tree code to its module, and add tests
This commit separates the code used to generate the tree structure characters from the code used to build tables, meaning that it'll become possible to display tree structures without using any of the table code.
Also, some tests are added to make sure that the tree code *basically* works.
-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 ]); + } +} |