use crate::terminal::detect::{supports_interactive_output, terminal_width_for_fd};
use std::fmt;
use std::io::{self, Write};
use std::sync::{Arc, Mutex};
pub enum DrawTarget {
Stdout,
Stderr,
Hidden,
CustomWriter(Arc<Mutex<dyn Write + Send>>),
}
impl DrawTarget {
pub fn stdout() -> Self {
Self::Stdout
}
pub fn stderr() -> Self {
Self::Stderr
}
pub fn hidden() -> Self {
Self::Hidden
}
pub fn to_writer(w: impl Write + Send + 'static) -> Self {
Self::CustomWriter(Arc::new(Mutex::new(w)))
}
pub fn is_hidden_for_tests(&self) -> bool {
self.is_hidden()
}
pub(crate) fn is_hidden(&self) -> bool {
matches!(self, Self::Hidden)
}
pub(crate) fn is_tty(&self) -> bool {
match self {
Self::Stdout => supports_interactive_output(1),
Self::Stderr => supports_interactive_output(2),
Self::Hidden | Self::CustomWriter(_) => false,
}
}
pub(crate) fn width(&self) -> usize {
match self {
Self::Stdout => terminal_width_for_fd(1),
Self::Stderr => terminal_width_for_fd(2),
Self::Hidden | Self::CustomWriter(_) => terminal_width_for_fd(1),
}
}
pub(crate) fn write_str(&self, s: &str) {
match self {
Self::Stdout => {
let mut out = io::stdout().lock();
let _ = out.write_all(s.as_bytes());
}
Self::Stderr => {
let mut out = io::stderr().lock();
let _ = out.write_all(s.as_bytes());
}
Self::Hidden => {}
Self::CustomWriter(writer) => {
if let Ok(mut guard) = writer.lock() {
let _ = guard.write_all(s.as_bytes());
}
}
}
}
pub(crate) fn flush(&self) {
match self {
Self::Stdout => {
let _ = io::stdout().lock().flush();
}
Self::Stderr => {
let _ = io::stderr().lock().flush();
}
Self::Hidden => {}
Self::CustomWriter(writer) => {
if let Ok(mut guard) = writer.lock() {
let _ = guard.flush();
}
}
}
}
}
impl Clone for DrawTarget {
fn clone(&self) -> Self {
match self {
Self::Stdout => Self::Stdout,
Self::Stderr => Self::Stderr,
Self::Hidden => Self::Hidden,
Self::CustomWriter(writer) => Self::CustomWriter(Arc::clone(writer)),
}
}
}
impl fmt::Debug for DrawTarget {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Stdout => f.write_str("DrawTarget::Stdout"),
Self::Stderr => f.write_str("DrawTarget::Stderr"),
Self::Hidden => f.write_str("DrawTarget::Hidden"),
Self::CustomWriter(_) => f.write_str("DrawTarget::CustomWriter(..)"),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::sync::{Arc, Mutex};
#[derive(Clone, Default)]
struct SharedBuffer(Arc<Mutex<Vec<u8>>>);
impl Write for SharedBuffer {
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
match self.0.lock() {
Ok(mut inner) => inner.write(buf),
Err(poisoned) => poisoned.into_inner().write(buf),
}
}
fn flush(&mut self) -> io::Result<()> {
Ok(())
}
}
#[test]
fn test_hidden_target_is_hidden() {
assert!(DrawTarget::hidden().is_hidden());
}
#[test]
fn test_stdout_not_hidden() {
assert!(!DrawTarget::stdout().is_hidden());
}
#[test]
fn test_custom_writer_captures_output() {
let buffer = SharedBuffer::default();
let mirror = buffer.clone();
let target = DrawTarget::to_writer(buffer);
target.write_str("hello");
let bytes = match mirror.0.lock() {
Ok(inner) => inner.clone(),
Err(poisoned) => poisoned.into_inner().clone(),
};
assert_eq!(bytes, b"hello");
}
#[test]
fn test_draw_target_clone() {
let first = DrawTarget::hidden();
let second = first.clone();
assert!(second.is_hidden());
}
}