Skip to content

Commit

Permalink
Merge pull request #39 from solidiquis/disk-usage
Browse files Browse the repository at this point in the history
Disk usage
  • Loading branch information
solidiquis authored Mar 5, 2023
2 parents ae3eae4 + 74b822a commit 277f70a
Show file tree
Hide file tree
Showing 10 changed files with 192 additions and 92 deletions.
10 changes: 10 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ path = "src/main.rs"
ansi_term = "0.12.1"
clap = { version = "4.1.1", features = ["derive"] }
crossbeam = "0.8.2"
filesize = "0.2.0"
ignore = "0.4.2"
lscolors = { version = "0.13.0", features = ["ansi_term"] }
once_cell = "1.17.0"
Expand Down
42 changes: 31 additions & 11 deletions src/cli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,22 +7,25 @@ use std::{
convert::From,
error::Error as StdError,
fmt::{self, Display, Formatter},
fs,
io,
fs, io,
path::{Path, PathBuf},
usize,
};

/// Defines the CLI.
#[derive(Parser, Debug)]
#[command(name = "Erdtree")]
#[command(name = "erdtree")]
#[command(author = "Benjamin Nguyen. <[email protected]>")]
#[command(version = "1.2")]
#[command(about = "erdtree (et) is a multi-threaded filetree visualizer and disk usage analyzer.", long_about = None)]
pub struct Clargs {
/// Root directory to traverse; defaults to current working directory
dir: Option<PathBuf>,

/// Print physical or logical file size
#[arg(short, long, value_enum, default_value_t = DiskUsage::Logical)]
disk_usage: DiskUsage,

/// Include or exclude files using glob patterns
#[arg(short, long)]
glob: Vec<String>,
Expand Down Expand Up @@ -78,32 +81,50 @@ pub enum Order {
/// Sort entries by file name
Name,

/// Sort entries by size in descending order
/// Sort entries by size smallest to largest, top to bottom
Size,

/// Sort entries by size largest to smallest, bottom to top
SizeRev,

/// No sorting
None,
}

/// Display disk usage output as either logical size or physical size.
#[derive(Copy, Clone, Debug, ValueEnum)]
pub enum DiskUsage {
/// How many bytes does a file contain
Logical,

/// How much actual space on disk based on blocks allocated, taking into account sparse files
/// and compression.
Physical,
}

impl Clargs {
/// Returns reference to the path of the root directory to be traversed.
pub fn dir(&self) -> &Path {
if let Some(ref path) = self.dir {
path.as_path()
} else {
Path::new(".")
}
self.dir
.as_ref()
.map_or_else(|| Path::new("."), |pb| pb.as_path())
}

/// The sort-order used for printing.
pub fn sort(&self) -> Order {
self.sort
}

/// Getter for `dirs_first` field.
pub fn dirs_first(&self) -> bool {
self.dirs_first
}

/// Getter for `disk_usage` field.
pub fn disk_usage(&self) -> &DiskUsage {
&self.disk_usage
}

/// The max depth to print. Note that all directories are fully traversed to compute file
/// sizes; this just determines how much to print.
pub fn level(&self) -> Option<usize> {
Expand Down Expand Up @@ -146,8 +167,7 @@ impl TryFrom<&Clargs> for WalkParallel {
fn try_from(clargs: &Clargs) -> Result<Self, Self::Error> {
let root = fs::canonicalize(clargs.dir())?;

fs::metadata(&root)
.map_err(|e| Error::DirNotFound(format!("{}: {e}", root.display())))?;
fs::metadata(&root).map_err(|e| Error::DirNotFound(format!("{}: {e}", root.display())))?;

Ok(WalkBuilder::new(root)
.follow_links(clargs.follow_links)
Expand Down
21 changes: 21 additions & 0 deletions src/fs/erdtree/disk_usage.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
use crate::cli;
use std::convert::From;

/// Determines between logical or physical size for display
#[derive(Debug)]
pub enum DiskUsage {
/// How many bytes does a file contain
Logical,

/// How much actual space on disk, taking into account sparse files and compression.
Physical,
}

impl From<&cli::DiskUsage> for DiskUsage {
fn from(du: &cli::DiskUsage) -> Self {
match du {
cli::DiskUsage::Logical => Self::Logical,
cli::DiskUsage::Physical => Self::Physical,
}
}
}
3 changes: 3 additions & 0 deletions src/fs/erdtree/mod.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
/// Operations that decide how to present info about disk usage.
pub mod disk_usage;

/// Contains components of the [`Tree`] data structure that derive from [`DirEntry`].
///
/// [`Tree`]: tree::Tree
Expand Down
102 changes: 60 additions & 42 deletions src/fs/erdtree/node.rs
Original file line number Diff line number Diff line change
@@ -1,17 +1,18 @@
use super::get_ls_colors;
use ansi_term::Color;
use super::{disk_usage::DiskUsage, get_ls_colors};
use crate::{
fs::file_size::FileSize,
icons::{self, icon_from_ext, icon_from_file_name, icon_from_file_type}
icons::{self, icon_from_ext, icon_from_file_name, icon_from_file_type},
};
use ansi_term::Color;
use ansi_term::Style;
use filesize::PathExt;
use ignore::DirEntry;
use lscolors::Style as LS_Style;
use std::{
borrow::Cow,
convert::From,
fmt::{self, Display, Formatter},
ffi::{OsStr, OsString},
fmt::{self, Display, Formatter},
fs::{self, FileType},
path::{Path, PathBuf},
slice::Iter,
Expand Down Expand Up @@ -81,17 +82,14 @@ impl Node {
/// Converts `OsStr` to `String`; if fails does a lossy conversion replacing non-Unicode
/// sequences with Unicode replacement scalar value.
pub fn file_name_lossy(&self) -> Cow<'_, str> {
self.file_name().to_str().map_or_else(
|| self.file_name().to_string_lossy(),
|s| Cow::from(s)
)
self.file_name()
.to_str()
.map_or_else(|| self.file_name().to_string_lossy(), |s| Cow::from(s))
}

/// Returns `true` if node is a directory.
pub fn is_dir(&self) -> bool {
self.file_type()
.map(|ft| ft.is_dir())
.unwrap_or(false)
self.file_type().map(|ft| ft.is_dir()).unwrap_or(false)
}

/// Is the Node a symlink.
Expand All @@ -106,7 +104,9 @@ impl Node {

/// Returns the file name of the symlink target if [Node] represents a symlink.
pub fn symlink_target_file_name(&self) -> Option<&OsStr> {
self.symlink_target_path().map(|path| path.file_name()).flatten()
self.symlink_target_path()
.map(|path| path.file_name())
.flatten()
}

/// Returns reference to underlying [FileType].
Expand Down Expand Up @@ -149,9 +149,11 @@ impl Node {
/// Gets stylized icon for node if enabled. Icons without extensions are styled based on the
/// [`LS_COLORS`] foreground configuration of the associated file name.
///
/// [`LS_COLORS`]: super::tree::ui::LS_COLORS
/// [`LS_COLORS`]: super::tree::ui::LS_COLORS
fn get_icon(&self) -> Option<String> {
if !self.show_icon { return None }
if !self.show_icon {
return None;
}

let path = self.symlink_target_path().unwrap_or_else(|| self.path());

Expand All @@ -163,7 +165,9 @@ impl Node {
return Some(self.stylize(icon));
}

let file_name = self.symlink_target_file_name().unwrap_or_else(|| self.file_name());
let file_name = self
.symlink_target_file_name()
.unwrap_or_else(|| self.file_name());

if let Some(icon) = icon_from_file_name(file_name) {
return Some(self.stylize(icon));
Expand All @@ -174,51 +178,57 @@ impl Node {

/// Stylizes input, `entity` based on [`LS_COLORS`]
///
/// [`LS_COLORS`]: super::tree::ui::LS_COLORS
/// [`LS_COLORS`]: super::tree::ui::LS_COLORS
fn stylize(&self, entity: &str) -> String {
self.style().foreground.map_or_else(
|| entity.to_string(),
|fg| fg.bold().paint(entity).to_string()
|fg| fg.bold().paint(entity).to_string(),
)
}

/// Stylizes symlink name for display.
fn stylize_link_name(&self) -> Option<String> {
self.symlink_target_file_name()
.map(|name| {
let file_name = self.file_name_lossy();
let styled_name = self.stylize(&file_name);
let target_name = Color::Red.paint(format!("\u{2192} {}", name.to_string_lossy()));
format!("{} {}", styled_name, target_name)
})
self.symlink_target_file_name().map(|name| {
let file_name = self.file_name_lossy();
let styled_name = self.stylize(&file_name);
let target_name = Color::Red.paint(format!("\u{2192} {}", name.to_string_lossy()));
format!("{} {}", styled_name, target_name)
})
}
}

/// Used to be converted directly into a [Node].
pub struct NodePrecursor {
pub struct NodePrecursor<'a> {
disk_usage: &'a DiskUsage,
dir_entry: DirEntry,
show_icon: bool,
}

impl NodePrecursor {
impl<'a> NodePrecursor<'a> {
/// Yields a [NodePrecursor] which is used for convenient conversion into a [Node].
pub fn new(dir_entry: DirEntry, show_icon: bool) -> Self {
Self { dir_entry, show_icon }
pub fn new(disk_usage: &'a DiskUsage, dir_entry: DirEntry, show_icon: bool) -> Self {
Self {
disk_usage,
dir_entry,
show_icon,
}
}
}

impl From<NodePrecursor> for Node {
impl From<NodePrecursor<'_>> for Node {
fn from(precursor: NodePrecursor) -> Self {
let NodePrecursor { dir_entry, show_icon } = precursor;
let NodePrecursor {
disk_usage,
dir_entry,
show_icon,
} = precursor;

let children = None;

let depth = dir_entry.depth();

let file_type = dir_entry.file_type();

let metadata = dir_entry.metadata().ok();

let path = dir_entry.path();

let symlink_target = dir_entry
Expand All @@ -233,6 +243,8 @@ impl From<NodePrecursor> for Node {
|os_str| os_str.to_owned(),
);

let metadata = dir_entry.metadata().ok();

let style = get_ls_colors()
.style_for_path_with_metadata(path, metadata.as_ref())
.map(LS_Style::to_ansi_term_style)
Expand All @@ -242,8 +254,11 @@ impl From<NodePrecursor> for Node {

if let Some(ref ft) = file_type {
if ft.is_file() {
if let Some(md) = metadata {
file_size = Some(md.len());
if let Some(ref md) = metadata {
file_size = match disk_usage {
DiskUsage::Logical => Some(md.len()),
DiskUsage::Physical => path.size_on_disk_fast(md).ok(),
}
}
}
};
Expand All @@ -264,19 +279,22 @@ impl From<NodePrecursor> for Node {

impl Display for Node {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
let size = self.file_size
let size = self
.file_size
.map(|size| format!("({})", FileSize::new(size)))
.or_else(|| Some("".to_owned()))
.unwrap();

let icon = self.show_icon
let icon = self
.show_icon
.then(|| self.get_icon())
.flatten()
.unwrap_or("".to_owned());

let icon_padding = (icon.len() > 1).then(|| icon.len() - 1).unwrap_or(0);

let (styled_name, name_padding) = self.stylize_link_name()
let (styled_name, name_padding) = self
.stylize_link_name()
.map(|name| {
let padding = name.len() - 1;
(name, padding)
Expand All @@ -292,11 +310,11 @@ impl Display for Node {

let output = format!(
"{:<icon_padding$}{:<name_padding$}{size}",
icon,
styled_name,
icon_padding = icon_padding,
name_padding = name_padding
);
icon,
styled_name,
icon_padding = icon_padding,
name_padding = name_padding
);

write!(f, "{output}")
}
Expand Down
Loading

0 comments on commit 277f70a

Please sign in to comment.