use std::{
env::var_os,
io::{self, Write},
path::Path,
};
use crate::{
cli::Pretty,
utils::{test_default_color, test_pretend_term},
};
pub use imp::Buffer;
#[cfg(not(windows))]
mod imp {
use std::io::{BufWriter, Write};
use termcolor::{Ansi, WriteColor};
pub struct Buffer {
inner: Ansi<BufWriter<Inner>>,
terminal: bool,
redirect: bool,
}
enum Inner {
File(std::fs::File),
Stdout(std::io::Stdout),
Stderr(std::io::Stderr),
}
impl Buffer {
pub fn stdout() -> Self {
Self {
inner: Ansi::new(BufWriter::new(Inner::Stdout(std::io::stdout()))),
terminal: true,
redirect: false,
}
}
pub fn stderr() -> Self {
Self {
inner: Ansi::new(BufWriter::new(Inner::Stderr(std::io::stderr()))),
terminal: true,
redirect: false,
}
}
pub fn redirect() -> Self {
Self {
inner: Ansi::new(BufWriter::new(Inner::Stdout(std::io::stdout()))),
terminal: crate::test_pretend_term(),
redirect: true,
}
}
pub fn file(file: std::fs::File) -> Self {
Self {
inner: Ansi::new(BufWriter::new(Inner::File(file))),
terminal: false,
redirect: false,
}
}
pub fn is_terminal(&self) -> bool {
self.terminal
}
pub fn is_redirect(&self) -> bool {
self.redirect
}
#[cfg(test)]
pub fn is_stdout(&self) -> bool {
matches!(self.inner.get_ref().get_ref(), Inner::Stdout(_))
}
#[cfg(test)]
pub fn is_stderr(&self) -> bool {
matches!(self.inner.get_ref().get_ref(), Inner::Stderr(_))
}
#[cfg(test)]
pub fn is_file(&self) -> bool {
matches!(self.inner.get_ref().get_ref(), Inner::File(_))
}
}
impl Write for Inner {
fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
match self {
Inner::File(w) => w.write(buf),
Inner::Stdout(w) => w.write(buf),
Inner::Stderr(w) => w.write(buf),
}
}
fn write_all(&mut self, buf: &[u8]) -> std::io::Result<()> {
match self {
Inner::File(w) => w.write_all(buf),
Inner::Stdout(w) => w.write_all(buf),
Inner::Stderr(w) => w.write_all(buf),
}
}
fn flush(&mut self) -> std::io::Result<()> {
match self {
Inner::File(w) => w.flush(),
Inner::Stdout(w) => w.flush(),
Inner::Stderr(w) => w.flush(),
}
}
}
impl Write for Buffer {
fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
self.inner.write(buf)
}
fn write_all(&mut self, buf: &[u8]) -> std::io::Result<()> {
self.inner.get_mut().write_all(buf)
}
fn flush(&mut self) -> std::io::Result<()> {
self.inner.flush()
}
}
impl WriteColor for Buffer {
fn supports_color(&self) -> bool {
true
}
fn set_color(&mut self, spec: &termcolor::ColorSpec) -> std::io::Result<()> {
self.inner.set_color(spec)
}
fn reset(&mut self) -> std::io::Result<()> {
self.inner.reset()
}
}
}
#[cfg(windows)]
mod imp {
use std::io::{BufWriter, Write};
use termcolor::{Ansi, BufferedStandardStream, ColorChoice, WriteColor};
use crate::utils::test_default_color;
pub enum Buffer {
File(Ansi<BufWriter<std::fs::File>>),
Redirect(Ansi<BufWriter<std::io::Stdout>>),
Stdout(BufferedStandardStream),
Stderr(BufferedStandardStream),
}
impl Buffer {
pub fn stdout() -> Self {
Buffer::Stdout(BufferedStandardStream::stdout(if test_default_color() {
ColorChoice::AlwaysAnsi
} else {
ColorChoice::Always
}))
}
pub fn stderr() -> Self {
Buffer::Stderr(BufferedStandardStream::stderr(if test_default_color() {
ColorChoice::AlwaysAnsi
} else {
ColorChoice::Always
}))
}
pub fn redirect() -> Self {
Buffer::Redirect(Ansi::new(BufWriter::new(std::io::stdout())))
}
pub fn file(file: std::fs::File) -> Self {
Buffer::File(Ansi::new(BufWriter::new(file)))
}
pub fn is_terminal(&self) -> bool {
matches!(self, Buffer::Stdout(_) | Buffer::Stderr(_))
}
pub fn is_redirect(&self) -> bool {
matches!(self, Buffer::Redirect(_))
}
#[cfg(test)]
pub fn is_stdout(&self) -> bool {
matches!(self, Buffer::Stdout(_))
}
#[cfg(test)]
pub fn is_stderr(&self) -> bool {
matches!(self, Buffer::Stderr(_))
}
#[cfg(test)]
pub fn is_file(&self) -> bool {
matches!(self, Buffer::File(_))
}
}
impl Write for Buffer {
fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
match self {
Buffer::File(w) => w.write(buf),
Buffer::Redirect(w) => w.write(buf),
Buffer::Stdout(w) | Buffer::Stderr(w) => w.write(buf),
}
}
fn write_all(&mut self, buf: &[u8]) -> std::io::Result<()> {
match self {
Buffer::File(w) => w.get_mut().write_all(buf),
Buffer::Redirect(w) => w.get_mut().write_all(buf),
Buffer::Stdout(w) | Buffer::Stderr(w) => w.write_all(buf),
}
}
fn flush(&mut self) -> std::io::Result<()> {
match self {
Buffer::File(w) => w.flush(),
Buffer::Redirect(w) => w.flush(),
Buffer::Stdout(w) | Buffer::Stderr(w) => w.flush(),
}
}
}
impl WriteColor for Buffer {
fn supports_color(&self) -> bool {
match self {
Buffer::File(w) => w.supports_color(),
Buffer::Redirect(w) => w.supports_color(),
Buffer::Stdout(w) | Buffer::Stderr(w) => w.supports_color(),
}
}
fn set_color(&mut self, spec: &termcolor::ColorSpec) -> std::io::Result<()> {
match self {
Buffer::File(w) => w.set_color(spec),
Buffer::Redirect(w) => w.set_color(spec),
Buffer::Stdout(w) | Buffer::Stderr(w) => w.set_color(spec),
}
}
fn reset(&mut self) -> std::io::Result<()> {
match self {
Buffer::File(w) => w.reset(),
Buffer::Redirect(w) => w.reset(),
Buffer::Stdout(w) | Buffer::Stderr(w) => w.reset(),
}
}
fn is_synchronous(&self) -> bool {
match self {
Buffer::File(w) => w.is_synchronous(),
Buffer::Redirect(w) => w.is_synchronous(),
Buffer::Stdout(w) | Buffer::Stderr(w) => w.is_synchronous(),
}
}
}
}
impl Buffer {
pub fn new(download: bool, output: Option<&Path>, is_stdout_tty: bool) -> io::Result<Self> {
log::trace!("is_stdout_tty: {is_stdout_tty}");
Ok(if download {
Buffer::stderr()
} else if let Some(output) = output {
log::trace!("creating file {output:?}");
let file = std::fs::File::create(output)?;
Buffer::file(file)
} else if is_stdout_tty {
Buffer::stdout()
} else {
Buffer::redirect()
})
}
pub fn print(&mut self, s: &str) -> io::Result<()> {
self.write_all(s.as_bytes())
}
pub fn guess_pretty(&self) -> Pretty {
if test_default_color() {
Pretty::All
} else if test_pretend_term() {
Pretty::Format
} else if self.is_terminal() {
if cfg!(test) {
Pretty::All
} else if var_os("NO_COLOR").is_some_and(|val| !val.is_empty()) {
Pretty::Format
} else {
match var_os("TERM") {
Some(term) if term == "dumb" => Pretty::Format,
Some(_) => Pretty::All,
None if cfg!(windows) => Pretty::All,
None => Pretty::Format,
}
}
} else {
Pretty::None
}
}
}