use std::io;
use std::io::IsTerminal;
use std::io::Write;
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
pub enum WriteMode {
Immediate,
Buffered,
}
pub struct Stdout {
mode: WriteMode,
buffer: Vec<u8>,
}
impl Stdout {
pub fn new(mode: WriteMode) -> Self {
Stdout {
mode,
buffer: Vec::new(),
}
}
pub fn write_all(&mut self, buf: &[u8]) -> Result<(), io::Error> {
match self.mode {
WriteMode::Immediate => write_stdout(buf),
WriteMode::Buffered => self.buffer.write_all(buf),
}
}
pub fn buffer(&self) -> &[u8] {
&self.buffer
}
pub fn is_terminal(&self) -> bool {
io::stdout().is_terminal()
}
}
#[cfg(target_family = "unix")]
fn write_stdout(buf: &[u8]) -> Result<(), io::Error> {
let mut handle = io::stdout().lock();
handle.write_all(buf)?;
Ok(())
}
#[cfg(target_family = "windows")]
fn write_stdout(buf: &[u8]) -> Result<(), io::Error> {
if io::stdout().is_terminal() {
println!("{}", String::from_utf8_lossy(buf));
} else {
let mut handle = io::stdout().lock();
handle.write_all(buf)?;
}
Ok(())
}
#[derive(Clone, Debug)]
pub struct Stderr {
mode: WriteMode,
buffer: String,
progress_bar: String,
}
impl Stderr {
pub fn new(mode: WriteMode) -> Self {
Stderr {
mode,
buffer: String::new(),
progress_bar: String::new(),
}
}
pub fn mode(&self) -> WriteMode {
self.mode
}
pub fn eprintln(&mut self, message: &str) {
match self.mode {
WriteMode::Immediate => {
let has_progress = !self.progress_bar.is_empty();
if has_progress {
self.rewind_cursor();
}
eprintln!("{message}");
if has_progress {
eprint!("{}", self.progress_bar);
}
}
WriteMode::Buffered => {
self.buffer.push_str(message);
self.buffer.push('\n');
}
}
}
pub fn eprint(&mut self, message: &str) {
match self.mode {
WriteMode::Immediate => {
let has_progress = !self.progress_bar.is_empty();
if has_progress {
self.rewind_cursor();
}
eprint!("{message}");
if has_progress {
eprint!("{}", self.progress_bar);
}
}
WriteMode::Buffered => {
self.buffer.push_str(message);
}
}
}
pub fn set_progress_bar(&mut self, progress: &str) {
match self.mode {
WriteMode::Immediate => {
self.progress_bar = progress.to_string();
eprint!("{}", self.progress_bar);
}
WriteMode::Buffered => {}
}
}
pub fn clear_progress_bar(&mut self) {
self.rewind_cursor();
self.progress_bar.clear();
}
pub fn buffer(&self) -> &str {
&self.buffer
}
pub fn set_buffer(&mut self, buffer: String) {
self.buffer = buffer;
}
fn rewind_cursor(&self) {
if self.progress_bar.is_empty() {
return;
}
match self.mode {
WriteMode::Immediate => {
let lines = self.progress_bar.chars().filter(|c| *c == '\n').count();
if lines > 0 {
(0..lines).for_each(|_| eprint!("\x1B[1A\x1B[K"));
} else {
eprint!("\x1B[K");
}
}
WriteMode::Buffered => {}
}
}
}
#[cfg(test)]
mod tests {
use crate::util::term::{Stderr, Stdout, WriteMode};
#[test]
fn buffered_stdout() {
let mut stdout = Stdout::new(WriteMode::Buffered);
stdout.write_all(b"Hello").unwrap();
stdout.write_all(b" ").unwrap();
stdout.write_all(b"World!").unwrap();
assert_eq!(stdout.buffer(), b"Hello World!");
}
#[test]
fn buffered_stderr() {
let mut stderr = Stderr::new(WriteMode::Buffered);
stderr.eprintln("toto");
stderr.set_progress_bar("some progress...\r");
stderr.eprintln("tutu");
assert_eq!(stderr.buffer(), "toto\ntutu\n");
}
}