stdio_utils/
lib.rs

1#[cfg_attr(unix, path = "sys/unix.rs")]
2#[cfg_attr(windows, path = "sys/windows.rs")]
3mod sys;
4
5use std::fs::File;
6use std::io::Result;
7
8use crate::sys::{borrow_fd, override_stdio, AsFd, BorrowedFd, OwnedFd, DEV_NULL};
9
10#[derive(Clone, Copy)]
11enum Stdio {
12    Stdin,
13    Stdout,
14    Stderr,
15}
16
17pub trait StdioOverride: AsFd {
18    /// Replace the process standard output with a duplicate of the file descriptor.
19    ///
20    /// ```rust
21    /// # use stdio_utils::StdioOverride as _;
22    /// # use std::fs::{File, read_to_string};
23    /// # use std::io::stdout;
24    /// # let mut lock = stdout().lock();
25    /// let _guard = File::create("./output.txt")?.override_stdout()?;
26    /// println!("hello world!");
27    /// let output = read_to_string("./output.txt")?;
28    /// assert_eq!(output, "hello world!\n");
29    /// # std::io::Result::Ok(())
30    /// ```
31    fn override_stdout(&self) -> Result<Guard> {
32        let stdio = Stdio::Stdout;
33        let backup = unsafe { override_stdio(self, stdio) }?;
34        let backup = Some(backup);
35        Ok(Guard { backup, stdio })
36    }
37
38    /// Replace the process standard error with a duplicate of the file descriptor.
39    ///
40    /// See [duplicate_to_stdout](StdioOverride::override_stdout).
41    fn override_stderr(&self) -> Result<Guard> {
42        let stdio = Stdio::Stderr;
43        let backup = unsafe { override_stdio(self, stdio) }?;
44        let backup = Some(backup);
45        Ok(Guard { backup, stdio })
46    }
47
48    /// Replace the process standard input with a duplicate of the file descriptor.
49    ///
50    /// ```rust
51    /// # use stdio_utils::StdioOverride as _;
52    /// # use std::fs::{File, write};
53    /// # use std::io::{stdin, stdout, read_to_string};
54    /// # let mut lock = stdout().lock();
55    /// write("./input.txt", "hello world!")?;
56    /// let _guard = File::open("./input.txt")?.override_stdin()?;
57    /// let input = read_to_string(stdin())?;
58    /// assert_eq!(input, "hello world!");
59    /// # std::io::Result::Ok(())
60    /// ```
61    fn override_stdin(&self) -> Result<Guard> {
62        let stdio = Stdio::Stdin;
63        let backup = unsafe { override_stdio(self, stdio) }?;
64        let backup = Some(backup);
65        Ok(Guard { backup, stdio })
66    }
67}
68
69impl<T: AsFd> StdioOverride for T {}
70
71#[must_use]
72/// A type that restores a replaced file descriptor when it's dropped
73pub struct Guard {
74    stdio: Stdio,
75    backup: Option<OwnedFd>,
76}
77
78impl Guard {
79    /// Consume the guard without restoring the file descriptor.
80    pub fn forget(self) {
81        self.into_inner();
82    }
83
84    /// Consume the guard returning an OwnedFd with the original file descriptor
85    pub fn into_inner(mut self) -> OwnedFd {
86        self.backup.take().unwrap()
87    }
88
89    /// Obtain a BorrowFd to the original file descriptor
90    pub fn borrow_inner(&self) -> BorrowedFd<'_> {
91        borrow_fd(self.backup.as_ref().unwrap())
92    }
93}
94
95impl Drop for Guard {
96    fn drop(&mut self) {
97        if let Some(backup) = self.backup.take() {
98            let _ = unsafe { override_stdio(backup, self.stdio) };
99        }
100    }
101}
102
103/// Returns a file that can be written to.
104/// The file discards all bytes written to it.
105///
106/// ```rust
107/// # use stdio_utils::null;
108/// # use std::io::Write;
109/// let mut f = null()?;
110/// f.write_all(b"hello world")?;
111/// # std::io::Result::Ok(())
112/// ```
113///
114/// It can be used to dicard all the data written
115/// to stdout
116///
117/// ```rust
118/// # use stdio_utils::{null, StdioOverride as _};
119/// # use std::io::Write;
120/// # use std::io::stdout;
121/// # let mut lock = stdout().lock();
122/// let _guard = null()?.override_stdout();
123/// println!("hello world!"); // this will never print
124/// # std::io::Result::Ok(())
125/// ```
126pub fn null() -> Result<File> {
127    File::options()
128        .create(false)
129        .read(false)
130        .append(true)
131        .open(DEV_NULL)
132}