Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 7 additions & 2 deletions lychee-bin/src/formatters/host_stats/compact.rs
Original file line number Diff line number Diff line change
@@ -1,11 +1,16 @@
use std::fmt::{self, Display};

use super::{host_heading, status_summary};
use crate::formatters::color::{NORMAL, color};
use crate::config::OutputMode;
use crate::formatters::{
color::{NORMAL, color},
icon,
};
use lychee_lib::ratelimit::HostStatsMap;

pub(crate) struct CompactHostStats {
pub(crate) host_stats: Option<HostStatsMap>,
pub(crate) mode: OutputMode,
}

impl Display for CompactHostStats {
Expand All @@ -14,7 +19,7 @@ impl Display for CompactHostStats {
return Ok(());
};

writeln!(f, "{}", host_heading("\nπŸ“Š ", host_stats))?;
writeln!(f, "\n{}", host_heading(icon(&self.mode, "πŸ“Š "), host_stats))?;

let sorted_hosts = host_stats.sorted();
let hostname_width = sorted_hosts
Expand Down
4 changes: 3 additions & 1 deletion lychee-bin/src/formatters/host_stats/detailed.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
use std::fmt::{self, Display};

use super::host_heading;
use crate::{config::OutputMode, formatters::icon};
use lychee_lib::ratelimit::HostStatsMap;

pub(crate) struct DetailedHostStats {
pub(crate) host_stats: Option<HostStatsMap>,
pub(crate) mode: OutputMode,
}

impl Display for DetailedHostStats {
Expand All @@ -13,7 +15,7 @@ impl Display for DetailedHostStats {
return Ok(());
};

let heading = host_heading("πŸ“Š ", host_stats);
let heading = host_heading(icon(&self.mode, "πŸ“Š "), host_stats);
writeln!(f, "\n{heading}")?;
writeln!(f, "{}", "-".repeat(heading.chars().count()))?;

Expand Down
10 changes: 10 additions & 0 deletions lychee-bin/src/formatters/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,16 @@ fn supports_color() -> bool {
supports_color::on(Stream::Stdout).is_some()
}

/// Returns `symbol` for use as a label prefix, or nothing in plain mode,
/// which is meant for terminals that cannot render emoji.
pub(crate) fn icon(mode: &OutputMode, symbol: &'static str) -> &'static str {
if *mode == OutputMode::Plain {
""
} else {
symbol
}
}

/// Create a stats formatter based on the given format option
pub(crate) fn get_stats_formatter(
format: &StatsFormat,
Expand Down
91 changes: 74 additions & 17 deletions lychee-bin/src/formatters/stats/compact.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ use crate::formatters::{
color::{BOLD_GREEN, BOLD_PINK, BOLD_YELLOW, DIM, NORMAL, color},
get_response_formatter,
host_stats::CompactHostStats,
icon,
response::ResponseFormatter,
stats::{OutputStats, ResponseStats},
};
Expand Down Expand Up @@ -54,25 +55,63 @@ impl Display for CompactResponseStats {
) {
color!(f, BOLD_YELLOW, "[{}]:\n", source)?;
write_responses(f, &*response_formatter, responses)?;
write_suggestions(f, stats, source)?;
write_suggestions(f, stats, source, icon(&self.mode, "β„Ή "))?;
writeln!(f)?;
}

color!(f, NORMAL, "πŸ” {} Total", stats.total)?;
let mode = &self.mode;
color!(f, NORMAL, "{}{} Total", icon(mode, "πŸ” "), stats.total)?;
color!(f, DIM, " (in {})", format_duration(stats.duration))?;
color!(f, NORMAL, " πŸ”— {} Unique", stats.unique)?;
color!(f, BOLD_GREEN, " βœ… {} OK", stats.successful)?;
color!(f, NORMAL, " {}{} Unique", icon(mode, "πŸ”— "), stats.unique)?;
color!(
f,
BOLD_GREEN,
" {}{} OK",
icon(mode, "βœ… "),
stats.successful
)?;

let total_errors = stats.errors;

let err_str = if total_errors == 1 { "Error" } else { "Errors" };
color!(f, BOLD_PINK, " 🚫 {} {}", total_errors, err_str)?;

write_if_any(stats.unknown, "❓", "Unknown", &BOLD_PINK, f)?;
write_if_any(stats.excludes, "πŸ‘»", "Excluded", &BOLD_YELLOW, f)?;
write_if_any(stats.timeouts, "⏳", "Timeouts", &BOLD_YELLOW, f)?;
write_if_any(stats.unsupported, "β›”", "Unsupported", &BOLD_YELLOW, f)?;
write_if_any(stats.redirects, "πŸ”€", "Redirects", &BOLD_YELLOW, f)?;
color!(
f,
BOLD_PINK,
" {}{} {}",
icon(mode, "🚫 "),
total_errors,
err_str
)?;

write_if_any(stats.unknown, icon(mode, "❓ "), "Unknown", &BOLD_PINK, f)?;
write_if_any(
stats.excludes,
icon(mode, "πŸ‘» "),
"Excluded",
&BOLD_YELLOW,
f,
)?;
write_if_any(
stats.timeouts,
icon(mode, "⏳ "),
"Timeouts",
&BOLD_YELLOW,
f,
)?;
write_if_any(
stats.unsupported,
icon(mode, "β›” "),
"Unsupported",
&BOLD_YELLOW,
f,
)?;
write_if_any(
stats.redirects,
icon(mode, "πŸ”€ "),
"Redirects",
&BOLD_YELLOW,
f,
)?;

Ok(())
}
Expand Down Expand Up @@ -102,6 +141,7 @@ fn write_suggestions(
f: &mut Formatter<'_>,
stats: &ResponseStats,
source: &lychee_lib::InputSource,
symbol: &str,
) -> Result<(), fmt::Error> {
if let Some(suggestions) = stats.suggestion_map.get(source) {
// Sort suggestions
Expand All @@ -111,7 +151,7 @@ fn write_suggestions(
numeric_sort::cmp(&a, &b)
});

writeln!(f, "\nβ„Ή Suggestions")?;
writeln!(f, "\n{symbol}Suggestions")?;
for suggestion in sorted_suggestions {
writeln!(f, "{suggestion}")?;
}
Expand All @@ -128,7 +168,7 @@ fn write_if_any(
f: &mut fmt::Formatter<'_>,
) -> Result<(), fmt::Error> {
if value > 0 {
color!(f, style, " {} {} {}", symbol, value, text)?;
color!(f, style, " {}{} {}", symbol, value, text)?;
}
Ok(())
}
Expand All @@ -151,6 +191,7 @@ impl StatsFormatter for Compact {
};
let host_stats = CompactHostStats {
host_stats: stats.host_stats,
mode: self.mode.clone(),
};

Ok(format!("{response_stats}\n{host_stats}"))
Expand Down Expand Up @@ -184,17 +225,33 @@ mod tests {
[404] https://github.com/mre/idiomatic-rust-doesnt-exist-man (at 1:1) | Rejected status code: 404 Not Found
[TIMEOUT] https://httpbin.org/delay/2 (at 1:1) | Request timed out

β„Ή Suggestions
Suggestions
https://original.dev/ --> https://suggestion.dev/

πŸ” 5 Total (in 0s) πŸ”— 5 Unique βœ… 3 OK 🚫 1 Error ⏳ 1 Timeouts πŸ”€ 1 Redirects
5 Total (in 0s) 5 Unique 3 OK 1 Error 1 Timeouts 1 Redirects

πŸ“Š Per-host Statistics (1 domains, 5 requests)
Per-host Statistics (1 domains, 5 requests)
example.com 5 reqs (20% cached) [βœ“ 3, βœ— 1, ? 1]
"
);
}

#[test]
fn test_formatter_emoji_mode_keeps_symbols() {
let formatter = Compact::new(OutputMode::Emoji);
let result = formatter.format(get_dummy_stats()).unwrap();
let without_color_codes = Regex::new(r"\u{1b}\[[0-9;]*m")
.unwrap()
.replace_all(&result, "")
.to_string();

assert!(without_color_codes.contains("\nβ„Ή Suggestions\n"));
assert!(without_color_codes.contains(
"\nπŸ” 5 Total (in 0s) πŸ”— 5 Unique βœ… 3 OK 🚫 1 Error ⏳ 1 Timeouts πŸ”€ 1 Redirects\n"
));
assert!(without_color_codes.contains("\nπŸ“Š Per-host Statistics (1 domains, 5 requests)\n"));
}

#[test]
fn test_formatter_lists_ignored_only_input() {
use std::collections::{HashMap, HashSet};
Expand Down Expand Up @@ -242,7 +299,7 @@ https://original.dev/ --> https://suggestion.dev/
[https://example.com/]:
[IGNORED] https://example.com/ignored | Unsupported: URL is missing a hostname

πŸ” 1 Total (in 0s) πŸ”— 1 Unique βœ… 0 OK 🚫 0 Errors β›” 1 Unsupported"
1 Total (in 0s) 1 Unique 0 OK 0 Errors 1 Unsupported"
);
}
}
77 changes: 45 additions & 32 deletions lychee-bin/src/formatters/stats/detailed.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ use crate::{
formatters::{
get_response_formatter,
host_stats::DetailedHostStats,
icon,
stats::{OutputStats, ResponseStats},
},
};
Expand Down Expand Up @@ -43,17 +44,19 @@ impl Display for DetailedResponseStats {
let stats = &self.stats;
let separator = "-".repeat(WIDTH + 1);

writeln!(f, "πŸ“ Summary")?;
let label = |symbol, text| format!("{}{text}", icon(&self.mode, symbol));

writeln!(f, "{}", label("πŸ“ ", "Summary"))?;
writeln!(f, "{separator}")?;
write_stat(f, "πŸ” Total", stats.total, true)?;
write_stat(f, "πŸ”— Unique", stats.unique, true)?;
write_stat(f, "βœ… Successful", stats.successful, true)?;
write_stat(f, "⏳ Timeouts", stats.timeouts, true)?;
write_stat(f, "πŸ”€ Redirected", stats.redirects, true)?;
write_stat(f, "πŸ‘» Excluded", stats.excludes, true)?;
write_stat(f, "❓ Unknown", stats.unknown, true)?;
write_stat(f, "🚫 Errors", stats.errors, true)?;
write_stat(f, "β›” Unsupported", stats.unsupported, false)?;
write_stat(f, &label("πŸ” ", "Total"), stats.total, true)?;
write_stat(f, &label("πŸ”— ", "Unique"), stats.unique, true)?;
write_stat(f, &label("βœ… ", "Successful"), stats.successful, true)?;
write_stat(f, &label("⏳ ", "Timeouts"), stats.timeouts, true)?;
write_stat(f, &label("πŸ”€ ", "Redirected"), stats.redirects, true)?;
write_stat(f, &label("πŸ‘» ", "Excluded"), stats.excludes, true)?;
write_stat(f, &label("❓ ", "Unknown"), stats.unknown, true)?;
write_stat(f, &label("🚫 ", "Errors"), stats.errors, true)?;
write_stat(f, &label("β›” ", "Unsupported"), stats.unsupported, false)?;

let response_formatter = get_response_formatter(&self.mode);

Expand Down Expand Up @@ -126,6 +129,7 @@ impl StatsFormatter for Detailed {
};
let host_stats = DetailedHostStats {
host_stats: stats.host_stats,
mode: self.mode.clone(),
};

Ok(format!("{response_stats}\n{host_stats}"))
Expand All @@ -145,17 +149,17 @@ mod tests {

assert_eq!(
result,
"πŸ“ Summary
"Summary
---------------------
πŸ” Total............5
πŸ”— Unique...........5
βœ… Successful.......3
⏳ Timeouts.........1
πŸ”€ Redirected.......1
πŸ‘» Excluded.........0
❓ Unknown..........0
🚫 Errors...........1
β›” Unsupported......0
Total..............5
Unique.............5
Successful.........3
Timeouts...........1
Redirected.........1
Excluded...........0
Unknown............0
Errors.............1
Unsupported........0

Errors in https://example.com/
[404] https://github.com/mre/idiomatic-rust-doesnt-exist-man (at 1:1) | Rejected status code: 404 Not Found
Expand All @@ -169,8 +173,8 @@ Redirects in https://example.com/
https://1.dev/ --[308]--> https://2.dev/ --[308]--> http://redirected.dev/


πŸ“Š Per-host Statistics (1 domains, 5 requests)
---------------------------------------------
Per-host Statistics (1 domains, 5 requests)
-------------------------------------------

Host: example.com
Total requests: 5
Expand All @@ -184,6 +188,15 @@ Host: example.com
);
}

#[test]
fn test_detailed_formatter_emoji_mode_keeps_symbols() {
let formatter = Detailed::new(OutputMode::Emoji);
let result = formatter.format(get_dummy_stats()).unwrap();

assert!(result.starts_with("πŸ“ Summary\n---------------------\nπŸ” Total............5\n"));
assert!(result.contains("\nπŸ“Š Per-host Statistics (1 domains, 5 requests)\n"));
}

#[test]
fn test_detailed_formatter_lists_ignored_only_input() {
use std::collections::{HashMap, HashSet};
Expand Down Expand Up @@ -222,17 +235,17 @@ Host: example.com

assert_eq!(
response_stats.to_string(),
"πŸ“ Summary
"Summary
---------------------
πŸ” Total............1
πŸ”— Unique...........1
βœ… Successful.......0
⏳ Timeouts.........0
πŸ”€ Redirected.......0
πŸ‘» Excluded.........0
❓ Unknown..........0
🚫 Errors...........0
β›” Unsupported......1
Total..............1
Unique.............1
Successful.........0
Timeouts...........0
Redirected.........0
Excluded...........0
Unknown............0
Errors.............0
Unsupported........1

Ignored in https://example.com/
[IGNORED] https://example.com/ignored | Unsupported: URL is missing a hostname"
Expand Down
Loading