@@ -2,8 +2,9 @@ use super::command_prelude::*;
2
2
use crate :: get_book_dir;
3
3
use anyhow:: Context ;
4
4
use mdbook:: MDBook ;
5
- use std:: fs ;
5
+ use std:: mem :: take ;
6
6
use std:: path:: PathBuf ;
7
+ use std:: { fmt, fs} ;
7
8
8
9
// Create clap subcommand arguments
9
10
pub fn make_subcommand ( ) -> Command {
@@ -23,10 +24,88 @@ pub fn execute(args: &ArgMatches) -> mdbook::errors::Result<()> {
23
24
None => book. root . join ( & book. config . build . build_dir ) ,
24
25
} ;
25
26
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}" ) ;
30
29
31
30
Ok ( ( ) )
32
31
}
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