-
-
Notifications
You must be signed in to change notification settings - Fork 116
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add response formatter; refactor stats formatter (#1398)
This adds support for formatting responses in different ways. For now, the options are: * `plain`: No color, basic formatting * `color`: Color, indented formatting (default) * `emoji`: Fancy mode with emoji icons Fixes #546 Related to #271
- Loading branch information
Showing
24 changed files
with
713 additions
and
238 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
18 changes: 17 additions & 1 deletion
18
lychee-bin/src/color.rs → lychee-bin/src/formatters/color.rs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,21 +1,37 @@ | ||
//! Defines the colors used in the output of the CLI. | ||
|
||
use console::Style; | ||
use log::Level; | ||
use once_cell::sync::Lazy; | ||
|
||
pub(crate) static NORMAL: Lazy<Style> = Lazy::new(Style::new); | ||
pub(crate) static DIM: Lazy<Style> = Lazy::new(|| Style::new().dim()); | ||
|
||
pub(crate) static GREEN: Lazy<Style> = Lazy::new(|| Style::new().color256(82).bright()); | ||
pub(crate) static GREEN: Lazy<Style> = Lazy::new(|| Style::new().color256(2).bold().bright()); | ||
pub(crate) static BOLD_GREEN: Lazy<Style> = Lazy::new(|| Style::new().color256(82).bold().bright()); | ||
pub(crate) static YELLOW: Lazy<Style> = Lazy::new(|| Style::new().yellow().bright()); | ||
pub(crate) static BOLD_YELLOW: Lazy<Style> = Lazy::new(|| Style::new().yellow().bold().bright()); | ||
pub(crate) static PINK: Lazy<Style> = Lazy::new(|| Style::new().color256(197)); | ||
pub(crate) static BOLD_PINK: Lazy<Style> = Lazy::new(|| Style::new().color256(197).bold()); | ||
|
||
// Used for debug log messages | ||
pub(crate) static BLUE: Lazy<Style> = Lazy::new(|| Style::new().blue().bright()); | ||
|
||
// Write output using predefined colors | ||
macro_rules! color { | ||
($f:ident, $color:ident, $text:tt, $($tts:tt)*) => { | ||
write!($f, "{}", $color.apply_to(format!($text, $($tts)*))) | ||
}; | ||
} | ||
|
||
/// Returns the appropriate color for a given log level. | ||
pub(crate) fn color_for_level(level: Level) -> &'static Style { | ||
match level { | ||
Level::Error => &BOLD_PINK, | ||
Level::Warn => &BOLD_YELLOW, | ||
Level::Info | Level::Debug => &BLUE, | ||
Level::Trace => &DIM, | ||
} | ||
} | ||
|
||
pub(crate) use color; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,83 @@ | ||
use env_logger::{Builder, Env}; | ||
use log::LevelFilter; | ||
use std::io::Write; | ||
|
||
use crate::{ | ||
formatters::{self, response::MAX_RESPONSE_OUTPUT_WIDTH}, | ||
options::OutputMode, | ||
verbosity::Verbosity, | ||
}; | ||
|
||
/// Initialize the logging system with the given verbosity level. | ||
pub(crate) fn init_logging(verbose: &Verbosity, mode: &OutputMode) { | ||
// Set a base level for all modules to `warn`, which is a reasonable default. | ||
// It will be overridden by RUST_LOG if it's set. | ||
let env = Env::default().filter_or("RUST_LOG", "warn"); | ||
|
||
let mut builder = Builder::from_env(env); | ||
builder | ||
.format_timestamp(None) | ||
.format_module_path(false) | ||
.format_target(false); | ||
|
||
if std::env::var("RUST_LOG").is_err() { | ||
// Adjust the base log level filter based on the verbosity from CLI. | ||
// This applies to all modules not explicitly mentioned in RUST_LOG. | ||
let level_filter = verbose.log_level_filter(); | ||
|
||
// Apply a global filter. This ensures that, by default, other modules don't log at the debug level. | ||
builder.filter_level(LevelFilter::Info); | ||
|
||
// Apply more specific filters to your own crates, enabling more verbose logging as per `-vv`. | ||
builder | ||
.filter_module("lychee", level_filter) | ||
.filter_module("lychee_lib", level_filter); | ||
} | ||
|
||
// Calculate the longest log level text, including brackets. | ||
let max_level_text_width = log::LevelFilter::iter() | ||
.map(|level| level.as_str().len() + 2) | ||
.max() | ||
.unwrap_or(0); | ||
|
||
// Customize the log message format according to the output mode | ||
if mode.is_plain() { | ||
// Explicitly disable colors for plain output | ||
builder.format(move |buf, record| writeln!(buf, "[{}] {}", record.level(), record.args())); | ||
} else if mode.is_emoji() { | ||
// Disable padding, keep colors | ||
builder.format(move |buf, record| { | ||
let level = record.level(); | ||
let color = formatters::color::color_for_level(level); | ||
writeln!( | ||
buf, | ||
"{} {}", | ||
color.apply_to(format!("[{level}]")), | ||
record.args() | ||
) | ||
}); | ||
} else { | ||
builder.format(move |buf, record| { | ||
let level = record.level(); | ||
let level_text = format!("{level:5}"); | ||
let padding = (MAX_RESPONSE_OUTPUT_WIDTH.saturating_sub(max_level_text_width)).max(0); | ||
let prefix = format!( | ||
"{:<width$}", | ||
format!("[{}]", level_text), | ||
width = max_level_text_width | ||
); | ||
let color = formatters::color::color_for_level(level); | ||
let colored_level = color.apply_to(&prefix); | ||
writeln!( | ||
buf, | ||
"{:<padding$}{} {}", | ||
"", | ||
colored_level, | ||
record.args(), | ||
padding = padding | ||
) | ||
}); | ||
} | ||
|
||
builder.init(); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,47 +1,41 @@ | ||
pub(crate) mod color; | ||
pub(crate) mod duration; | ||
pub(crate) mod log; | ||
pub(crate) mod response; | ||
pub(crate) mod stats; | ||
|
||
use lychee_lib::{CacheStatus, ResponseBody, Status}; | ||
use self::{response::ResponseFormatter, stats::StatsFormatter}; | ||
use crate::options::{OutputMode, StatsFormat}; | ||
use supports_color::Stream; | ||
|
||
use crate::{ | ||
color::{DIM, GREEN, NORMAL, PINK, YELLOW}, | ||
options::{self, Format}, | ||
}; | ||
|
||
use self::response::ResponseFormatter; | ||
|
||
/// Detects whether a terminal supports color, and gives details about that | ||
/// support. It takes into account the `NO_COLOR` environment variable. | ||
fn supports_color() -> bool { | ||
supports_color::on(Stream::Stdout).is_some() | ||
} | ||
|
||
/// Color the response body for TTYs that support it | ||
pub(crate) fn color_response(body: &ResponseBody) -> String { | ||
if supports_color() { | ||
let out = match body.status { | ||
Status::Ok(_) | Status::Cached(CacheStatus::Ok(_)) => GREEN.apply_to(body), | ||
Status::Excluded | ||
| Status::Unsupported(_) | ||
| Status::Cached(CacheStatus::Excluded | CacheStatus::Unsupported) => { | ||
DIM.apply_to(body) | ||
} | ||
Status::Redirected(_) => NORMAL.apply_to(body), | ||
Status::UnknownStatusCode(_) | Status::Timeout(_) => YELLOW.apply_to(body), | ||
Status::Error(_) | Status::Cached(CacheStatus::Error(_)) => PINK.apply_to(body), | ||
}; | ||
out.to_string() | ||
} else { | ||
body.to_string() | ||
/// Create a stats formatter based on the given format option | ||
pub(crate) fn get_stats_formatter( | ||
format: &StatsFormat, | ||
mode: &OutputMode, | ||
) -> Box<dyn StatsFormatter> { | ||
match format { | ||
StatsFormat::Compact => Box::new(stats::Compact::new(mode.clone())), | ||
StatsFormat::Detailed => Box::new(stats::Detailed::new(mode.clone())), | ||
StatsFormat::Json => Box::new(stats::Json::new()), | ||
StatsFormat::Markdown => Box::new(stats::Markdown::new()), | ||
StatsFormat::Raw => Box::new(stats::Raw::new()), | ||
} | ||
} | ||
|
||
/// Create a response formatter based on the given format option | ||
pub(crate) fn get_formatter(format: &options::Format) -> Box<dyn ResponseFormatter> { | ||
if matches!(format, Format::Raw) || !supports_color() { | ||
return Box::new(response::Raw::new()); | ||
pub(crate) fn get_response_formatter(mode: &OutputMode) -> Box<dyn ResponseFormatter> { | ||
if !supports_color() { | ||
return Box::new(response::PlainFormatter); | ||
} | ||
match mode { | ||
OutputMode::Plain => Box::new(response::PlainFormatter), | ||
OutputMode::Color => Box::new(response::ColorFormatter), | ||
OutputMode::Emoji => Box::new(response::EmojiFormatter), | ||
} | ||
Box::new(response::Color::new()) | ||
} |
Oops, something went wrong.