mirror of
https://github.com/Llewellynvdm/exa.git
synced 2024-09-28 21:29:02 +00:00
70a30ed683
This commit uses Clippy to fix all the 'use_self' warnings. Using Self instead of the type name has been good Rust style for a while now, and it's become the style I'm used to seeing.
275 lines
9.2 KiB
Rust
275 lines
9.2 KiB
Rust
//! 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:
|
||
//!
|
||
//! ```text
|
||
//! ┌───────┬───────┬───────────────────────┐
|
||
//! │ 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 {
|
||
|
||
/// 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 {
|
||
|
||
/// Turn this tree part into ASCII-licious box drawing characters!
|
||
/// (Warning: not actually ASCII)
|
||
pub fn ascii_art(&self) -> &'static str {
|
||
match *self {
|
||
Self::Edge => "├──",
|
||
Self::Line => "│ ",
|
||
Self::Corner => "└──",
|
||
Self::Blank => " ",
|
||
}
|
||
}
|
||
}
|
||
|
||
|
||
/// 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<TreeParams>,
|
||
}
|
||
|
||
#[derive(Debug, Copy, Clone)]
|
||
pub struct TreeParams {
|
||
|
||
/// How many directories deep into the tree structure this is. Directories
|
||
/// on top have depth 0.
|
||
depth: TreeDepth,
|
||
|
||
/// Whether this is the last entry in the directory.
|
||
last: bool,
|
||
}
|
||
|
||
#[derive(Debug, Copy, Clone)]
|
||
pub struct TreeDepth(pub usize);
|
||
|
||
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, params: TreeParams) -> &[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) = self.last_params {
|
||
self.stack[last.depth.0] = 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(params.depth.0 + 1, TreePart::Edge);
|
||
self.stack[params.depth.0] = if params.last { TreePart::Corner } else { TreePart::Edge };
|
||
self.last_params = Some(params);
|
||
|
||
// Return the tree parts as a slice of the stack.
|
||
//
|
||
// 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
|
||
//
|
||
&self.stack[1..]
|
||
}
|
||
}
|
||
|
||
impl TreeParams {
|
||
pub fn new(depth: TreeDepth, last: bool) -> Self {
|
||
Self { depth, last }
|
||
}
|
||
|
||
pub fn is_at_root(&self) -> bool {
|
||
self.depth.0 == 0
|
||
}
|
||
}
|
||
|
||
impl TreeDepth {
|
||
pub fn root() -> Self {
|
||
Self(0)
|
||
}
|
||
|
||
pub fn deeper(self) -> Self {
|
||
Self(self.0 + 1)
|
||
}
|
||
|
||
/// 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)
|
||
})
|
||
}
|
||
}
|
||
|
||
|
||
#[cfg(test)]
|
||
mod trunk_test {
|
||
use super::*;
|
||
|
||
fn params(depth: usize, last: bool) -> TreeParams {
|
||
TreeParams::new(TreeDepth(depth), last)
|
||
}
|
||
|
||
#[test]
|
||
fn empty_at_first() {
|
||
let mut tt = TreeTrunk::default();
|
||
assert_eq!(tt.new_row(params(0, true)), &[]);
|
||
}
|
||
|
||
#[test]
|
||
fn one_child() {
|
||
let mut tt = TreeTrunk::default();
|
||
assert_eq!(tt.new_row(params(0, true)), &[]);
|
||
assert_eq!(tt.new_row(params(1, true)), &[ TreePart::Corner ]);
|
||
}
|
||
|
||
#[test]
|
||
fn two_children() {
|
||
let mut tt = TreeTrunk::default();
|
||
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 ]);
|
||
}
|
||
|
||
#[test]
|
||
fn two_times_two_children() {
|
||
let mut tt = TreeTrunk::default();
|
||
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 ]);
|
||
|
||
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 ]);
|
||
}
|
||
|
||
#[test]
|
||
fn two_times_two_nested_children() {
|
||
let mut tt = TreeTrunk::default();
|
||
assert_eq!(tt.new_row(params(0, true)), &[]);
|
||
|
||
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 ]);
|
||
|
||
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 ]);
|
||
}
|
||
}
|
||
|
||
|
||
|
||
#[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());
|
||
}
|
||
}
|