use async_recursion::async_recursion;
use async_std::{io::Result, path::PathBuf, stream::StreamExt};
use clap::{App, AppSettings, Arg};
use std::env;
use std::fmt::Display;
pub struct Config {
mode: Mode,
target_dir: PathBuf,
}
impl Config {
pub fn from_args() -> Self {
let matches =
App::new("dru")
.settings(&[AppSettings::ColoredHelp])
.about("du with a little rust in it.")
.version(env!("CARGO_PKG_VERSION"))
.author("Curtis Jones <mail@curtisjones.ca>")
.arg(Arg::with_name("TARGET_DIR").help(
"The base directory to recurse down from. Defaults to current directory.",
))
.arg(
Arg::with_name("SUMMARIZE")
.short("s")
.long("summarize")
.help("Just give the total bytes taken by TARGET_DIR."),
)
.get_matches();
let target_dir = matches.value_of("TARGET_DIR").unwrap_or(".").into();
let mode = if matches.is_present("SUMMARIZE") {
Mode::Summarize
} else {
Mode::Default
};
Self { mode, target_dir }
}
fn new(target_dir: PathBuf) -> Self {
Self {
target_dir,
mode: Mode::Default,
}
}
}
#[derive(Debug, Clone, Copy)]
pub enum Mode {
Default,
Summarize,
}
#[derive(Debug, Clone, Copy)]
pub enum Units {
Bytes,
KiloBytes,
MegaBytes,
GigaBytes,
}
impl Display for Units {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let out = match self {
Self::Bytes => "B",
Self::KiloBytes => "KB",
Self::MegaBytes => "MB",
Self::GigaBytes => "GB",
};
write!(f, "{}", out)
}
}
struct Report {
path: PathBuf,
units: Units,
size: u64,
}
impl Display for Report {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}{}\t{}", self.size, self.units, self.path.display())
}
}
pub struct Dru {
mode: Mode,
target_dir: PathBuf,
target_size: u64,
reports: Vec<Report>,
}
impl Display for Dru {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "Disk usage report for: {}\n", self.target_dir.display())?;
match self.mode {
Mode::Default => {
for report in &self.reports {
write!(f, "{}\n", report)?;
}
write!(
f,
"{}\t{}",
self.summarize() + self.target_size,
self.target_dir.display()
)?;
}
Mode::Summarize => write!(f, "{}\t{}B", self.target_dir.display(), self.summarize())?,
}
Ok(())
}
}
impl Dru {
fn summarize(&self) -> u64 {
self.reports.iter().fold(0, |acc, report| acc + report.size)
}
pub async fn new(config: Config) -> Result<Self> {
Ok(Self {
mode: config.mode,
target_size: config.target_dir.metadata().await?.len(),
target_dir: config.target_dir,
reports: Vec::new(),
})
}
#[async_recursion]
pub async fn generate_reports(&mut self) -> Result<()> {
let mut dir = self.target_dir.read_dir().await?;
while let Some(fd) = dir.next().await {
let fd = fd?;
let path = fd.path();
let meta = fd.metadata().await?;
if !path.is_dir().await && !meta.file_type().is_symlink() {
self.reports.push(Report {
path,
size: meta.len(),
units: Units::Bytes,
});
} else {
let mut temp = Self::new(Config::new(path.clone())).await?;
self.reports.push(Report {
path,
size: meta.len(),
units: Units::Bytes,
});
temp.generate_reports().await?;
self.reports.append(&mut temp.into_reports());
}
}
Ok(())
}
fn into_reports(self) -> Vec<Report> {
self.reports
}
}