diff --git a/src/bin/lychee/main.rs b/src/bin/lychee/main.rs index 28a14e7..9d54eef 100644 --- a/src/bin/lychee/main.rs +++ b/src/bin/lychee/main.rs @@ -175,7 +175,12 @@ async fn run(cfg: &Config, inputs: Vec) -> Result { if let Some(output) = &cfg.output { fs::write(output, stats_formatted).context("Cannot write status output to file")?; } else { - println!("\n{}", stats_formatted); + if cfg.verbose && !stats.is_empty() { + // separate summary from the verbose list of links above + println!(); + } + // we assume that the formatted stats don't have a final newline + println!("{}", stats_formatted); } match stats.is_success() { diff --git a/src/bin/lychee/stats.rs b/src/bin/lychee/stats.rs index 5e0446d..09cc71a 100644 --- a/src/bin/lychee/stats.rs +++ b/src/bin/lychee/stats.rs @@ -74,9 +74,13 @@ impl ResponseStats { pub fn is_success(&self) -> bool { self.total == self.successful + self.excludes } + + pub fn is_empty(&self) -> bool { + self.total == 0 + } } -fn write_stat(f: &mut fmt::Formatter, title: &str, stat: usize) -> fmt::Result { +fn write_stat(f: &mut fmt::Formatter, title: &str, stat: usize, newline: bool) -> fmt::Result { let fill = title.chars().count(); f.write_str(title)?; f.write_str( @@ -84,7 +88,12 @@ fn write_stat(f: &mut fmt::Formatter, title: &str, stat: usize) -> fmt::Result { .to_string() .pad(MAX_PADDING - fill, '.', Alignment::Right, false), )?; - f.write_str("\n") + + if newline { + f.write_str("\n")?; + } + + Ok(()) } impl Display for ResponseStats { @@ -93,24 +102,23 @@ impl Display for ResponseStats { writeln!(f, "📝 Summary")?; writeln!(f, "{}", separator)?; - write_stat(f, "🔍 Total", self.total)?; - write_stat(f, "✅ Successful", self.successful)?; - write_stat(f, "⏳ Timeouts", self.timeouts)?; - write_stat(f, "🔀 Redirected", self.redirects)?; - write_stat(f, "👻 Excluded", self.excludes)?; - write_stat(f, "🚫 Errors", self.errors + self.failures)?; + write_stat(f, "🔍 Total", self.total, true)?; + write_stat(f, "✅ Successful", self.successful, true)?; + write_stat(f, "⏳ Timeouts", self.timeouts, true)?; + write_stat(f, "🔀 Redirected", self.redirects, true)?; + write_stat(f, "👻 Excluded", self.excludes, true)?; + write_stat(f, "🚫 Errors", self.errors + self.failures, false)?; - if !&self.fail_map.is_empty() { - writeln!(f)?; - } for (input, responses) in &self.fail_map { - writeln!(f, "Errors in {}", input)?; + // Using leading newlines over trailing ones (e.g. `writeln!`) + // lets us avoid extra newlines without any additional logic. + write!(f, "\n\nErrors in {}", input)?; for response in responses { - writeln!(f, "{}", color_response(response))? + write!(f, "\n{}", color_response(response))? } - writeln!(f)?; } - writeln!(f) + + Ok(()) } } @@ -120,6 +128,20 @@ mod test_super { use super::*; + #[test] + fn test_stats_is_empty() { + let mut stats = ResponseStats::new(); + assert!(stats.is_empty()); + + stats.add(Response { + uri: website("http://example.org/ok"), + status: Status::Ok(http::StatusCode::OK), + source: Input::Stdin, + }); + + assert!(!stats.is_empty()); + } + #[test] fn test_stats() { let mut stats = ResponseStats::new();