diff --git a/cli/Cargo.toml b/cli/Cargo.toml index 0e7050d926..ebbe042d1f 100644 --- a/cli/Cargo.toml +++ b/cli/Cargo.toml @@ -157,6 +157,7 @@ tower-lsp.workspace = true twox-hash.workspace = true typed-arena = "=2.0.2" uuid = { workspace = true, features = ["serde"] } +walkdir = "=2.3.2" which.workspace = true zeromq.workspace = true zip = { version = "2.1.6", default-features = false, features = ["deflate-flate2"] } @@ -173,7 +174,6 @@ nix.workspace = true deno_bench_util.workspace = true pretty_assertions.workspace = true test_util.workspace = true -walkdir = "=2.3.2" [package.metadata.winres] # This section defines the metadata that appears in the deno.exe PE header. diff --git a/cli/main.rs b/cli/main.rs index 1b2640758b..e8ecaa3930 100644 --- a/cli/main.rs +++ b/cli/main.rs @@ -32,7 +32,6 @@ use crate::args::flags_from_vec; use crate::args::DenoSubcommand; use crate::args::Flags; use crate::args::DENO_FUTURE; -use crate::cache::DenoDir; use crate::graph_container::ModuleGraphContainer; use crate::util::display; use crate::util::v8::get_v8_flags_from_env; @@ -138,12 +137,7 @@ async fn run_subcommand(flags: Arc) -> Result { .await }), DenoSubcommand::Clean => spawn_subcommand(async move { - let deno_dir = DenoDir::new(None)?; - if deno_dir.root.exists() { - std::fs::remove_dir_all(&deno_dir.root)?; - log::info!("{} {}", colors::green("Removed"), deno_dir.root.display()); - } - Ok::<(), std::io::Error>(()) + tools::clean::clean() }), DenoSubcommand::Compile(compile_flags) => spawn_subcommand(async { tools::compile::compile(flags, compile_flags).await diff --git a/cli/tools/clean.rs b/cli/tools/clean.rs new file mode 100644 index 0000000000..2a77434f88 --- /dev/null +++ b/cli/tools/clean.rs @@ -0,0 +1,97 @@ +// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. + +use deno_core::anyhow::Context; +use deno_core::error::AnyError; +use std::path::Path; + +use crate::cache::DenoDir; +use crate::colors; +use crate::display; +use crate::util::progress_bar::ProgressBar; +use crate::util::progress_bar::ProgressBarStyle; +use crate::util::progress_bar::ProgressMessagePrompt; +use crate::util::progress_bar::UpdateGuard; + +struct CleanState { + files_removed: u64, + dirs_removed: u64, + bytes_removed: u64, + progress_guard: UpdateGuard, +} + +impl CleanState { + fn update_progress(&self) { + self + .progress_guard + .set_position(self.files_removed + self.dirs_removed); + } +} + +pub fn clean() -> Result<(), AnyError> { + let deno_dir = DenoDir::new(None)?; + if deno_dir.root.exists() { + let no_of_files = walkdir::WalkDir::new(&deno_dir.root).into_iter().count(); + let progress_bar = ProgressBar::new(ProgressBarStyle::ProgressBars); + let progress_guard = + progress_bar.update_with_prompt(ProgressMessagePrompt::Cleaning, ""); + + let mut state = CleanState { + files_removed: 0, + dirs_removed: 0, + bytes_removed: 0, + progress_guard, + }; + state + .progress_guard + .set_total_size(no_of_files.try_into().unwrap()); + + rm_rf(&mut state, &deno_dir.root)?; + + // Drop the guard so that progress bar disappears. + drop(state.progress_guard); + + log::info!( + "{} {} {}", + colors::green("Removed"), + deno_dir.root.display(), + colors::gray(&format!( + "({} files, {})", + state.files_removed + state.dirs_removed, + display::human_size(state.bytes_removed as f64) + )) + ); + } + + Ok(()) +} + +fn rm_rf(state: &mut CleanState, path: &Path) -> Result<(), AnyError> { + for entry in walkdir::WalkDir::new(path).contents_first(true) { + let entry = entry?; + + if entry.file_type().is_dir() { + state.dirs_removed += 1; + state.update_progress(); + std::fs::remove_dir_all(entry.path())?; + } else { + remove_file(state, entry.path(), entry.metadata().ok())?; + } + } + + Ok(()) +} + +fn remove_file( + state: &mut CleanState, + path: &Path, + meta: Option, +) -> Result<(), AnyError> { + if let Some(meta) = meta { + state.bytes_removed += meta.len(); + } + state.files_removed += 1; + state.update_progress(); + std::fs::remove_file(path) + .with_context(|| format!("Failed to remove file: {}", path.display()))?; + Ok(()) +} diff --git a/cli/tools/mod.rs b/cli/tools/mod.rs index 4593092ed2..7bb9b7cf65 100644 --- a/cli/tools/mod.rs +++ b/cli/tools/mod.rs @@ -3,6 +3,7 @@ pub mod bench; pub mod bundle; pub mod check; +pub mod clean; pub mod compile; pub mod coverage; pub mod doc; diff --git a/cli/util/progress_bar/mod.rs b/cli/util/progress_bar/mod.rs index 91bf4950fb..85be056d84 100644 --- a/cli/util/progress_bar/mod.rs +++ b/cli/util/progress_bar/mod.rs @@ -28,6 +28,7 @@ pub enum ProgressMessagePrompt { Download, Blocking, Initialize, + Cleaning, } impl ProgressMessagePrompt { @@ -38,6 +39,7 @@ impl ProgressMessagePrompt { ProgressMessagePrompt::Initialize => { colors::green("Initialize").to_string() } + ProgressMessagePrompt::Cleaning => colors::green("Cleaning").to_string(), } } } @@ -71,7 +73,13 @@ impl UpdateGuard { #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum ProgressBarStyle { + /// Shows a progress bar with human readable download size DownloadBars, + + /// Shows a progress bar with numeric progres count + ProgressBars, + + /// Shows a list of currently downloaded files. TextOnly, } @@ -270,7 +278,14 @@ impl ProgressBar { Self { inner: ProgressBarInner::new(match style { ProgressBarStyle::DownloadBars => { - Arc::new(renderer::BarProgressBarRenderer) + Arc::new(renderer::BarProgressBarRenderer { + display_human_download_size: true, + }) + } + ProgressBarStyle::ProgressBars => { + Arc::new(renderer::BarProgressBarRenderer { + display_human_download_size: false, + }) } ProgressBarStyle::TextOnly => { Arc::new(renderer::TextOnlyProgressBarRenderer::default()) diff --git a/cli/util/progress_bar/renderer.rs b/cli/util/progress_bar/renderer.rs index 64d5339799..a83ceb3334 100644 --- a/cli/util/progress_bar/renderer.rs +++ b/cli/util/progress_bar/renderer.rs @@ -34,7 +34,9 @@ pub trait ProgressBarRenderer: Send + Sync + std::fmt::Debug { /// Indicatif style progress bar. #[derive(Debug)] -pub struct BarProgressBarRenderer; +pub struct BarProgressBarRenderer { + pub display_human_download_size: bool, +} impl ProgressBarRenderer for BarProgressBarRenderer { fn render(&self, data: ProgressData) -> String { @@ -48,13 +50,16 @@ impl ProgressBarRenderer for BarProgressBarRenderer { if total_size == 0 { (String::new(), 0) } else { - let total_size_str = human_download_size(total_size, total_size); - ( - format!( - " {}/{}", + let (pos_str, total_size_str) = if self.display_human_download_size { + ( human_download_size(pos, total_size), - total_size_str, - ), + human_download_size(total_size, total_size), + ) + } else { + (pos.to_string(), total_size.to_string()) + }; + ( + format!(" {}/{}", pos_str, total_size_str,), 2 + total_size_str.len() * 2, ) } @@ -244,7 +249,9 @@ mod test { #[test] fn should_render_bar_progress() { - let renderer = BarProgressBarRenderer; + let renderer = BarProgressBarRenderer { + display_human_download_size: true, + }; let mut data = ProgressData { display_entries: vec![ProgressDataDisplayEntry { prompt: ProgressMessagePrompt::Download, diff --git a/tests/specs/clean/general/__test__.jsonc b/tests/specs/clean/general/__test__.jsonc index f425a20c16..cdc0d0b449 100644 --- a/tests/specs/clean/general/__test__.jsonc +++ b/tests/specs/clean/general/__test__.jsonc @@ -15,7 +15,7 @@ "output": "true\n" }, { "args": "clean", - "output": "Removed [WILDLINE]/deno_dir\n" + "output": "Removed [WILDLINE]/deno_dir ([WILDCARD]files, [WILDCARD])\n" }, { "envs": { // use a new dir to avoid creating the old one