use std::sync::{Arc, Mutex};
use arc_swap::ArcSwapOption;
use crate::observer::{LogLevel, colorize_log_line};
pub trait OutputChannel: Send + Sync {
fn op_output(&self, line: &str);
fn log(&self, level: LogLevel, message: &str) {
eprintln!("{}", colorize_log_line(level, message));
}
fn raster(&self, frame: &str) {
use std::io::Write;
let mut out = std::io::stdout().lock();
let _ = out.write_all(frame.as_bytes());
let _ = out.flush();
}
}
pub struct DisplayRoutedChannel;
impl OutputChannel for DisplayRoutedChannel {
fn op_output(&self, line: &str) {
crate::observer::log(crate::observer::LogLevel::Info, line);
}
}
pub struct RawStdoutChannel;
impl OutputChannel for RawStdoutChannel {
fn op_output(&self, line: &str) {
crate::observer::op_output_raw(line);
}
}
#[derive(Default, Clone)]
pub struct CaptureChannel {
op_lines: Arc<Mutex<Vec<String>>>,
log_lines: Arc<Mutex<Vec<(LogLevel, String)>>>,
raster_frames: Arc<Mutex<Vec<String>>>,
}
impl CaptureChannel {
pub fn new() -> Self {
Self::default()
}
pub fn op_lines(&self) -> Vec<String> {
self.op_lines
.lock()
.unwrap_or_else(|e| e.into_inner())
.clone()
}
pub fn log_lines(&self) -> Vec<(LogLevel, String)> {
self.log_lines
.lock()
.unwrap_or_else(|e| e.into_inner())
.clone()
}
pub fn raster_frames(&self) -> Vec<String> {
self.raster_frames
.lock()
.unwrap_or_else(|e| e.into_inner())
.clone()
}
}
impl OutputChannel for CaptureChannel {
fn op_output(&self, line: &str) {
self.op_lines
.lock()
.unwrap_or_else(|e| e.into_inner())
.push(line.to_string());
}
fn log(&self, level: LogLevel, message: &str) {
self.log_lines
.lock()
.unwrap_or_else(|e| e.into_inner())
.push((level, message.to_string()));
}
fn raster(&self, frame: &str) {
self.raster_frames
.lock()
.unwrap_or_else(|e| e.into_inner())
.push(frame.to_string());
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ChannelKind {
RawStdout,
DisplayRouted,
}
pub fn select_kind(silent_console: bool, is_tty: bool) -> ChannelKind {
if is_tty && !silent_console {
ChannelKind::DisplayRouted
} else {
ChannelKind::RawStdout
}
}
pub fn select(silent_console: bool, is_tty: bool) -> Arc<dyn OutputChannel> {
match select_kind(silent_console, is_tty) {
ChannelKind::DisplayRouted => Arc::new(DisplayRoutedChannel),
ChannelKind::RawStdout => Arc::new(RawStdoutChannel),
}
}
struct Holder(Arc<dyn OutputChannel>);
static CHANNEL: ArcSwapOption<Holder> = ArcSwapOption::const_empty();
pub fn install(channel: Arc<dyn OutputChannel>) {
CHANNEL.store(Some(Arc::new(Holder(channel))));
}
pub fn clear() {
CHANNEL.store(None);
}
pub fn installed() -> Option<Arc<dyn OutputChannel>> {
if let Some(ch) = crate::execution_context::current_channel() {
return Some(ch);
}
CHANNEL.load_full().map(|h| h.0.clone())
}
pub fn log_to_surface(level: LogLevel, message: &str) {
if let Some(ch) = installed() {
ch.log(level, message);
} else {
eprintln!("{}", colorize_log_line(level, message));
}
}
pub fn raster(frame: &str) {
if let Some(ch) = installed() {
ch.raster(frame);
} else {
use std::io::Write;
let mut out = std::io::stdout().lock();
let _ = out.write_all(frame.as_bytes());
let _ = out.flush();
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn capture_channel_records_op_output_in_order() {
let ch = CaptureChannel::new();
ch.op_output("id-0");
ch.op_output("id-1");
assert_eq!(ch.op_lines(), vec!["id-0".to_string(), "id-1".to_string()]);
}
#[test]
fn capture_channel_records_raster_frames() {
let ch = CaptureChannel::new();
ch.raster("frame-a");
ch.raster("frame-b");
assert_eq!(
ch.raster_frames(),
vec!["frame-a".to_string(), "frame-b".to_string()]
);
}
#[test]
fn capture_channel_records_log_lines() {
let ch = CaptureChannel::new();
ch.log(LogLevel::Info, "started");
ch.log(LogLevel::Warn, "careful");
assert_eq!(
ch.log_lines(),
vec![
(LogLevel::Info, "started".to_string()),
(LogLevel::Warn, "careful".to_string()),
]
);
}
#[test]
fn select_kind_console_owning_and_piped_are_raw() {
assert_eq!(select_kind(true, true), ChannelKind::RawStdout);
assert_eq!(select_kind(false, false), ChannelKind::RawStdout);
assert_eq!(select_kind(true, false), ChannelKind::RawStdout);
assert_eq!(select_kind(false, true), ChannelKind::DisplayRouted);
}
}