Repository: trailofbits/cargo-unmaintained (language: rust)

Issue:
More progress reporting
Suggestion from @tjade273.


Discussion / hints:

Fix patch:
diff --git a/src/main.rs b/src/main.rs
index 8319f7ef..ecefb608 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -9,12 +9,14 @@ use clap::{crate_version, Parser};
 use crates_index::GitIndex;
 use home::cargo_home;
 use once_cell::{sync::Lazy, unsync::OnceCell};
+use progress::Progress;
 use std::{
     cell::RefCell,
     collections::{HashMap, HashSet},
     env::args,
     ffi::OsStr,
     fs::{read_to_string, File},
+    io::IsTerminal,
     path::{Path, PathBuf},
     process::{exit, Command},
     str::FromStr,
@@ -32,6 +34,7 @@ mod github;
 mod on_disk_cache;
 mod opts;
 mod packaging;
+mod progress;
 mod url;
 mod verbose;
 
@@ -412,7 +415,17 @@ fn unmaintained() -> Result<bool> {
         }
     );
 
+    let mut progress = if std::io::stderr().is_terminal() && !opts::get().verbose {
+        Some(Progress::new(packages.len()))
+    } else {
+        None
+    };
+
     for pkg in packages {
+        if let Some(progress) = progress.as_mut() {
+            progress.advance(&pkg.name)?;
+        }
+
         if let Some(unmaintained_pkg) = is_unmaintained_package(&metadata, pkg)? {
             unmaintained_pkgs.push(unmaintained_pkg);
 
@@ -422,6 +435,10 @@ fn unmaintained() -> Result<bool> {
         }
     }
 
+    if let Some(progress) = progress.as_mut() {
+        progress.finish()?;
+    }
+
     if unmaintained_pkgs.is_empty() {
         eprintln!("No unmaintained packages found");
         return Ok(false);
diff --git a/src/progress.rs b/src/progress.rs
new file mode 100644
index 00000000..39edd5d4
--- /dev/null
+++ b/src/progress.rs
@@ -0,0 +1,56 @@
+use crate::flush::Flush;
+use anyhow::{Context, Result};
+
+pub struct Progress {
+    n: usize,
+    i: usize,
+    width_n: usize,
+    width_prev: usize,
+    finished: bool,
+}
+
+impl Drop for Progress {
+    fn drop(&mut self) {
+        if !self.finished {
+            self.finish().unwrap_or_default();
+        }
+    }
+}
+
+impl Progress {
+    pub fn new(n: usize) -> Self {
+        Self {
+            n,
+            i: 0,
+            width_n: n.to_string().len(),
+            width_prev: 0,
+            finished: false,
+        }
+    }
+
+    pub fn advance(&mut self, msg: &str) -> Result<()> {
+        self.draw(msg)?;
+        assert!(self.i < self.n);
+        self.i += 1;
+        Ok(())
+    }
+
+    pub fn finish(&mut self) -> Result<()> {
+        assert!(self.i == self.n);
+        self.draw("")?;
+        eprintln!();
+        self.finished = true;
+        Ok(())
+    }
+
+    fn draw(&mut self, msg: &str) -> Result<()> {
+        let width_n = self.width_n;
+        let percent = self.i * 100 / self.n;
+        let formatted_msg = format!("{:>width_n$}/{} ({percent}%) {msg}", self.i, self.n,);
+        let width_to_overwrite = self.width_prev.saturating_sub(formatted_msg.len());
+        eprint!("{formatted_msg}{:width_to_overwrite$}\r", "");
+        <_ as Flush>::flush(&mut std::io::stderr()).with_context(|| "failed to flush stderr")?;
+        self.width_prev = formatted_msg.len();
+        Ok(())
+    }
+}


Test patch: