gag-1.0.0/.cargo_vcs_info.json0000644000000001120000000000000115610ustar { "git": { "sha1": "c5048b93523ff42f2983b8354a65fff6174501db" } } gag-1.0.0/.gitignore000064400000000000000000000000220000000000000123170ustar 00000000000000target Cargo.lock gag-1.0.0/.travis.yml000064400000000000000000000001130000000000000124410ustar 00000000000000language: rust rust: - nightly - beta - stable os: - linux - osx gag-1.0.0/Cargo.toml0000644000000020520000000000000075640ustar # THIS FILE IS AUTOMATICALLY GENERATED BY CARGO # # When uploading crates to the registry Cargo will automatically # "normalize" Cargo.toml files for maximal compatibility # with all versions of Cargo and also rewrite `path` dependencies # to registry (e.g., crates.io) dependencies # # If you believe there's an error in this file please file an # issue against the rust-lang/cargo repository. If you're # editing this file be aware that the upstream Cargo.toml # will likely look very different (and much more reasonable) [package] name = "gag" version = "1.0.0" authors = ["Steven Allen "] description = "Gag, redirect, or hold stdout/stderr output. Currently only *nix operating systems are supported." documentation = "https://docs.rs/gag/" keywords = ["stdout", "stderr", "stdio", "redirect"] license = "MIT" repository = "https://github.com/Stebalien/gag-rs" [dependencies.filedescriptor] version = "0.8.0" [dependencies.tempfile] version = "3.0" [dev-dependencies.dirs] version = "3.0.2" [dev-dependencies.lazy_static] version = "1" gag-1.0.0/Cargo.toml.orig000064400000000000000000000007350000000000000132310ustar 00000000000000[package] name = "gag" version = "1.0.0" authors = ["Steven Allen "] description = "Gag, redirect, or hold stdout/stderr output. Currently only *nix operating systems are supported." documentation = "https://docs.rs/gag/" repository = "https://github.com/Stebalien/gag-rs" keywords = ["stdout", "stderr", "stdio", "redirect"] license = "MIT" [dependencies] tempfile = "3.0" filedescriptor = "0.8.0" [dev-dependencies] lazy_static = "1" dirs = "3.0.2" gag-1.0.0/LICENSE000064400000000000000000000020670000000000000113470ustar 00000000000000Copyright (C) 2015 Steven Allen Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. gag-1.0.0/README.md000064400000000000000000000021750000000000000116210ustar 00000000000000Redirect and/or gag stdout/stderr. [![Build Status](https://travis-ci.org/Stebalien/gag-rs.svg?branch=master)](https://travis-ci.org/Stebalien/gag-rs) Documentation (with examples): https://docs.rs/gag/ # Limitations * Won't work if something else has called `std::io::set_print` (currently unstable). Unfortunately, this function doesn't actually redirect the stdio file descriptor, it just replaces the `std::io::stdout` writer. * Won't work in rust test cases. The rust test cases use `std::io::set_print` to redirect stdout. You can get around this though by using the `--nocapture` argument when running your tests. # TODO: * General: * Better error handling? * Redirect: * Be generic over references. That is, accept both a reference to an AsRawFd or an AsRawFd. Unfortunately, I don't know if this is even possible. Borrow doesn't work because I really want the following constraint: `impl Redirect where F: BorrowMut, T: AsMut` so I can write `file.borrow_mut().as_mut()` but that would be ambiguous... * Buffer: * Deallocate the buffer as it is read (FALLOC_FL_PUNCH_HOLE) if possible. gag-1.0.0/src/buffer.rs000064400000000000000000000025600000000000000127460ustar 00000000000000use std::fs::File; use std::io::{self, Read}; use redirect::Redirect; use tempfile::NamedTempFile; /// Buffer output in an in-memory buffer. pub struct BufferRedirect { #[allow(dead_code)] redir: Redirect, outer: File, } /// An in-memory read-only buffer into which BufferRedirect buffers output. pub struct Buffer(File); impl Read for Buffer { #[inline(always)] fn read(&mut self, buf: &mut [u8]) -> io::Result { self.0.read(buf) } } impl BufferRedirect { /// Buffer stdout. pub fn stdout() -> io::Result { let tempfile = NamedTempFile::new()?; let inner = tempfile.reopen()?; let outer = tempfile.reopen()?; let redir = Redirect::stdout(inner)?; Ok(BufferRedirect { redir, outer }) } /// Buffer stderr. pub fn stderr() -> io::Result { let tempfile = NamedTempFile::new()?; let inner = tempfile.reopen()?; let outer = tempfile.reopen()?; let redir = Redirect::stderr(inner)?; Ok(BufferRedirect { redir, outer }) } /// Extract the inner buffer and stop redirecting output. pub fn into_inner(self) -> Buffer { Buffer(self.outer) } } impl Read for BufferRedirect { #[inline(always)] fn read(&mut self, buf: &mut [u8]) -> io::Result { self.outer.read(buf) } } gag-1.0.0/src/gag.rs000064400000000000000000000012450000000000000122320ustar 00000000000000use redirect::Redirect; use std::fs::File; use std::fs::OpenOptions; use std::io; // Helper function for opening /dev/null in unix or NUL on windows fn null() -> io::Result { #[cfg(windows)] return OpenOptions::new().write(true).open("NUL"); #[cfg(unix)] return OpenOptions::new().write(true).open("/dev/null"); } /// Discard output until dropped. pub struct Gag(Redirect); impl Gag { /// Discard stdout until dropped. pub fn stdout() -> io::Result { Ok(Gag(Redirect::stdout(null()?)?)) } /// Discard stderr until dropped. pub fn stderr() -> io::Result { Ok(Gag(Redirect::stderr(null()?)?)) } } gag-1.0.0/src/hold.rs000064400000000000000000000031610000000000000124210ustar 00000000000000use std::io::{self, Read, Write}; use BufferRedirect; /// Hold output until dropped. On drop, the held output is sent to the stdout/stderr. /// /// Note: This will ignore IO errors when printing held output. pub struct Hold { buf_redir: Option, is_stdout: bool, } impl Hold { /// Hold stderr output. pub fn stderr() -> io::Result { Ok(Hold { buf_redir: Some(BufferRedirect::stderr()?), is_stdout: false, }) } /// Hold stdout output. pub fn stdout() -> io::Result { Ok(Hold { buf_redir: Some(BufferRedirect::stdout()?), is_stdout: true, }) } } impl Drop for Hold { fn drop(&mut self) { fn read_into(mut from: R, mut to: W) { // TODO: use sendfile? let mut buf = [0u8; 4096]; loop { // Ignore errors match from.read(&mut buf) { Ok(0) => break, Ok(size) => { if to.write_all(&buf[..size]).is_err() { break; } } Err(_) => break, } } // Just in case... let _ = to.flush(); } let from = self.buf_redir.take().unwrap().into_inner(); // Ignore errors. if self.is_stdout { let stdout = io::stdout(); read_into(from, stdout.lock()); } else { let stderr = io::stderr(); read_into(from, stderr.lock()); } } } gag-1.0.0/src/lib.rs000064400000000000000000000063300000000000000122420ustar 00000000000000//! Temporarily redirect stdout/stderr. //! //! For example, one can temporarily throw away stdout output: //! //! ``` //! use gag::Gag; //! println!("Hello world!"); //! { //! let print_gag = Gag::stdout().unwrap(); //! println!("No one will see this!"); //! println!("Or this!"); //! } //! println!("But they will see this!"); //! ``` //! //! You can also temporarily un-gag by dropping the gag: //! //! ``` //! use gag::Gag; //! let mut print_gag = Gag::stdout().unwrap(); //! println!("No one will see this!"); //! if true { //! drop(print_gag); //! println!("They will see this..."); //! print_gag = Gag::stdout().unwrap(); //! println!("Not this..."); //! } //! println!("Nor this."); //! ``` //! //! However, you can't open multiple Gags/Redirects/Holds for the same output at once: //! //! ``` //! use gag::Gag; //! // Multiple stdout gags //! let gag_a = Gag::stdout().unwrap(); //! let gag_b_result = Gag::stdout(); //! assert!(gag_b_result.is_err()); //! assert_eq!(gag_b_result.err().expect("Expected an error").kind(), //! std::io::ErrorKind::AlreadyExists); //! //! // However, you can still gag stderr: //! let gag_c = Gag::stderr().unwrap(); //! ``` //! //! If you don't want to throw away stdout, you can write it to a file: //! //! ``` //! # extern crate dirs; //! use std::fs::OpenOptions; //! use std::io::{Read, Write, Seek, SeekFrom}; //! use gag::Redirect; //! use dirs::data_local_dir; //! //! fn get_temp_filepath() -> String { //! #[cfg(windows)] //! return data_local_dir() //! .unwrap() //! .join("Temp") //! .join("my_log.log") //! .to_string_lossy() //! .into(); //! //! #[cfg(unix)] //! return "/tmp/my_log.log".into(); //! } //! //! println!("Displayed"); //! //! // Open a log //! let log = OpenOptions::new() //! .truncate(true) //! .read(true) //! .create(true) //! .write(true) //! .open(get_temp_filepath()) //! .unwrap(); //! //! let print_redirect = Redirect::stdout(log).unwrap(); //! println!("Hidden"); //! //! // Extract redirect //! let mut log = print_redirect.into_inner(); //! println!("Displayed"); //! //! let mut buf = String::new(); //! log.seek(SeekFrom::Start(0)).unwrap(); //! log.read_to_string(&mut buf).unwrap(); //! assert_eq!(&buf[..], "Hidden\n"); //! //! ``` //! //! Alternatively, you can buffer stdout to a temporary file. On linux 3.11+, this file is //! guarenteed to be stored in-memory. //! //! ``` //! use std::io::Read; //! use gag::BufferRedirect; //! //! let mut buf = BufferRedirect::stdout().unwrap(); //! println!("Hello world!"); //! //! let mut output = String::new(); //! buf.read_to_string(&mut output).unwrap(); //! //! assert_eq!(&output[..], "Hello world!\n"); //! ``` //! //! Finally, if you just want to temporarily hold std output, you can use `Hold` to hold the output //! until dropped: //! //! ``` //! use gag::Hold; //! //! let hold = Hold::stdout().unwrap(); //! println!("first"); //! println!("second"); //! drop(hold); // printing happens here! //! ``` extern crate filedescriptor; extern crate tempfile; mod buffer; mod gag; mod hold; mod redirect; pub use buffer::{Buffer, BufferRedirect}; pub use gag::Gag; pub use hold::Hold; pub use redirect::{Redirect, RedirectError}; gag-1.0.0/src/redirect.rs000064400000000000000000000055710000000000000133030ustar 00000000000000use std::any::Any; use std::io; use std::sync::atomic::{AtomicBool, Ordering}; use filedescriptor::{AsRawFileDescriptor, FileDescriptor, StdioDescriptor}; static REDIRECT_FLAGS: [AtomicBool; 3] = [ AtomicBool::new(false), AtomicBool::new(false), AtomicBool::new(false), ]; pub struct RedirectError { pub error: io::Error, pub file: F, } impl From> for io::Error { fn from(err: RedirectError) -> io::Error { err.error } } impl ::std::error::Error for RedirectError { fn description(&self) -> &str { self.error.description() } fn cause(&self) -> Option<&::std::error::Error> { Some(&self.error) } } impl ::std::fmt::Display for RedirectError { fn fmt(&self, fmt: &mut ::std::fmt::Formatter) -> Result<(), ::std::fmt::Error> { self.error.fmt(fmt) } } impl ::std::fmt::Debug for RedirectError { fn fmt(&self, fmt: &mut ::std::fmt::Formatter) -> Result<(), ::std::fmt::Error> { self.error.fmt(fmt) } } /// Redirect stderr/stdout to a file. pub struct Redirect { #[allow(dead_code)] fds: RedirectFds, file: F, } // Separate struct so we can destruct the redirect and drop the file descriptors. struct RedirectFds { std_fd: FileDescriptor, stdio: StdioDescriptor, } impl RedirectFds { fn make(file: &F, stdio: StdioDescriptor) -> io::Result { if REDIRECT_FLAGS[stdio as usize].fetch_or(true, Ordering::Relaxed) { return Err(io::Error::new( io::ErrorKind::AlreadyExists, "Redirect already exists.", )); } let std_fd = FileDescriptor::redirect_stdio(file, stdio) .map_err(|error| io::Error::new(io::ErrorKind::Other, error.to_string()))?; // Dropping this will redirect stdio back to original std_fd Ok(RedirectFds { std_fd, stdio }) } } impl Drop for RedirectFds { fn drop(&mut self) { let _ = FileDescriptor::redirect_stdio(&self.std_fd, self.stdio); REDIRECT_FLAGS[self.stdio as usize].store(false, Ordering::Relaxed); } } impl Redirect where F: AsRawFileDescriptor, { fn make(file: F, stdio: StdioDescriptor) -> Result> { let fds = match RedirectFds::make(&file, stdio) { Ok(fds) => fds, Err(error) => return Err(RedirectError { error, file }), }; Ok(Redirect { fds, file }) } /// Redirect stdout to `file`. pub fn stdout(file: F) -> Result> { Redirect::make(file, StdioDescriptor::Stdout) } /// Redirect stderr to `file`. pub fn stderr(file: F) -> Result> { Redirect::make(file, StdioDescriptor::Stderr) } /// Extract inner file object. pub fn into_inner(self) -> F { self.file } } gag-1.0.0/tests/test_redirect.rs000064400000000000000000000014740000000000000147130ustar 00000000000000extern crate gag; #[macro_use] extern crate lazy_static; use gag::{BufferRedirect, Hold}; use std::io::{Read, Write}; use std::sync::Mutex; lazy_static! { static ref STDERR_MUTEX: Mutex<()> = Mutex::new(()); } // Catch the cases not covered by the doc tests. #[test] fn test_buffer_stderr() { let _l = STDERR_MUTEX.lock().unwrap(); let mut buf = BufferRedirect::stderr().unwrap(); println!("Don't capture"); ::std::io::stderr().write_all(b"Hello world!\n").unwrap(); let mut output = String::new(); buf.read_to_string(&mut output).unwrap(); assert_eq!(&output[..], "Hello world!\n"); } #[test] fn test_gag_stderr_twice() { let _l = STDERR_MUTEX.lock().unwrap(); let hold = Hold::stderr(); let hold2 = Hold::stderr(); assert!(hold.is_ok()); assert!(hold2.is_err()); }