mdbook/src/cmd/clean.rs

Ignoring revisions in .git-blame-ignore-revs. Click here to bypass and see the normal blame view.

113 lines
3.6 KiB
Rust
Raw Normal View History

2022-07-04 23:16:31 +08:00
use super::command_prelude::*;
use crate::get_book_dir;
use anyhow::Context;
use anyhow::Result;
2025-07-21 21:42:58 -07:00
use mdbook_driver::MDBook;
use std::mem::take;
2022-07-04 23:16:31 +08:00
use std::path::PathBuf;
use std::{fmt, fs};
2018-02-04 16:00:29 +03:00
// Create clap subcommand arguments
2022-07-04 23:16:31 +08:00
pub fn make_subcommand() -> Command {
Command::new("clean")
2018-08-02 15:48:22 -05:00
.about("Deletes a built book")
2022-07-04 23:16:31 +08:00
.arg_dest_dir()
.arg_root_dir()
2018-02-04 16:00:29 +03:00
}
// Clean command implementation
pub fn execute(args: &ArgMatches) -> Result<()> {
2018-02-04 16:00:29 +03:00
let book_dir = get_book_dir(args);
2023-05-13 09:44:11 -07:00
let book = MDBook::load(book_dir)?;
2018-02-04 16:00:29 +03:00
2022-07-04 23:16:31 +08:00
let dir_to_remove = match args.get_one::<PathBuf>("dest-dir") {
2018-08-02 15:48:22 -05:00
Some(dest_dir) => dest_dir.into(),
2018-02-04 16:00:29 +03:00
None => book.root.join(&book.config.build.build_dir),
};
let removed = Clean::new(&dir_to_remove)?;
println!("{removed}");
2018-02-04 16:00:29 +03:00
Ok(())
}
/// Formats a number of bytes into a human readable SI-prefixed size.
/// Returns a tuple of `(quantity, units)`.
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() {
// Note: This can over-count bytes removed for hard-linked
// files. It also under-counts since it only counts the exact
// byte sizes and not the block sizes.
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 {
// Don't show a fractional number of bytes.
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")
}
}
}
}