Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add ability to prune empty branches #55

Merged
merged 7 commits into from
Mar 13, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ Options:
-l, --level <NUM> Maximum depth to display
-n, --scale <NUM> Total number of digits after the decimal to display for disk usage [default: 2]
-p, --prefix <PREFIX> Display disk usage as binary or SI units [default: bin] [possible values: bin, si]
-P, --prune Disable printing of empty branches
-s, --sort <SORT> Sort-order to display directory content [possible values: name, size, size-rev]
--dirs-first Always sorts directories above files
-S, --follow-links Traverse symlink directories and consider their disk usage; disabled by default
Expand Down
4 changes: 4 additions & 0 deletions src/render/context/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,10 @@ pub struct Context {
#[arg(short, long, value_enum, default_value_t = PrefixKind::Bin)]
pub prefix: PrefixKind,

/// Disable printing of empty branches
#[arg(short = 'P', long)]
pub prune: bool,

/// Sort-order to display directory content
#[arg(short, long, value_enum)]
sort: Option<SortType>,
Expand Down
5 changes: 4 additions & 1 deletion src/render/order.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,12 @@ pub struct Order {
dir_first: bool,
}

/// Comparator type used to sort [Node]s.
pub type NodeComparator<'a> = dyn Fn(&Node, &Node) -> Ordering + 'a;

impl Order {
/// Yields function pointer to the appropriate `Node` comparator.
pub fn comparator(&self) -> Option<Box<dyn Fn(&Node, &Node) -> Ordering + '_>> {
pub fn comparator(&self) -> Option<Box<NodeComparator<'_>>> {
if self.dir_first {
return Some(Box::new(|a, b| {
Self::dir_comparator(a, b, self.sort.comparator())
Expand Down
63 changes: 35 additions & 28 deletions src/render/tree/mod.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
use super::order::Order;
use crate::render::{context::Context, disk_usage::FileSize};
use crate::render::{context::Context, disk_usage::FileSize, order::Order};
use crossbeam::channel::{self, Sender};
use error::Error;
use ignore::{WalkBuilder, WalkParallel, WalkState};
Expand Down Expand Up @@ -52,15 +51,19 @@ impl Tree {
}

/// Returns a reference to the root [Node].
pub fn root(&self) -> &Node {
fn root(&self) -> &Node {
&self.root
}

/// Maximum depth to display
pub fn level(&self) -> usize {
fn level(&self) -> usize {
self.ctx.level.unwrap_or(usize::MAX)
}

fn context(&self) -> &Context {
&self.ctx
}

/// Parallel traversal of the root directory and its contents taking `.gitignore` into
/// consideration. Parallel traversal relies on `WalkParallel`. Any filesystem I/O or related
/// system calls are expected to occur during parallel traversal; thus post-processing of all
Expand Down Expand Up @@ -134,41 +137,41 @@ impl Tree {

Self::assemble_tree(&mut root, &mut branches, ctx);

if ctx.prune {
root.prune_directories()
}

Ok(root)
}

/// Takes the results of the parallel traversal and uses it to construct the [Tree] data
/// structure. Sorting occurs if specified.
fn assemble_tree(current_dir: &mut Node, branches: &mut Branches, ctx: &Context) {
let current_node = branches
.remove(current_dir.path())
.map(|children| {
current_dir.set_children(children);
current_dir
})
.unwrap();
fn assemble_tree(current_node: &mut Node, branches: &mut Branches, ctx: &Context) {
let children = branches.remove(current_node.path()).unwrap();

if children.len() > 0 {
current_node.set_children(children);
}

let mut dir_size = FileSize::new(0, ctx.disk_usage, ctx.prefix, ctx.scale);

current_node.children_mut().map(|nodes| {
nodes.iter_mut().for_each(|node| {
if node.is_dir() {
Self::assemble_tree(node, branches, ctx);
}
current_node.children_mut().for_each(|node| {
if node.is_dir() {
Self::assemble_tree(node, branches, ctx);
}

if let Some(fs) = node.file_size() {
dir_size += fs
}
})
if let Some(fs) = node.file_size() {
dir_size += fs
}
});

if dir_size.bytes > 0 {
current_node.set_file_size(dir_size)
}

if let Some(ord) = ctx.sort().map(|s| Order::from((s, ctx.dirs_first()))) {
ord.comparator()
.map(|func| current_node.children_mut().map(|nodes| nodes.sort_by(func)));
if let Some(ordr) = ctx.sort().map(|s| Order::from((s, ctx.dirs_first()))) {
ordr.comparator()
.map(|func| current_node.sort_children(func));
}
}
}
Expand Down Expand Up @@ -196,6 +199,7 @@ impl Display for Tree {
let root = self.root();
let level = self.level();
let theme = ui::get_theme();
let prune = self.context().prune;
let mut output = String::from("");

#[inline]
Expand All @@ -210,6 +214,7 @@ impl Display for Tree {
base_prefix: &str,
level: usize,
theme: &ui::ThemesMap,
prune: bool,
) {
let mut peekable = children.peekable();

Expand All @@ -231,7 +236,9 @@ impl Display for Tree {
continue;
}

if let Some(iter_children) = child.children() {
if child.has_children() {
let children = child.children();

let mut new_base = base_prefix.to_owned();

let new_theme = child
Expand All @@ -245,7 +252,7 @@ impl Display for Tree {
new_base.push_str(theme.get("vt").unwrap());
}

traverse(output, iter_children, &new_base, level, new_theme);
traverse(output, children, &new_base, level, new_theme, prune);
}

continue;
Expand All @@ -256,8 +263,8 @@ impl Display for Tree {

extend_output(&mut output, root, "");

if let Some(iter_children) = root.children() {
traverse(&mut output, iter_children, "", level, theme)
if root.has_children() {
traverse(&mut output, root.children(), "", level, theme, prune)
}

write!(f, "{output}")
Expand Down
52 changes: 39 additions & 13 deletions src/render/tree/node.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ use crate::{
render::{
context::Context,
disk_usage::{DiskUsage, FileSize},
order::NodeComparator,
},
};
use ansi_term::Color;
Expand All @@ -18,7 +19,7 @@ use std::{
fmt::{self, Display, Formatter},
fs::{self, FileType},
path::{Path, PathBuf},
slice::Iter,
slice::{Iter, IterMut},
};

/// A node of [`Tree`] that can be created from a [DirEntry]. Any filesystem I/O and
Expand All @@ -31,7 +32,7 @@ use std::{
pub struct Node {
pub depth: usize,
pub file_size: Option<FileSize>,
children: Option<Vec<Node>>,
children: Vec<Node>,
file_name: OsString,
file_type: Option<FileType>,
inode: Option<Inode>,
Expand All @@ -46,7 +47,7 @@ impl Node {
pub fn new(
depth: usize,
file_size: Option<FileSize>,
children: Option<Vec<Node>>,
children: Vec<Node>,
file_name: OsString,
file_type: Option<FileType>,
inode: Option<Inode>,
Expand All @@ -70,13 +71,43 @@ impl Node {
}

/// Returns a mutable reference to `children` if any.
pub fn children_mut(&mut self) -> Option<&mut Vec<Node>> {
self.children.as_mut()
pub fn children_mut(&mut self) -> IterMut<Node> {
self.children.iter_mut()
}

/// Returns an iter over a `children` slice if any.
pub fn children(&self) -> Option<Iter<Node>> {
self.children.as_ref().map(|children| children.iter())
pub fn children(&self) -> Iter<Node> {
self.children.iter()
}

/// Setter for `children`.
pub fn set_children(&mut self, children: Vec<Node>) {
self.children = children;
}

/// Sorts `children` given comparator.
pub fn sort_children(&mut self, comparator: Box<NodeComparator<'_>>) {
self.children.sort_by(comparator)
}

/// Whether or not a [Node] has children.
pub fn has_children(&self) -> bool {
self.children.len() > 0
}

/// Recursively traverse [Node]s, removing any [Node]s that have no children.
pub fn prune_directories(&mut self) {
self.children.retain_mut(|node| {
if node.is_dir() {
if node.has_children() {
node.prune_directories();
return true;
} else {
return false;
}
}
true
});
}

/// Returns a reference to `file_name`. If file is a symlink then `file_name` is the name of
Expand Down Expand Up @@ -137,11 +168,6 @@ impl Node {
&self.path
}

/// Sets `children`.
pub fn set_children(&mut self, children: Vec<Node>) {
self.children = Some(children);
}

/// Gets 'file_size'.
pub fn file_size(&self) -> Option<&FileSize> {
self.file_size.as_ref()
Expand Down Expand Up @@ -229,7 +255,7 @@ impl From<(&DirEntry, &Context)> for Node {
let prefix = prefix.clone();
let icons = icons.clone();

let children = None;
let children = vec![];

let depth = dir_entry.depth();

Expand Down
29 changes: 29 additions & 0 deletions tests/prune.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
use indoc::indoc;

mod utils;

#[test]
fn prune() {
assert_eq!(
utils::run_cmd(&[
"--sort",
"name",
"--glob",
"*.txt",
"--no-config",
"--prune",
"tests/data"
]),
indoc!(
"
data (1.07 KiB)
├─ dream_cycle (308 B)
│ └─ polaris.txt (308 B)
├─ lipsum (446 B)
│ └─ lipsum.txt (446 B)
├─ necronomicon.txt (83 B)
├─ nemesis.txt (161 B)
└─ nylarlathotep.txt (100 B)"
)
)
}