use indicatif::ProgressBar;
use std::io::Write;
use std::sync::Mutex;
static BULK_PROGRESS_BAR: Mutex<Option<ProgressBar>> = Mutex::new(None);
pub fn set_bulk_progress_bar(pb: ProgressBar) {
let mut guard = BULK_PROGRESS_BAR
.lock()
.expect("progress bar mutex poisoned");
*guard = Some(pb);
}
pub fn clear_bulk_progress_bar() {
let mut guard = BULK_PROGRESS_BAR
.lock()
.expect("progress bar mutex poisoned");
*guard = None;
}
pub fn get_bulk_progress_bar() -> Option<ProgressBar> {
let guard = BULK_PROGRESS_BAR
.lock()
.expect("progress bar mutex poisoned");
guard.clone()
}
pub fn bar_println(pb: &ProgressBar, line: &str) -> std::io::Result<()> {
bar_println_or(pb, line, &mut std::io::stderr().lock())
}
fn bar_println_or<W: Write>(pb: &ProgressBar, line: &str, fallback: &mut W) -> std::io::Result<()> {
if pb.is_hidden() {
fallback.write_all(line.as_bytes())?;
fallback.write_all(b"\n")
} else {
pb.println(line);
Ok(())
}
}
fn write_line(line: &str) -> std::io::Result<()> {
match get_bulk_progress_bar() {
Some(pb) => bar_println(&pb, line),
None => {
let mut stderr = std::io::stderr().lock();
stderr.write_all(line.as_bytes())?;
stderr.write_all(b"\n")
}
}
}
pub struct ProgressWriter {
buffer: Vec<u8>,
}
impl ProgressWriter {
pub fn new() -> Self {
Self { buffer: Vec::new() }
}
}
impl Default for ProgressWriter {
fn default() -> Self {
Self::new()
}
}
impl Write for ProgressWriter {
fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
self.buffer.extend_from_slice(buf);
while let Some(newline_pos) = self.buffer.iter().position(|&b| b == b'\n') {
let line: Vec<u8> = self.buffer.drain(..=newline_pos).collect();
let line_str = String::from_utf8_lossy(&line);
let trimmed = line_str.trim_end_matches('\n');
write_line(trimmed)?;
}
Ok(buf.len())
}
fn flush(&mut self) -> std::io::Result<()> {
if !self.buffer.is_empty() {
let line_str = String::from_utf8_lossy(&self.buffer);
let trimmed = line_str.trim_end();
if !trimmed.is_empty() {
write_line(trimmed)?;
}
self.buffer.clear();
}
Ok(())
}
}
impl Drop for ProgressWriter {
fn drop(&mut self) {
let _ = self.flush();
}
}
pub struct ProgressWriterFactory;
impl ProgressWriterFactory {
pub fn new() -> Self {
Self
}
}
impl Default for ProgressWriterFactory {
fn default() -> Self {
Self::new()
}
}
impl<'a> tracing_subscriber::fmt::MakeWriter<'a> for ProgressWriterFactory {
type Writer = ProgressWriter;
fn make_writer(&'a self) -> Self::Writer {
ProgressWriter::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn hidden_bar_falls_back_to_the_stderr_sink() {
let pb = ProgressBar::hidden();
let mut sink = Vec::new();
bar_println_or(&pb, "\u{2717} bad.example (timeout)", &mut sink).expect("write");
assert_eq!(
String::from_utf8(sink).expect("utf8"),
"\u{2717} bad.example (timeout)\n"
);
}
}