summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--src/output/details.rs58
-rw-r--r--src/output/tree.rs149
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 ]);
+ }
+}