Skip to content

Commit abf3e4a

Browse files
committed
Display what is removed from mdbook clean.
This is based off of [cargo's][1] clean command. cargo is licensed under MIT or Apache-2.0. [1]: https://github.com/rust-lang/cargo
1 parent d107843 commit abf3e4a

File tree

1 file changed

+84
-5
lines changed

1 file changed

+84
-5
lines changed

src/cmd/clean.rs

Lines changed: 84 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,9 @@ use super::command_prelude::*;
22
use crate::get_book_dir;
33
use anyhow::Context;
44
use mdbook::MDBook;
5-
use std::fs;
5+
use std::mem::take;
66
use std::path::PathBuf;
7+
use std::{fmt, fs};
78

89
// Create clap subcommand arguments
910
pub fn make_subcommand() -> Command {
@@ -23,10 +24,88 @@ pub fn execute(args: &ArgMatches) -> mdbook::errors::Result<()> {
2324
None => book.root.join(&book.config.build.build_dir),
2425
};
2526

26-
if dir_to_remove.exists() {
27-
fs::remove_dir_all(&dir_to_remove)
28-
.with_context(|| "Unable to remove the build directory")?;
29-
}
27+
let removed = Clean::new(&dir_to_remove)?;
28+
println!("{removed}");
3029

3130
Ok(())
3231
}
32+
33+
/// Formats a number of bytes into a human readable SI-prefixed size.
34+
/// Returns a tuple of `(quantity, units)`.
35+
pub fn human_readable_bytes(bytes: u64) -> (f32, &'static str) {
36+
static UNITS: [&str; 7] = ["B", "KiB", "MiB", "GiB", "TiB", "PiB", "EiB"];
37+
let bytes = bytes as f32;
38+
let i = ((bytes.log2() / 10.0) as usize).min(UNITS.len() - 1);
39+
(bytes / 1024_f32.powi(i as i32), UNITS[i])
40+
}
41+
42+
#[derive(Debug)]
43+
pub struct Clean {
44+
num_files_removed: u64,
45+
num_dirs_removed: u64,
46+
total_bytes_removed: u64,
47+
}
48+
49+
impl Clean {
50+
fn new(dir: &PathBuf) -> mdbook::errors::Result<Clean> {
51+
let mut files = vec![dir.clone()];
52+
let mut children = Vec::new();
53+
let mut num_files_removed = 0;
54+
let mut num_dirs_removed = 0;
55+
let mut total_bytes_removed = 0;
56+
57+
if dir.exists() {
58+
while !files.is_empty() {
59+
for file in files {
60+
if let Ok(meta) = file.metadata() {
61+
// Note: This can over-count bytes removed for hard-linked
62+
// files. It also under-counts since it only counts the exact
63+
// byte sizes and not the block sizes.
64+
total_bytes_removed += meta.len();
65+
}
66+
if file.is_file() {
67+
num_files_removed += 1;
68+
} else if file.is_dir() {
69+
num_dirs_removed += 1;
70+
for entry in fs::read_dir(file)? {
71+
children.push(entry?.path());
72+
}
73+
}
74+
}
75+
files = take(&mut children);
76+
}
77+
fs::remove_dir_all(&dir).with_context(|| "Unable to remove the build directory")?;
78+
}
79+
80+
Ok(Clean {
81+
num_files_removed,
82+
num_dirs_removed,
83+
total_bytes_removed,
84+
})
85+
}
86+
}
87+
88+
impl fmt::Display for Clean {
89+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
90+
write!(f, "Removed ")?;
91+
match (self.num_files_removed, self.num_dirs_removed) {
92+
(0, 0) => write!(f, "0 files")?,
93+
(0, 1) => write!(f, "1 directory")?,
94+
(0, 2..) => write!(f, "{} directories", self.num_dirs_removed)?,
95+
(1, _) => write!(f, "1 file")?,
96+
(2.., _) => write!(f, "{} files", self.num_files_removed)?,
97+
}
98+
99+
if self.total_bytes_removed == 0 {
100+
Ok(())
101+
} else {
102+
// Don't show a fractional number of bytes.
103+
if self.total_bytes_removed < 1024 {
104+
write!(f, ", {}B total", self.total_bytes_removed)
105+
} else {
106+
let (bytes, unit) = human_readable_bytes(self.total_bytes_removed);
107+
write!(f, ", {bytes:.2}{unit} total")
108+
}
109+
}
110+
}
111+
}

0 commit comments

Comments
 (0)