use std::io::{self, Write};
use crate::ansi::AnsiDecoder;
use crate::text::Text;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum ProxyStream {
Stdout,
Stderr,
}
pub trait ProxyTarget {
fn proxy_write(&mut self, stream: ProxyStream, bytes: &[u8]) -> bool;
fn proxy_flush(&mut self, stream: ProxyStream) -> bool;
}
pub struct FileProxy<T: ProxyTarget, F: Write> {
target: T,
stream: ProxyStream,
file: F,
}
impl<T: ProxyTarget, F: Write> FileProxy<T, F> {
pub fn new(target: T, stream: ProxyStream, file: F) -> Self {
FileProxy {
target,
stream,
file,
}
}
pub fn rich_proxied_file(&self) -> &F {
&self.file
}
pub fn stream(&self) -> ProxyStream {
self.stream
}
pub fn into_inner(self) -> F {
self.file
}
}
impl<T: ProxyTarget, F: Write> Write for FileProxy<T, F> {
fn write(&mut self, bytes: &[u8]) -> io::Result<usize> {
if self.target.proxy_write(self.stream, bytes) {
Ok(bytes.len())
} else {
self.file.write(bytes)
}
}
fn flush(&mut self) -> io::Result<()> {
if self.target.proxy_flush(self.stream) {
Ok(())
} else {
self.file.flush()
}
}
}
#[derive(Default)]
pub(crate) struct ProxyBuffer {
pending: Vec<u8>,
decoder: AnsiDecoder,
}
impl ProxyBuffer {
pub(crate) fn write(&mut self, bytes: &[u8]) -> Option<Text> {
let mut lines = Vec::new();
let mut rest = bytes;
while let Some(end) = rest.iter().position(|&byte| byte == b'\n') {
let mut line = std::mem::take(&mut self.pending);
line.extend_from_slice(&rest[..end]);
lines.push(String::from_utf8_lossy(&line).into_owned());
rest = &rest[end + 1..];
}
self.pending.extend_from_slice(rest);
if lines.is_empty() {
return None;
}
let decoded: Vec<Text> = lines
.iter()
.map(|line| self.decoder.decode_line(line))
.collect();
Some(Text::new("\n").join(&decoded))
}
pub(crate) fn flush(&mut self) -> Option<String> {
let pending = std::mem::take(&mut self.pending);
(!pending.is_empty()).then(|| String::from_utf8_lossy(&pending).into_owned())
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn buffer_splits_lines_like_upstream() {
let mut buffer = ProxyBuffer::default();
assert!(buffer.write(b"part").is_none());
let text = buffer.write(b"ial\nline2\nrest").expect("two lines");
assert_eq!(text.plain(), "partial\nline2");
assert_eq!(buffer.flush().as_deref(), Some("rest"));
assert_eq!(buffer.flush(), None);
}
struct Stopped;
impl ProxyTarget for Stopped {
fn proxy_write(&mut self, _: ProxyStream, _: &[u8]) -> bool {
false
}
fn proxy_flush(&mut self, _: ProxyStream) -> bool {
false
}
}
#[test]
fn a_stopped_display_writes_to_the_file() {
let mut proxy = FileProxy::new(Stopped, ProxyStream::Stdout, Vec::new());
writeln!(proxy, "plain").unwrap();
proxy.flush().unwrap();
assert_eq!(proxy.into_inner(), b"plain\n");
}
}