use super::command_prelude::*;
use crate::get_book_dir;
use anyhow::Context;
use anyhow::Result;
use mdbook_driver::MDBook;
use std::mem::take;
use std::path::PathBuf;
use std::{fmt, fs};
pub fn make_subcommand() -> Command {
Command::new("clean")
.about("Deletes a built book")
.arg_dest_dir()
.arg_root_dir()
}
pub fn execute(args: &ArgMatches) -> Result<()> {
let book_dir = get_book_dir(args);
let book = MDBook::load(book_dir)?;
let dir_to_remove = match args.get_one::<PathBuf>("dest-dir") {
Some(dest_dir) => std::env::current_dir()
.expect("current dir should be valid")
.join(dest_dir),
None => book.root.join(&book.config.build.build_dir),
};
let removed = Clean::new(&dir_to_remove)?;
println!("{removed}");
Ok(())
}
pub fn human_readable_bytes(bytes: u64) -> (f32, &'static str) {
static UNITS: [&str; 7] = ["B", "KiB", "MiB", "GiB", "TiB", "PiB", "EiB"];
let bytes = bytes as f32;
let i = ((bytes.log2() / 10.0) as usize).min(UNITS.len() - 1);
(bytes / 1024_f32.powi(i as i32), UNITS[i])
}
#[derive(Debug)]
pub struct Clean {
num_files_removed: u64,
num_dirs_removed: u64,
total_bytes_removed: u64,
}
impl Clean {
fn new(dir: &PathBuf) -> Result<Clean> {
let mut files = vec![dir.clone()];
let mut children = Vec::new();
let mut num_files_removed = 0;
let mut num_dirs_removed = 0;
let mut total_bytes_removed = 0;
if dir.exists() {
while !files.is_empty() {
for file in files {
if let Ok(meta) = file.metadata() {
total_bytes_removed += meta.len();
}
if file.is_file() {
num_files_removed += 1;
} else if file.is_dir() {
num_dirs_removed += 1;
for entry in fs::read_dir(file)? {
children.push(entry?.path());
}
}
}
files = take(&mut children);
}
fs::remove_dir_all(&dir).with_context(|| "Unable to remove the build directory")?;
}
Ok(Clean {
num_files_removed,
num_dirs_removed,
total_bytes_removed,
})
}
}
impl fmt::Display for Clean {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "Removed ")?;
match (self.num_files_removed, self.num_dirs_removed) {
(0, 0) => write!(f, "0 files")?,
(0, 1) => write!(f, "1 directory")?,
(0, 2..) => write!(f, "{} directories", self.num_dirs_removed)?,
(1, _) => write!(f, "1 file")?,
(2.., _) => write!(f, "{} files", self.num_files_removed)?,
}
if self.total_bytes_removed == 0 {
Ok(())
} else {
if self.total_bytes_removed < 1024 {
write!(f, ", {}B total", self.total_bytes_removed)
} else {
let (bytes, unit) = human_readable_bytes(self.total_bytes_removed);
write!(f, ", {bytes:.2}{unit} total")
}
}
}
}