dru 0.1.0

du with little rust in there.
Documentation
//! Library part of Dru

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();
        // pull target dir or use current directory.
        let target_dir = matches.value_of("TARGET_DIR").unwrap_or(".").into();
        // pull mode or use default
        let mode = if matches.is_present("SUMMARIZE") {
            Mode::Summarize
        } else {
            Mode::Default
        };

        // reutrn the result
        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
    }
}