@@ -2,8 +2,9 @@ use super::command_prelude::*;
22use crate :: get_book_dir;
33use anyhow:: Context ;
44use mdbook:: MDBook ;
5- use std:: fs ;
5+ use std:: mem :: take ;
66use std:: path:: PathBuf ;
7+ use std:: { fmt, fs} ;
78
89// Create clap subcommand arguments
910pub 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