use once_cell::sync::Lazy;
use std::io::{self, Write};
use std::sync::Mutex;
static STDOUT_MUTEX: Lazy<Mutex<io::Stdout>> = Lazy::new(|| Mutex::new(io::stdout()));
static STDERR_MUTEX: Lazy<Mutex<io::Stderr>> = Lazy::new(|| Mutex::new(io::stderr()));
pub fn synchronized_println(text: &str) -> io::Result<()> {
let mut stdout = STDOUT_MUTEX.lock().unwrap();
writeln!(stdout, "{text}")?;
stdout.flush()?;
Ok(())
}
#[allow(dead_code)]
pub fn synchronized_eprintln(text: &str) -> io::Result<()> {
let mut stderr = STDERR_MUTEX.lock().unwrap();
writeln!(stderr, "{text}")?;
stderr.flush()?;
Ok(())
}
#[allow(dead_code)]
pub fn synchronized_print_lines<'a, I>(lines: I) -> io::Result<()>
where
I: Iterator<Item = &'a str>,
{
let mut stdout = STDOUT_MUTEX.lock().unwrap();
for line in lines {
writeln!(stdout, "{line}")?;
}
stdout.flush()?;
Ok(())
}
#[allow(dead_code)]
pub fn synchronized_eprint_lines<'a, I>(lines: I) -> io::Result<()>
where
I: Iterator<Item = &'a str>,
{
let mut stderr = STDERR_MUTEX.lock().unwrap();
for line in lines {
writeln!(stderr, "{line}")?;
}
stderr.flush()?;
Ok(())
}
pub struct NodeOutputWriter {
node_prefix: String,
no_prefix: bool,
}
impl NodeOutputWriter {
#[allow(dead_code)]
pub fn new(node_host: &str) -> Self {
Self::new_with_no_prefix(node_host, false)
}
pub fn new_with_no_prefix(node_host: &str, no_prefix: bool) -> Self {
Self {
node_prefix: format!("[{node_host}]"),
no_prefix,
}
}
fn format_line(&self, line: &str) -> String {
if self.no_prefix {
line.to_string()
} else {
format!("{} {}", self.node_prefix, line)
}
}
pub fn write_stdout_lines(&self, text: &str) -> io::Result<()> {
let lines: Vec<String> = text.lines().map(|line| self.format_line(line)).collect();
if !lines.is_empty() {
let mut stdout = STDOUT_MUTEX.lock().unwrap();
for line in lines {
writeln!(stdout, "{line}")?;
}
stdout.flush()?;
}
Ok(())
}
pub fn write_stderr_lines(&self, text: &str) -> io::Result<()> {
let lines: Vec<String> = text.lines().map(|line| self.format_line(line)).collect();
if !lines.is_empty() {
let mut stderr = STDERR_MUTEX.lock().unwrap();
for line in lines {
writeln!(stderr, "{line}")?;
}
stderr.flush()?;
}
Ok(())
}
pub fn write_stdout(&self, line: &str) -> io::Result<()> {
synchronized_println(&self.format_line(line))
}
#[allow(dead_code)]
pub fn write_stderr(&self, line: &str) -> io::Result<()> {
synchronized_eprintln(&self.format_line(line))
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_node_output_writer() {
let writer = NodeOutputWriter::new("test-host");
assert_eq!(writer.node_prefix, "[test-host]");
assert!(!writer.no_prefix);
}
#[test]
fn test_node_output_writer_with_no_prefix() {
let writer = NodeOutputWriter::new_with_no_prefix("test-host", true);
assert_eq!(writer.node_prefix, "[test-host]");
assert!(writer.no_prefix);
assert_eq!(writer.format_line("test output"), "test output");
let writer_with_prefix = NodeOutputWriter::new_with_no_prefix("test-host", false);
assert_eq!(
writer_with_prefix.format_line("test output"),
"[test-host] test output"
);
}
#[test]
fn test_synchronized_output() {
let _ = synchronized_println("test");
let _ = synchronized_eprintln("test error");
let lines = ["line1", "line2"];
let _ = synchronized_print_lines(lines.iter().copied());
let _ = synchronized_eprint_lines(lines.iter().copied());
}
}