2015-12-22 15:44:51 +11:00
|
|
|
|
//! 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:
|
|
|
|
|
//!
|
2016-04-19 07:48:41 +01:00
|
|
|
|
//! ```text
|
2015-12-22 15:44:51 +11:00
|
|
|
|
//! ┌───────┬───────┬───────────────────────┐
|
|
|
|
|
//! │ 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 │
|
|
|
|
|
//! └───────┴───────┴───────────────────────┘
|
2016-04-19 07:48:41 +01:00
|
|
|
|
//! ```
|
2015-12-22 15:44:51 +11:00
|
|
|
|
//!
|
|
|
|
|
//! 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)
|
|
|
|
|
|
2017-07-03 22:22:40 +01:00
|
|
|
|
|
2015-12-22 13:14:32 +11:00
|
|
|
|
#[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 {
|
2015-12-22 15:44:51 +11:00
|
|
|
|
|
|
|
|
|
/// Turn this tree part into ASCII-licious box drawing characters!
|
|
|
|
|
/// (Warning: not actually ASCII)
|
2015-12-22 13:14:32 +11:00
|
|
|
|
pub fn ascii_art(&self) -> &'static str {
|
|
|
|
|
match *self {
|
|
|
|
|
TreePart::Edge => "├──",
|
|
|
|
|
TreePart::Line => "│ ",
|
|
|
|
|
TreePart::Corner => "└──",
|
|
|
|
|
TreePart::Blank => " ",
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2015-12-22 15:44:51 +11:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/// 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.
|
2017-07-03 22:22:40 +01:00
|
|
|
|
last_params: Option<TreeParams>,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[derive(Debug, Copy, Clone)]
|
|
|
|
|
pub struct TreeParams {
|
|
|
|
|
|
|
|
|
|
/// How many directories deep into the tree structure this is. Directories
|
|
|
|
|
/// on top have depth 0.
|
2017-07-03 22:46:40 +01:00
|
|
|
|
depth: TreeDepth,
|
2017-07-03 22:22:40 +01:00
|
|
|
|
|
|
|
|
|
/// Whether this is the last entry in the directory.
|
|
|
|
|
last: bool,
|
2015-12-22 15:44:51 +11:00
|
|
|
|
}
|
|
|
|
|
|
2017-07-03 22:46:40 +01:00
|
|
|
|
#[derive(Debug, Copy, Clone)]
|
|
|
|
|
pub struct TreeDepth(pub usize);
|
|
|
|
|
|
2015-12-22 15:44:51 +11:00
|
|
|
|
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.
|
2017-07-03 22:22:40 +01:00
|
|
|
|
pub fn new_row(&mut self, params: TreeParams) -> &[TreePart] {
|
2015-12-22 15:44:51 +11:00
|
|
|
|
|
|
|
|
|
// If this isn’t our first iteration, then update the tree parts thus
|
|
|
|
|
// far to account for there being another row after it.
|
2017-07-03 22:22:40 +01:00
|
|
|
|
if let Some(last) = self.last_params {
|
2017-07-03 22:46:40 +01:00
|
|
|
|
self.stack[last.depth.0] = if last.last { TreePart::Blank } else { TreePart::Line };
|
2015-12-22 15:44:51 +11:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Make sure the stack has enough space, then add or modify another
|
|
|
|
|
// part into it.
|
2017-07-03 22:46:40 +01:00
|
|
|
|
self.stack.resize(params.depth.0 + 1, TreePart::Edge);
|
|
|
|
|
self.stack[params.depth.0] = if params.last { TreePart::Corner } else { TreePart::Edge };
|
2017-07-03 22:22:40 +01:00
|
|
|
|
self.last_params = Some(params);
|
2015-12-22 15:44:51 +11:00
|
|
|
|
|
|
|
|
|
// Return the tree parts as a slice of the stack.
|
|
|
|
|
//
|
2017-07-03 22:46:40 +01:00
|
|
|
|
// Ignore the first element here to prevent a 'zeroth level' from
|
|
|
|
|
// appearing before the very first directory. This level would
|
|
|
|
|
// join unrelated directories without connecting to anything:
|
|
|
|
|
//
|
|
|
|
|
// with [0..] with [1..]
|
|
|
|
|
// ========== ==========
|
|
|
|
|
// ├── folder folder
|
|
|
|
|
// │ └── file └── file
|
|
|
|
|
// └── folder folder
|
|
|
|
|
// └── file └──file
|
2015-12-22 15:44:51 +11:00
|
|
|
|
//
|
|
|
|
|
&self.stack[1..]
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2017-07-03 22:22:40 +01:00
|
|
|
|
impl TreeParams {
|
2017-07-03 22:46:40 +01:00
|
|
|
|
pub fn new(depth: TreeDepth, last: bool) -> TreeParams {
|
2017-07-03 22:22:40 +01:00
|
|
|
|
TreeParams { depth, last }
|
|
|
|
|
}
|
|
|
|
|
|
2017-07-03 23:25:56 +01:00
|
|
|
|
pub fn is_at_root(&self) -> bool {
|
2017-07-03 22:46:40 +01:00
|
|
|
|
self.depth.0 == 0
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl TreeDepth {
|
2017-07-03 23:25:56 +01:00
|
|
|
|
pub fn root() -> TreeDepth {
|
|
|
|
|
TreeDepth(0)
|
|
|
|
|
}
|
|
|
|
|
|
2017-07-03 22:46:40 +01:00
|
|
|
|
pub fn deeper(self) -> TreeDepth {
|
|
|
|
|
TreeDepth(self.0 + 1)
|
2017-07-03 22:22:40 +01:00
|
|
|
|
}
|
2017-07-04 08:29:36 +01:00
|
|
|
|
|
|
|
|
|
/// Creates an iterator that, as well as yielding each value, yields a
|
|
|
|
|
/// `TreeParams` with the current depth and last flag filled in.
|
|
|
|
|
pub fn iterate_over<I, T>(self, inner: I) -> Iter<I>
|
|
|
|
|
where I: ExactSizeIterator+Iterator<Item=T> {
|
|
|
|
|
Iter { current_depth: self, inner }
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
pub struct Iter<I> {
|
|
|
|
|
current_depth: TreeDepth,
|
|
|
|
|
inner: I,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl<I, T> Iterator for Iter<I>
|
|
|
|
|
where I: ExactSizeIterator+Iterator<Item=T> {
|
|
|
|
|
type Item = (TreeParams, T);
|
|
|
|
|
|
|
|
|
|
fn next(&mut self) -> Option<Self::Item> {
|
|
|
|
|
self.inner.next().map(|t| {
|
|
|
|
|
// use exact_size_is_empty API soon
|
|
|
|
|
(TreeParams::new(self.current_depth, self.inner.len() == 0), t)
|
|
|
|
|
})
|
|
|
|
|
}
|
2017-07-03 22:22:40 +01:00
|
|
|
|
}
|
|
|
|
|
|
2015-12-22 15:44:51 +11:00
|
|
|
|
|
|
|
|
|
#[cfg(test)]
|
2017-07-04 08:29:36 +01:00
|
|
|
|
mod trunk_test {
|
2015-12-22 15:44:51 +11:00
|
|
|
|
use super::*;
|
|
|
|
|
|
2017-07-03 22:46:40 +01:00
|
|
|
|
fn params(depth: usize, last: bool) -> TreeParams {
|
|
|
|
|
TreeParams::new(TreeDepth(depth), last)
|
|
|
|
|
}
|
|
|
|
|
|
2015-12-22 15:44:51 +11:00
|
|
|
|
#[test]
|
|
|
|
|
fn empty_at_first() {
|
|
|
|
|
let mut tt = TreeTrunk::default();
|
2017-07-03 22:46:40 +01:00
|
|
|
|
assert_eq!(tt.new_row(params(0, true)), &[]);
|
2015-12-22 15:44:51 +11:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn one_child() {
|
|
|
|
|
let mut tt = TreeTrunk::default();
|
2017-07-03 22:46:40 +01:00
|
|
|
|
assert_eq!(tt.new_row(params(0, true)), &[]);
|
|
|
|
|
assert_eq!(tt.new_row(params(1, true)), &[ TreePart::Corner ]);
|
2015-12-22 15:44:51 +11:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn two_children() {
|
|
|
|
|
let mut tt = TreeTrunk::default();
|
2017-07-03 22:46:40 +01:00
|
|
|
|
assert_eq!(tt.new_row(params(0, true)), &[]);
|
|
|
|
|
assert_eq!(tt.new_row(params(1, false)), &[ TreePart::Edge ]);
|
|
|
|
|
assert_eq!(tt.new_row(params(1, true)), &[ TreePart::Corner ]);
|
2015-12-22 15:44:51 +11:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn two_times_two_children() {
|
|
|
|
|
let mut tt = TreeTrunk::default();
|
2017-07-03 22:46:40 +01:00
|
|
|
|
assert_eq!(tt.new_row(params(0, false)), &[]);
|
|
|
|
|
assert_eq!(tt.new_row(params(1, false)), &[ TreePart::Edge ]);
|
|
|
|
|
assert_eq!(tt.new_row(params(1, true)), &[ TreePart::Corner ]);
|
2015-12-22 15:44:51 +11:00
|
|
|
|
|
2017-07-03 22:46:40 +01:00
|
|
|
|
assert_eq!(tt.new_row(params(0, true)), &[]);
|
|
|
|
|
assert_eq!(tt.new_row(params(1, false)), &[ TreePart::Edge ]);
|
|
|
|
|
assert_eq!(tt.new_row(params(1, true)), &[ TreePart::Corner ]);
|
2015-12-22 15:44:51 +11:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn two_times_two_nested_children() {
|
|
|
|
|
let mut tt = TreeTrunk::default();
|
2017-07-03 22:46:40 +01:00
|
|
|
|
assert_eq!(tt.new_row(params(0, true)), &[]);
|
2015-12-22 15:44:51 +11:00
|
|
|
|
|
2017-07-03 22:46:40 +01:00
|
|
|
|
assert_eq!(tt.new_row(params(1, false)), &[ TreePart::Edge ]);
|
|
|
|
|
assert_eq!(tt.new_row(params(2, false)), &[ TreePart::Line, TreePart::Edge ]);
|
|
|
|
|
assert_eq!(tt.new_row(params(2, true)), &[ TreePart::Line, TreePart::Corner ]);
|
2015-12-22 15:44:51 +11:00
|
|
|
|
|
2017-07-03 22:46:40 +01:00
|
|
|
|
assert_eq!(tt.new_row(params(1, true)), &[ TreePart::Corner ]);
|
|
|
|
|
assert_eq!(tt.new_row(params(2, false)), &[ TreePart::Blank, TreePart::Edge ]);
|
|
|
|
|
assert_eq!(tt.new_row(params(2, true)), &[ TreePart::Blank, TreePart::Corner ]);
|
2015-12-22 15:44:51 +11:00
|
|
|
|
}
|
|
|
|
|
}
|
2017-07-04 08:29:36 +01:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
#[cfg(test)]
|
|
|
|
|
mod iter_test {
|
|
|
|
|
use super::*;
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn test_iteration() {
|
|
|
|
|
let foos = &[ "first", "middle", "last" ];
|
|
|
|
|
let mut iter = TreeDepth::root().iterate_over(foos.into_iter());
|
|
|
|
|
|
|
|
|
|
let next = iter.next().unwrap();
|
|
|
|
|
assert_eq!(&"first", next.1);
|
|
|
|
|
assert_eq!(false, next.0.last);
|
|
|
|
|
|
|
|
|
|
let next = iter.next().unwrap();
|
|
|
|
|
assert_eq!(&"middle", next.1);
|
|
|
|
|
assert_eq!(false, next.0.last);
|
|
|
|
|
|
|
|
|
|
let next = iter.next().unwrap();
|
|
|
|
|
assert_eq!(&"last", next.1);
|
|
|
|
|
assert_eq!(true, next.0.last);
|
|
|
|
|
|
|
|
|
|
assert!(iter.next().is_none());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn test_empty() {
|
|
|
|
|
let nothing: &[usize] = &[];
|
|
|
|
|
let mut iter = TreeDepth::root().iterate_over(nothing.into_iter());
|
|
|
|
|
assert!(iter.next().is_none());
|
|
|
|
|
}
|
|
|
|
|
}
|