use indicatif::{ProgressBar as Bar, ProgressStyle};
use log::Level;
use lychee_lib::ResponseBody;
use std::sync::{Arc, LazyLock};
use crate::{
config::OutputMode,
formatters::{get_progress_formatter, response::ResponseFormatter},
};
#[derive(Clone)]
struct ProgressConfig {
template: &'static str,
progress_chars: &'static str,
}
const CONFIG: ProgressConfig = ProgressConfig {
template: "{pos}/{len:.238} {bar:.162/238} {wide_msg}",
progress_chars: "━ ━",
};
static STYLE: LazyLock<ProgressStyle> = LazyLock::new(|| {
ProgressStyle::with_template(CONFIG.template)
.expect("Valid progress bar")
.progress_chars(CONFIG.progress_chars)
});
#[derive(Clone)]
pub(crate) struct Progress {
bar: Option<Bar>,
log_level: Level,
formatter: Arc<Box<dyn ResponseFormatter>>,
}
impl Progress {
pub(crate) fn new(
initial_message: &'static str,
hide_bar: bool,
log_level: Level,
mode: &OutputMode,
) -> Self {
let detailed = log_level >= Level::Info; let bar = if hide_bar || detailed {
None
} else {
let bar = Bar::new_spinner().with_style(STYLE.clone());
bar.set_length(0);
bar.set_message(initial_message);
Some(bar)
};
Progress {
bar,
log_level,
formatter: Arc::new(get_progress_formatter(mode)),
}
}
pub(crate) fn update(&self, response: Option<&ResponseBody>) {
let response_message = response.map(|r| (r, self.formatter.format_response(r)));
self.print_progress(response_message.as_ref());
self.with_bar(|bar| {
bar.inc(1);
if let Some((_, message)) = response_message {
bar.set_message(message);
}
});
}
fn print_progress(&self, response_message: Option<&(&ResponseBody, String)>) {
if self.log_level < Level::Info {
return;
}
let Some((response, message)) = response_message else {
return;
};
let should_show_success = self.log_level > Level::Info;
let is_success = response.status.is_success();
if !is_success || should_show_success {
eprintln!("{message}");
}
}
pub(crate) fn set_length(&self, n: u64) {
self.with_bar(|b| b.set_length(n));
}
pub(crate) fn inc_length(&self, n: u64) {
self.with_bar(|b| b.inc_length(n));
}
pub(crate) fn finish(&self, message: &'static str) {
self.with_bar(|b| b.finish_with_message(message));
}
fn with_bar<F>(&self, action: F)
where
F: FnOnce(&Bar),
{
if let Some(bar) = &self.bar {
action(bar);
}
}
}