#![deny(missing_docs)]
#![cfg_attr(test, deny(warnings))]
use std::fs::File;
use std::io::{self, IoSlice, IoSliceMut, Read, Write};
use std::mem::ManuallyDrop;
use std::path::Path;
use std::sync::atomic::{AtomicUsize, Ordering};
#[cfg(not(any(unix, windows)))]
compile_error!("stdio-override only supports Unix and Windows");
#[cfg_attr(unix, path = "unix.rs")]
#[cfg_attr(windows, path = "windows.rs")]
mod imp;
static OVERRIDDEN_STDIN_COUNT: AtomicUsize = AtomicUsize::new(0);
#[derive(Debug)]
pub struct StdinOverride {
original: ManuallyDrop<File>,
index: usize,
}
impl StdinOverride {
fn from_raw_inner(raw: imp::Raw, owned: bool) -> io::Result<Self> {
Ok(Self {
original: ManuallyDrop::new(imp::override_stdin(raw, owned)?),
index: OVERRIDDEN_STDIN_COUNT.fetch_add(1, Ordering::SeqCst),
})
}
pub fn from_raw(raw: imp::Raw) -> io::Result<Self> {
Self::from_raw_inner(raw, false)
}
pub fn from_raw_owned(raw: imp::Raw) -> io::Result<Self> {
Self::from_raw_inner(raw, true)
}
pub fn from_io_ref<T: imp::AsRaw>(io: &T) -> io::Result<Self> {
Self::from_raw(imp::as_raw(io))
}
pub fn from_io<T: imp::IntoRaw>(io: T) -> io::Result<Self> {
Self::from_raw_owned(imp::into_raw(io))
}
pub fn from_file<P: AsRef<Path>>(path: P) -> io::Result<Self> {
Self::from_io(File::open(path)?)
}
pub fn reset(self) -> io::Result<()> {
self.reset_inner()?;
std::mem::forget(self);
Ok(())
}
fn reset_inner(&self) -> io::Result<()> {
if OVERRIDDEN_STDIN_COUNT.swap(self.index, Ordering::SeqCst) <= self.index {
panic!("Stdin override reset out of order!");
}
imp::reset_stdin(imp::as_raw(&*self.original))
}
}
impl Read for StdinOverride {
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
self.original.read(buf)
}
fn read_vectored(&mut self, bufs: &mut [IoSliceMut]) -> io::Result<usize> {
self.original.read_vectored(bufs)
}
}
impl Read for &'_ StdinOverride {
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
(&*self.original).read(buf)
}
fn read_vectored(&mut self, bufs: &mut [IoSliceMut]) -> io::Result<usize> {
(&*self.original).read_vectored(bufs)
}
}
impl Drop for StdinOverride {
fn drop(&mut self) {
let _ = self.reset_inner();
}
}
static OVERRIDDEN_STDOUT_COUNT: AtomicUsize = AtomicUsize::new(0);
#[derive(Debug)]
pub struct StdoutOverride {
original: ManuallyDrop<File>,
index: usize,
}
impl StdoutOverride {
fn from_raw_inner(raw: imp::Raw, owned: bool) -> io::Result<Self> {
Ok(Self {
original: ManuallyDrop::new(imp::override_stdout(raw, owned)?),
index: OVERRIDDEN_STDOUT_COUNT.fetch_add(1, Ordering::SeqCst),
})
}
pub fn from_raw(raw: imp::Raw) -> io::Result<Self> {
Self::from_raw_inner(raw, false)
}
pub fn from_raw_owned(raw: imp::Raw) -> io::Result<Self> {
Self::from_raw_inner(raw, true)
}
pub fn from_io_ref<T: imp::AsRaw>(io: &T) -> io::Result<Self> {
Self::from_raw(imp::as_raw(io))
}
pub fn from_io<T: imp::IntoRaw>(io: T) -> io::Result<Self> {
Self::from_raw_owned(imp::into_raw(io))
}
pub fn from_file<P: AsRef<Path>>(path: P) -> io::Result<Self> {
Self::from_io(File::create(path)?)
}
pub fn reset(self) -> io::Result<()> {
self.reset_inner()?;
std::mem::forget(self);
Ok(())
}
fn reset_inner(&self) -> io::Result<()> {
if OVERRIDDEN_STDOUT_COUNT.swap(self.index, Ordering::SeqCst) <= self.index {
panic!("Stdout override reset out of order!");
}
imp::reset_stdout(imp::as_raw(&*self.original))
}
}
impl Write for StdoutOverride {
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
self.original.write(buf)
}
fn write_vectored(&mut self, bufs: &[IoSlice]) -> io::Result<usize> {
self.original.write_vectored(bufs)
}
fn flush(&mut self) -> io::Result<()> {
self.original.flush()
}
}
impl Write for &'_ StdoutOverride {
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
(&*self.original).write(buf)
}
fn write_vectored(&mut self, bufs: &[IoSlice]) -> io::Result<usize> {
(&*self.original).write_vectored(bufs)
}
fn flush(&mut self) -> io::Result<()> {
(&*self.original).flush()
}
}
impl Drop for StdoutOverride {
fn drop(&mut self) {
let _ = self.reset_inner();
}
}
static OVERRIDDEN_STDERR_COUNT: AtomicUsize = AtomicUsize::new(0);
#[derive(Debug)]
pub struct StderrOverride {
original: ManuallyDrop<File>,
index: usize,
}
impl StderrOverride {
fn from_raw_inner(raw: imp::Raw, owned: bool) -> io::Result<Self> {
Ok(Self {
original: ManuallyDrop::new(imp::override_stderr(raw, owned)?),
index: OVERRIDDEN_STDERR_COUNT.fetch_add(1, Ordering::SeqCst),
})
}
pub fn from_raw(raw: imp::Raw) -> io::Result<Self> {
Self::from_raw_inner(raw, false)
}
pub fn from_raw_owned(raw: imp::Raw) -> io::Result<Self> {
Self::from_raw_inner(raw, true)
}
pub fn from_io_ref<T: imp::AsRaw>(io: &T) -> io::Result<Self> {
Self::from_raw(imp::as_raw(io))
}
pub fn from_io<T: imp::IntoRaw>(io: T) -> io::Result<Self> {
Self::from_raw_owned(imp::into_raw(io))
}
pub fn from_file<P: AsRef<Path>>(path: P) -> io::Result<Self> {
Self::from_io(File::create(path)?)
}
pub fn reset(self) -> io::Result<()> {
self.reset_inner()?;
std::mem::forget(self);
Ok(())
}
fn reset_inner(&self) -> io::Result<()> {
if OVERRIDDEN_STDERR_COUNT.swap(self.index, Ordering::SeqCst) <= self.index {
panic!("Stderr override reset out of order!");
}
imp::reset_stderr(imp::as_raw(&*self.original))
}
}
impl Write for StderrOverride {
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
self.original.write(buf)
}
fn write_vectored(&mut self, bufs: &[IoSlice]) -> io::Result<usize> {
self.original.write_vectored(bufs)
}
fn flush(&mut self) -> io::Result<()> {
self.original.flush()
}
}
impl Write for &'_ StderrOverride {
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
(&*self.original).write(buf)
}
fn write_vectored(&mut self, bufs: &[IoSlice]) -> io::Result<usize> {
(&*self.original).write_vectored(bufs)
}
fn flush(&mut self) -> io::Result<()> {
(&*self.original).flush()
}
}
impl Drop for StderrOverride {
fn drop(&mut self) {
let _ = self.reset_inner();
}
}
#[cfg(feature = "test-readme")]
doc_comment::doctest!("../README.md");
#[cfg(test)]
mod test {
use crate::*;
use std::io::{stderr, stdin, stdout, Read, Result, Write};
use os_pipe::pipe;
#[test]
fn test_stdout() -> Result<()> {
let (mut rx, tx) = pipe()?;
let data = "12345";
let guard = StdoutOverride::from_io_ref(&tx)?;
print!("{}", data);
stdout().flush()?;
writeln!(&guard, "Outside! (1/2)")?;
drop(guard);
drop(tx);
let mut contents = String::new();
rx.read_to_string(&mut contents)?;
assert_eq!(data, contents);
println!("Outside! (2/2)");
Ok(())
}
#[test]
fn test_stderr() -> Result<()> {
let (mut rx, tx) = pipe()?;
let data = "123456";
let guard = StderrOverride::from_io_ref(&tx)?;
eprint!("{}", data);
stderr().flush()?;
writeln!(&guard, "Outside! (1/2)")?;
drop(guard);
drop(tx);
let mut contents = String::new();
rx.read_to_string(&mut contents)?;
assert_eq!(data, contents);
eprintln!("Outside! (2/2)");
Ok(())
}
#[test]
fn test_stdin() -> Result<()> {
let (rx, mut tx) = pipe()?;
let data = "12345\n";
write!(&tx, "{}", data)?;
tx.flush()?;
let guard = StdinOverride::from_io(rx)?;
print!("Please enter some text: ");
stdout().flush()?;
let mut s = String::new();
stdin().read_line(&mut s)?;
drop(guard);
assert_eq!(data, s);
println!("You typed: {}", s);
Ok(())
}
fn null() -> Result<File> {
File::create(if cfg!(windows) {
"nul"
} else if cfg!(target_os = "redox") {
"null:"
} else {
"/dev/null"
})
}
#[test]
fn test_multiple() -> Result<()> {
let null = null()?;
let guard_1 = StdoutOverride::from_io_ref(&null)?;
let guard_2 = StdoutOverride::from_io_ref(&null)?;
std::mem::forget(guard_2);
drop(guard_1);
Ok(())
}
#[test]
fn test_multiple_panic() -> Result<()> {
let null = null()?;
let guard_0 = StdoutOverride::from_io_ref(&null)?;
let guard_1 = StdoutOverride::from_io_ref(&null)?;
let guard_2 = StdoutOverride::from_io_ref(&null)?;
drop(guard_1);
let old_hook = std::panic::take_hook();
std::panic::set_hook(Box::new(|info| {
let payload: &'static str = info.payload().downcast_ref::<&'static str>().unwrap();
assert_eq!(payload, "Stdout override reset out of order!");
}));
assert!(std::panic::catch_unwind(|| drop(guard_2)).is_err());
std::panic::set_hook(old_hook);
drop(guard_0);
Ok(())
}
}