use std::io::{self, stderr, stdout, LineWriter, Stderr, Stdout, Write};
use crate::utils::test_pretend_term;
#[derive(Debug)]
pub enum Buffer {
File(std::fs::File),
Redirect(Stdout),
Stdout(Stdout),
Stderr(Stderr),
}
impl Buffer {
pub fn new(download: bool, output: &Option<String>, is_stdout_tty: bool) -> io::Result<Self> {
Ok(if download {
Buffer::Stderr(stderr())
} else if let Some(output) = output {
let file = std::fs::File::create(&output)?;
Buffer::File(file)
} else if is_stdout_tty {
Buffer::Stdout(stdout())
} else {
Buffer::Redirect(stdout())
})
}
pub fn is_terminal(&self) -> bool {
matches!(self, Buffer::Stdout(..) | Buffer::Stderr(..))
|| (matches!(self, Buffer::Redirect(..)) && test_pretend_term())
}
pub fn is_redirect(&self) -> bool {
matches!(self, Buffer::Redirect(..))
}
pub fn print(&mut self, s: &str) -> io::Result<()> {
write!(self.inner(), "{}", s)
}
fn inner(&mut self) -> &mut dyn Write {
match self {
Buffer::File(file) => file,
Buffer::Redirect(stdout) | Buffer::Stdout(stdout) => stdout,
Buffer::Stderr(stderr) => stderr,
}
}
pub fn with_guard(
&mut self,
code: impl FnOnce(&mut dyn Write) -> io::Result<()>,
) -> io::Result<()> {
if self.is_terminal() {
let mut guard = LineWriter::new(BinaryGuard(self));
code(&mut guard)?;
guard.flush()
} else {
code(self.inner())
}
}
pub fn unguarded(&mut self) -> &mut dyn Write {
self.inner()
}
}
struct BinaryGuard<'a>(&'a mut Buffer);
impl BinaryGuard<'_> {
fn check_dirty(&mut self, buf: &[u8]) -> io::Result<()> {
if buf.contains(&b'\0') {
Err(io::Error::new(
io::ErrorKind::InvalidData,
"Found binary data",
))
} else {
Ok(())
}
}
}
impl Write for BinaryGuard<'_> {
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
self.check_dirty(buf)?;
self.0.inner().write(buf)
}
fn flush(&mut self) -> io::Result<()> {
self.0.inner().flush()
}
fn write_all(&mut self, buf: &[u8]) -> io::Result<()> {
self.check_dirty(buf)?;
self.0.inner().write_all(buf)
}
}