From 1e800c91e8c1c91a8c0f9bbc382d644085509e6a Mon Sep 17 00:00:00 2001 From: Neculai Balaban Date: Wed, 10 Mar 2021 19:43:31 +0200 Subject: [PATCH 1/6] blanket implementation for unix --- examples/trait.rs | 25 +++++++++++++++++++++++++ src/lib.rs | 29 +++++++++++++++++++++++++++-- 2 files changed, 52 insertions(+), 2 deletions(-) create mode 100644 examples/trait.rs diff --git a/examples/trait.rs b/examples/trait.rs new file mode 100644 index 0000000..3609f67 --- /dev/null +++ b/examples/trait.rs @@ -0,0 +1,25 @@ +extern crate atty; + +use atty::IsATTY; + +fn main() { + let stdin = std::io::stdin(); + let stdout = std::io::stdout(); + let stderr = std::io::stderr(); + let file = std::fs::File::open("LICENSE").unwrap(); + #[cfg(unix)] + let tcp = std::net::TcpStream::connect("1.1.1.1:80").unwrap(); + + println!("{:<25} - {}", "std::io::Stdin", stdin.isatty()); + println!("{:<25} - {}", "std::io::Stdout", stdout.isatty()); + println!("{:<25} - {}", "std::io::Stderr", stderr.isatty()); + println!("{:<25} - {}", "std::fs::File", file.isatty()); + #[cfg(unix)] + println!("{:<25} - {}", "std::net::TcpStream", tcp.isatty()); + + println!("\ndyn trait Example:"); + let all : Vec> = vec![Box::new(stderr), Box::new(file)]; + for (i, b) in all.into_iter().enumerate() { + println!("element #{} : {}", i, b.isatty()); + } +} \ No newline at end of file diff --git a/src/lib.rs b/src/lib.rs index 501cad6..b606c83 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -15,8 +15,6 @@ //! } //! ``` -#![cfg_attr(unix, no_std)] - #[cfg(unix)] extern crate libc; #[cfg(windows)] @@ -35,6 +33,10 @@ pub enum Stream { Stdin, } +pub trait IsATTY { + fn isatty(&self) -> bool; +} + /// returns true if this is a tty #[cfg(all(unix, not(target_arch = "wasm32")))] pub fn is(stream: Stream) -> bool { @@ -48,6 +50,14 @@ pub fn is(stream: Stream) -> bool { unsafe { libc::isatty(fd) != 0 } } +#[cfg(all(unix, not(target_arch = "wasm32")))] +impl IsATTY for T { + fn isatty(&self) -> bool { + extern crate libc; + unsafe { libc::isatty(self.as_raw_fd()) != 0} + } +} + /// returns true if this is a tty #[cfg(target_os = "hermit")] pub fn is(stream: Stream) -> bool { @@ -61,6 +71,14 @@ pub fn is(stream: Stream) -> bool { hermit_abi::isatty(fd) } +#[cfg(target_os = "hermit")] +impl IsATTY for T { + fn isatty(&self) -> bool { + extern crate hermit_abi; + hermit_abi::isatty(self.as_raw_fd()) + } +} + /// returns true if this is a tty #[cfg(windows)] pub fn is(stream: Stream) -> bool { @@ -159,6 +177,13 @@ pub fn is(_stream: Stream) -> bool { false } +#[cfg(target_arch = "wasm32")] +impl IsATTY for T { + fn isatty(&self) -> bool { + false + } +} + #[cfg(test)] mod tests { use super::{is, Stream}; From 32be64e886e08776b893ece248d5ff318dbf4cac Mon Sep 17 00:00:00 2001 From: Neculai Balaban Date: Wed, 17 Mar 2021 11:22:59 +0200 Subject: [PATCH 2/6] partial windows trait implementation --- src/lib.rs | 74 +++++++++++++++++++++++++++++++++++------------------- 1 file changed, 48 insertions(+), 26 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index b606c83..416f162 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -21,9 +21,12 @@ extern crate libc; extern crate winapi; #[cfg(windows)] -use winapi::shared::minwindef::DWORD; +use winapi::shared::ntdef::{WCHAR, HANDLE}; #[cfg(windows)] -use winapi::shared::ntdef::WCHAR; +use winapi::um::winbase::{ + STD_ERROR_HANDLE as STD_ERROR, STD_INPUT_HANDLE as STD_INPUT, + STD_OUTPUT_HANDLE as STD_OUTPUT, +}; /// possible stream sources #[derive(Clone, Copy, Debug)] @@ -82,17 +85,37 @@ impl IsATTY for T { /// returns true if this is a tty #[cfg(windows)] pub fn is(stream: Stream) -> bool { - use winapi::um::winbase::{ - STD_ERROR_HANDLE as STD_ERROR, STD_INPUT_HANDLE as STD_INPUT, - STD_OUTPUT_HANDLE as STD_OUTPUT, + use winapi::um::processenv::GetStdHandle; + let stdin = unsafe {GetStdHandle(STD_INPUT) as HANDLE}; + let stdout = unsafe {GetStdHandle(STD_OUTPUT) as HANDLE}; + let stderr = unsafe {GetStdHandle(STD_ERROR) as HANDLE}; + let (handle, others) = match stream { + Stream::Stdin => (stdin, [stderr, stdout]), + Stream::Stderr => (stderr, [stdin, stdout]), + Stream::Stdout => (stdout, [stdin, stderr]), }; + + is_handle_a_tty(&handle, &others) +} - let (fd, others) = match stream { - Stream::Stdin => (STD_INPUT, [STD_ERROR, STD_OUTPUT]), - Stream::Stderr => (STD_ERROR, [STD_INPUT, STD_OUTPUT]), - Stream::Stdout => (STD_OUTPUT, [STD_INPUT, STD_ERROR]), - }; - if unsafe { console_on_any(&[fd]) } { +#[cfg(windows)] +impl IsATTY for T { + fn isatty(&self) -> bool { + use winapi::um::processenv::GetStdHandle; + let handle = self.as_raw_handle() as HANDLE; + let stdin = unsafe {GetStdHandle(STD_INPUT) as HANDLE}; + let stdout = unsafe {GetStdHandle(STD_OUTPUT) as HANDLE}; + let stderr = unsafe {GetStdHandle(STD_ERROR) as HANDLE}; + let others = [stdin, stdout, stderr]; + + is_handle_a_tty(&handle, &others) + } +} + +/// returns true if this is a tty +#[cfg(windows)] +pub fn is_handle_a_tty(handle: &HANDLE, others: &[HANDLE]) -> bool { + if unsafe { console_on(handle) } { // False positives aren't possible. If we got a console then // we definitely have a tty on stdin. return true; @@ -102,13 +125,15 @@ pub fn is(stream: Stream) -> bool { // this is true negative if we can detect the presence of a console on // any of the other streams. If another stream has a console, then we know // we're in a Windows console and can therefore trust the negative. - if unsafe { console_on_any(&others) } { - return false; + for h in others { + if unsafe { console_on(h) } { + return false; + } } // Otherwise, we fall back to a very strange msys hack to see if we can // sneakily detect the presence of a tty. - unsafe { msys_tty_on(fd) } + unsafe { msys_tty_on(handle) } } /// returns true if this is _not_ a tty @@ -118,29 +143,26 @@ pub fn isnt(stream: Stream) -> bool { /// Returns true if any of the given fds are on a console. #[cfg(windows)] -unsafe fn console_on_any(fds: &[DWORD]) -> bool { - use winapi::um::{consoleapi::GetConsoleMode, processenv::GetStdHandle}; - - for &fd in fds { - let mut out = 0; - let handle = GetStdHandle(fd); - if GetConsoleMode(handle, &mut out) != 0 { - return true; - } +unsafe fn console_on(handle: &HANDLE) -> bool { + use winapi::um::{consoleapi::GetConsoleMode}; + + let mut out = 0; + if GetConsoleMode(*handle, &mut out) != 0 { + return true; } false } /// Returns true if there is an MSYS tty on the given handle. #[cfg(windows)] -unsafe fn msys_tty_on(fd: DWORD) -> bool { +unsafe fn msys_tty_on(handle: &HANDLE) -> bool { use std::{mem, slice}; use winapi::{ ctypes::c_void, shared::minwindef::MAX_PATH, um::{ - fileapi::FILE_NAME_INFO, minwinbase::FileNameInfo, processenv::GetStdHandle, + fileapi::FILE_NAME_INFO, minwinbase::FileNameInfo, winbase::GetFileInformationByHandleEx, }, }; @@ -148,7 +170,7 @@ unsafe fn msys_tty_on(fd: DWORD) -> bool { let size = mem::size_of::(); let mut name_info_bytes = vec![0u8; size + MAX_PATH * mem::size_of::()]; let res = GetFileInformationByHandleEx( - GetStdHandle(fd), + *handle, FileNameInfo, &mut *name_info_bytes as *mut _ as *mut c_void, name_info_bytes.len() as u32, From 0d0a1310db730bfef22b5e6431580a7e2529c089 Mon Sep 17 00:00:00 2001 From: Neculai Balaban Date: Wed, 17 Mar 2021 13:26:48 +0200 Subject: [PATCH 3/6] handled trait implementation for SOCKET --- examples/trait.rs | 23 ++++++++++++++++------- src/lib.rs | 16 +++++++++++----- 2 files changed, 27 insertions(+), 12 deletions(-) diff --git a/examples/trait.rs b/examples/trait.rs index 3609f67..6101ff7 100644 --- a/examples/trait.rs +++ b/examples/trait.rs @@ -7,18 +7,27 @@ fn main() { let stdout = std::io::stdout(); let stderr = std::io::stderr(); let file = std::fs::File::open("LICENSE").unwrap(); - #[cfg(unix)] let tcp = std::net::TcpStream::connect("1.1.1.1:80").unwrap(); - println!("{:<25} - {}", "std::io::Stdin", stdin.isatty()); - println!("{:<25} - {}", "std::io::Stdout", stdout.isatty()); - println!("{:<25} - {}", "std::io::Stderr", stderr.isatty()); - println!("{:<25} - {}", "std::fs::File", file.isatty()); - #[cfg(unix)] + println!("{:<25} - {}", "std::io::Stdin", stdin.isatty()); + println!("{:<25} - {}", "std::io::Stdout", stdout.isatty()); + println!("{:<25} - {}", "std::io::Stderr", stderr.isatty()); + println!("{:<25} - {}", "std::fs::File", file.isatty()); println!("{:<25} - {}", "std::net::TcpStream", tcp.isatty()); println!("\ndyn trait Example:"); - let all : Vec> = vec![Box::new(stderr), Box::new(file)]; + let all : Vec>> = vec![ + Box::new(stdin), + Box::new(stdout), + Box::new(stderr), + Box::new(file), + // The following is not possible on windows because, as the IsATTY trait was + // implemented, the boxed type signatures differ: + // Box> vs Box> + #[cfg(unix)] + Box::new(tcp) + ]; + for (i, b) in all.into_iter().enumerate() { println!("element #{} : {}", i, b.isatty()); } diff --git a/src/lib.rs b/src/lib.rs index 416f162..a0298d6 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -36,7 +36,7 @@ pub enum Stream { Stdin, } -pub trait IsATTY { +pub trait IsATTY { fn isatty(&self) -> bool; } @@ -54,7 +54,7 @@ pub fn is(stream: Stream) -> bool { } #[cfg(all(unix, not(target_arch = "wasm32")))] -impl IsATTY for T { +impl IsATTY for T { fn isatty(&self) -> bool { extern crate libc; unsafe { libc::isatty(self.as_raw_fd()) != 0} @@ -75,7 +75,7 @@ pub fn is(stream: Stream) -> bool { } #[cfg(target_os = "hermit")] -impl IsATTY for T { +impl IsATTY for T { fn isatty(&self) -> bool { extern crate hermit_abi; hermit_abi::isatty(self.as_raw_fd()) @@ -99,7 +99,7 @@ pub fn is(stream: Stream) -> bool { } #[cfg(windows)] -impl IsATTY for T { +impl IsATTY for T { fn isatty(&self) -> bool { use winapi::um::processenv::GetStdHandle; let handle = self.as_raw_handle() as HANDLE; @@ -112,7 +112,13 @@ impl IsATTY for T { } } -/// returns true if this is a tty +#[cfg(windows)] +impl IsATTY for T { + fn isatty(&self) -> bool { + false + } +} + #[cfg(windows)] pub fn is_handle_a_tty(handle: &HANDLE, others: &[HANDLE]) -> bool { if unsafe { console_on(handle) } { From 8cdd5d86b60de462204553fd252811cc821d0922 Mon Sep 17 00:00:00 2001 From: Neculai Balaban Date: Wed, 17 Mar 2021 13:46:11 +0200 Subject: [PATCH 4/6] updated tests --- src/lib.rs | 24 ++++++++++++++++-------- 1 file changed, 16 insertions(+), 8 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index a0298d6..93d5816 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -37,6 +37,7 @@ pub enum Stream { } pub trait IsATTY { + /// test whether this structure refers to a terminal fn isatty(&self) -> bool; } @@ -214,50 +215,57 @@ impl IsATTY for T { #[cfg(test)] mod tests { - use super::{is, Stream}; + use super::{is, Stream, IsATTY}; #[test] #[cfg(windows)] fn is_err() { // appveyor pipes its output - assert!(!is(Stream::Stderr)) + assert!(!is(Stream::Stderr)); + assert!(!std::io::stderr().isatty()); } #[test] #[cfg(windows)] fn is_out() { // appveyor pipes its output - assert!(!is(Stream::Stdout)) + assert!(!is(Stream::Stdout)); + assert!(!std::io::stdout().isatty()); } #[test] #[cfg(windows)] fn is_in() { - assert!(is(Stream::Stdin)) + assert!(is(Stream::Stdin)); + assert!(std::io::stdin().isatty()); } #[test] #[cfg(unix)] fn is_err() { - assert!(is(Stream::Stderr)) + assert!(is(Stream::Stderr)); + assert!(std::io::stderr().isatty()); } #[test] #[cfg(unix)] fn is_out() { - assert!(is(Stream::Stdout)) + assert!(is(Stream::Stdout)); + assert!(std::io::stdout().isatty()); } #[test] #[cfg(target_os = "macos")] fn is_in() { // macos on travis seems to pipe its input - assert!(is(Stream::Stdin)) + assert!(is(Stream::Stdin)); + assert!(std::io::stdin().isatty()); } #[test] #[cfg(all(not(target_os = "macos"), unix))] fn is_in() { - assert!(is(Stream::Stdin)) + assert!(is(Stream::Stdin)); + assert!(std::io::stdin().isatty()); } } From 77101e21d3bb9fc46345eae4f7115ddf94b54f49 Mon Sep 17 00:00:00 2001 From: Neculai Balaban Date: Wed, 17 Mar 2021 14:01:20 +0200 Subject: [PATCH 5/6] removed unnecessary casts --- src/lib.rs | 21 ++++++++++----------- 1 file changed, 10 insertions(+), 11 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 93d5816..14b6b84 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -21,12 +21,11 @@ extern crate libc; extern crate winapi; #[cfg(windows)] -use winapi::shared::ntdef::{WCHAR, HANDLE}; +use winapi::shared::ntdef::WCHAR; #[cfg(windows)] -use winapi::um::winbase::{ - STD_ERROR_HANDLE as STD_ERROR, STD_INPUT_HANDLE as STD_INPUT, - STD_OUTPUT_HANDLE as STD_OUTPUT, -}; +use winapi::um::winnt::HANDLE; +#[cfg(windows)] +use winapi::um::winbase::{STD_ERROR_HANDLE,STD_INPUT_HANDLE,STD_OUTPUT_HANDLE}; /// possible stream sources #[derive(Clone, Copy, Debug)] @@ -87,9 +86,9 @@ impl IsATTY for T { #[cfg(windows)] pub fn is(stream: Stream) -> bool { use winapi::um::processenv::GetStdHandle; - let stdin = unsafe {GetStdHandle(STD_INPUT) as HANDLE}; - let stdout = unsafe {GetStdHandle(STD_OUTPUT) as HANDLE}; - let stderr = unsafe {GetStdHandle(STD_ERROR) as HANDLE}; + let stdin = unsafe {GetStdHandle(STD_INPUT_HANDLE)}; + let stdout = unsafe {GetStdHandle(STD_OUTPUT_HANDLE)}; + let stderr = unsafe {GetStdHandle(STD_ERROR_HANDLE)}; let (handle, others) = match stream { Stream::Stdin => (stdin, [stderr, stdout]), Stream::Stderr => (stderr, [stdin, stdout]), @@ -104,9 +103,9 @@ impl IsATTY bool { use winapi::um::processenv::GetStdHandle; let handle = self.as_raw_handle() as HANDLE; - let stdin = unsafe {GetStdHandle(STD_INPUT) as HANDLE}; - let stdout = unsafe {GetStdHandle(STD_OUTPUT) as HANDLE}; - let stderr = unsafe {GetStdHandle(STD_ERROR) as HANDLE}; + let stdin = unsafe {GetStdHandle(STD_INPUT_HANDLE)}; + let stdout = unsafe {GetStdHandle(STD_OUTPUT_HANDLE)}; + let stderr = unsafe {GetStdHandle(STD_ERROR_HANDLE)}; let others = [stdin, stdout, stderr]; is_handle_a_tty(&handle, &others) From 4805b9fb9f4d6114d463aec361dfc4350a0c99ce Mon Sep 17 00:00:00 2001 From: Neculai Balaban Date: Wed, 17 Mar 2021 14:05:05 +0200 Subject: [PATCH 6/6] cargo fmt --- examples/trait.rs | 22 +++++++++++----------- src/lib.rs | 34 +++++++++++++++++----------------- 2 files changed, 28 insertions(+), 28 deletions(-) diff --git a/examples/trait.rs b/examples/trait.rs index 6101ff7..ab79370 100644 --- a/examples/trait.rs +++ b/examples/trait.rs @@ -3,20 +3,20 @@ extern crate atty; use atty::IsATTY; fn main() { - let stdin = std::io::stdin(); + let stdin = std::io::stdin(); let stdout = std::io::stdout(); let stderr = std::io::stderr(); - let file = std::fs::File::open("LICENSE").unwrap(); - let tcp = std::net::TcpStream::connect("1.1.1.1:80").unwrap(); + let file = std::fs::File::open("LICENSE").unwrap(); + let tcp = std::net::TcpStream::connect("1.1.1.1:80").unwrap(); - println!("{:<25} - {}", "std::io::Stdin", stdin.isatty()); - println!("{:<25} - {}", "std::io::Stdout", stdout.isatty()); - println!("{:<25} - {}", "std::io::Stderr", stderr.isatty()); - println!("{:<25} - {}", "std::fs::File", file.isatty()); + println!("{:<25} - {}", "std::io::Stdin", stdin.isatty()); + println!("{:<25} - {}", "std::io::Stdout", stdout.isatty()); + println!("{:<25} - {}", "std::io::Stderr", stderr.isatty()); + println!("{:<25} - {}", "std::fs::File", file.isatty()); println!("{:<25} - {}", "std::net::TcpStream", tcp.isatty()); println!("\ndyn trait Example:"); - let all : Vec>> = vec![ + let all: Vec>> = vec![ Box::new(stdin), Box::new(stdout), Box::new(stderr), @@ -25,10 +25,10 @@ fn main() { // implemented, the boxed type signatures differ: // Box> vs Box> #[cfg(unix)] - Box::new(tcp) - ]; + Box::new(tcp), + ]; for (i, b) in all.into_iter().enumerate() { println!("element #{} : {}", i, b.isatty()); } -} \ No newline at end of file +} diff --git a/src/lib.rs b/src/lib.rs index 14b6b84..9569a32 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -23,9 +23,9 @@ extern crate winapi; #[cfg(windows)] use winapi::shared::ntdef::WCHAR; #[cfg(windows)] -use winapi::um::winnt::HANDLE; +use winapi::um::winbase::{STD_ERROR_HANDLE, STD_INPUT_HANDLE, STD_OUTPUT_HANDLE}; #[cfg(windows)] -use winapi::um::winbase::{STD_ERROR_HANDLE,STD_INPUT_HANDLE,STD_OUTPUT_HANDLE}; +use winapi::um::winnt::HANDLE; /// possible stream sources #[derive(Clone, Copy, Debug)] @@ -54,10 +54,10 @@ pub fn is(stream: Stream) -> bool { } #[cfg(all(unix, not(target_arch = "wasm32")))] -impl IsATTY for T { +impl IsATTY for T { fn isatty(&self) -> bool { extern crate libc; - unsafe { libc::isatty(self.as_raw_fd()) != 0} + unsafe { libc::isatty(self.as_raw_fd()) != 0 } } } @@ -75,7 +75,7 @@ pub fn is(stream: Stream) -> bool { } #[cfg(target_os = "hermit")] -impl IsATTY for T { +impl IsATTY for T { fn isatty(&self) -> bool { extern crate hermit_abi; hermit_abi::isatty(self.as_raw_fd()) @@ -86,26 +86,26 @@ impl IsATTY for T { #[cfg(windows)] pub fn is(stream: Stream) -> bool { use winapi::um::processenv::GetStdHandle; - let stdin = unsafe {GetStdHandle(STD_INPUT_HANDLE)}; - let stdout = unsafe {GetStdHandle(STD_OUTPUT_HANDLE)}; - let stderr = unsafe {GetStdHandle(STD_ERROR_HANDLE)}; + let stdin = unsafe { GetStdHandle(STD_INPUT_HANDLE) }; + let stdout = unsafe { GetStdHandle(STD_OUTPUT_HANDLE) }; + let stderr = unsafe { GetStdHandle(STD_ERROR_HANDLE) }; let (handle, others) = match stream { Stream::Stdin => (stdin, [stderr, stdout]), Stream::Stderr => (stderr, [stdin, stdout]), Stream::Stdout => (stdout, [stdin, stderr]), }; - + is_handle_a_tty(&handle, &others) } #[cfg(windows)] -impl IsATTY for T { +impl IsATTY for T { fn isatty(&self) -> bool { use winapi::um::processenv::GetStdHandle; let handle = self.as_raw_handle() as HANDLE; - let stdin = unsafe {GetStdHandle(STD_INPUT_HANDLE)}; - let stdout = unsafe {GetStdHandle(STD_OUTPUT_HANDLE)}; - let stderr = unsafe {GetStdHandle(STD_ERROR_HANDLE)}; + let stdin = unsafe { GetStdHandle(STD_INPUT_HANDLE) }; + let stdout = unsafe { GetStdHandle(STD_OUTPUT_HANDLE) }; + let stderr = unsafe { GetStdHandle(STD_ERROR_HANDLE) }; let others = [stdin, stdout, stderr]; is_handle_a_tty(&handle, &others) @@ -113,7 +113,7 @@ impl IsATTY IsATTY for T { +impl IsATTY for T { fn isatty(&self) -> bool { false } @@ -150,7 +150,7 @@ pub fn isnt(stream: Stream) -> bool { /// Returns true if any of the given fds are on a console. #[cfg(windows)] unsafe fn console_on(handle: &HANDLE) -> bool { - use winapi::um::{consoleapi::GetConsoleMode}; + use winapi::um::consoleapi::GetConsoleMode; let mut out = 0; if GetConsoleMode(*handle, &mut out) != 0 { @@ -206,7 +206,7 @@ pub fn is(_stream: Stream) -> bool { } #[cfg(target_arch = "wasm32")] -impl IsATTY for T { +impl IsATTY for T { fn isatty(&self) -> bool { false } @@ -214,7 +214,7 @@ impl IsATTY for T { #[cfg(test)] mod tests { - use super::{is, Stream, IsATTY}; + use super::{is, IsATTY, Stream}; #[test] #[cfg(windows)]