diff --git a/.github/actions/install-rust/README.md b/.github/actions/install-rust/README.md deleted file mode 100644 index df8e94d..0000000 --- a/.github/actions/install-rust/README.md +++ /dev/null @@ -1,18 +0,0 @@ -# install-rust - -A small github action to install `rustup` and a Rust toolchain. This is -generally expressed inline, but it was repeated enough in this repository it -seemed worthwhile to extract. - -Some gotchas: - -* Can't `--self-update` on Windows due to permission errors (a bug in Github - Actions) -* `rustup` isn't installed on macOS (a bug in Github Actions) - -When the above are fixed we should delete this action and just use this inline: - -```yml -- run: rustup update $toolchain && rustup default $toolchain - shell: bash -``` diff --git a/.github/actions/install-rust/action.yml b/.github/actions/install-rust/action.yml deleted file mode 100644 index 6afc01d..0000000 --- a/.github/actions/install-rust/action.yml +++ /dev/null @@ -1,12 +0,0 @@ -name: 'Install Rust toolchain' -description: 'Install both `rustup` and a Rust toolchain' - -inputs: - toolchain: - description: 'Default toolchan to install' - required: false - default: 'stable' - -runs: - using: node16 - main: 'main.js' diff --git a/.github/actions/install-rust/main.js b/.github/actions/install-rust/main.js deleted file mode 100644 index b144d70..0000000 --- a/.github/actions/install-rust/main.js +++ /dev/null @@ -1,36 +0,0 @@ -const child_process = require('child_process'); -const toolchain = process.env.INPUT_TOOLCHAIN; -const fs = require('fs'); - -function set_env(name, val) { - fs.appendFileSync(process.env['GITHUB_ENV'], `${name}=${val}\n`) -} - -// Needed for now to get 1.24.2 which fixes a bug in 1.24.1 that causes issues -// on Windows. -if (process.platform === 'win32') { - child_process.execFileSync('rustup', ['self', 'update']); -} - -child_process.execFileSync('rustup', ['set', 'profile', 'minimal']); -child_process.execFileSync('rustup', ['update', toolchain, '--no-self-update']); -child_process.execFileSync('rustup', ['default', toolchain]); - -// Deny warnings on CI to keep our code warning-free as it lands in-tree. Don't -// do this on nightly though since there's a fair amount of warning churn there. -if (!toolchain.startsWith('nightly')) { - set_env("RUSTFLAGS", "-D warnings"); -} - -// Save disk space by avoiding incremental compilation, and also we don't use -// any caching so incremental wouldn't help anyway. -set_env("CARGO_INCREMENTAL", "0"); - -// Turn down debuginfo from 2 to 1 to help save disk space -set_env("CARGO_PROFILE_DEV_DEBUG", "1"); -set_env("CARGO_PROFILE_TEST_DEBUG", "1"); - -if (process.platform === 'darwin') { - set_env("CARGO_PROFILE_DEV_SPLIT_DEBUGINFO", "unpacked"); - set_env("CARGO_PROFILE_TEST_SPLIT_DEBUGINFO", "unpacked"); -} diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml deleted file mode 100644 index 50852f6..0000000 --- a/.github/workflows/main.yml +++ /dev/null @@ -1,110 +0,0 @@ -name: CI - -on: - push: - branches: - - main - pull_request: - -jobs: - rustfmt: - name: Rustfmt - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v3 - with: - submodules: true - - uses: ./.github/actions/install-rust - with: - toolchain: stable - - run: cargo fmt --all -- --check - - check: - name: Check - runs-on: ${{ matrix.os }} - strategy: - matrix: - build: [msrv, stable, nightly] - include: - - build: msrv - os: ubuntu-latest - rust: 1.63 - - build: stable - os: ubuntu-latest - rust: stable - - build: nightly - os: ubuntu-latest - rust: nightly - - env: - # -D warnings is commented out in our install-rust action; re-add it here. - RUSTFLAGS: -D warnings - steps: - - uses: actions/checkout@v3 - with: - submodules: true - - uses: ./.github/actions/install-rust - with: - toolchain: ${{ matrix.rust }} - - - run: rustup target add x86_64-apple-darwin wasm32-unknown-unknown - - run: cargo check --workspace --release -vv --target=x86_64-apple-darwin - - run: cargo check --workspace --release -vv --target=wasm32-unknown-unknown - - check_nightly: - name: Check on Rust nightly - runs-on: ${{ matrix.os }} - strategy: - matrix: - build: [nightly] - include: - - build: nightly - os: ubuntu-latest - rust: nightly - - steps: - - uses: actions/checkout@v3 - with: - submodules: true - - uses: ./.github/actions/install-rust - with: - toolchain: ${{ matrix.rust }} - - run: > - rustup target add - wasm32-wasip2 - - run: cargo check --workspace --release -vv --target=wasm32-wasip2 --all-targets - - test: - name: Test - runs-on: ${{ matrix.os }} - strategy: - matrix: - build: [ubuntu-nightly, windows-nightly, ubuntu-stable, windows-stable, macos-nightly, macos-stable] - include: - - build: ubuntu-nightly - os: ubuntu-latest - rust: nightly - - build: windows-nightly - os: windows-latest - rust: nightly - - build: macos-nightly - os: macos-latest - rust: nightly - - build: ubuntu-stable - os: ubuntu-latest - rust: stable - - build: windows-stable - os: windows-latest - rust: stable - - build: macos-stable - os: macos-latest - rust: stable - - steps: - - uses: actions/checkout@v3 - with: - submodules: true - - uses: ./.github/actions/install-rust - with: - toolchain: ${{ matrix.rust }} - - run: cargo test --workspace diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..a628724 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,73 @@ +sudo: false +language: rust +matrix: + fast_finish: true + include: + - rust: nightly + - rust: nightly + os: osx + - rust: beta + - rust: beta + os: osx + - rust: stable + - rust: stable + os: osx + allow_failures: + - rust: nightly + +before_cache: + # Travis can't cache files that are not readable by "others" + - chmod -R a+r $HOME/.cargo + +before_install: + # install kcov + - > + if [ ! -d "$HOME/.kcov/bin" ]; then + wget https://github.com/SimonKagstrom/kcov/archive/master.tar.gz && + tar xzf master.tar.gz && mkdir kcov-master/build && cd kcov-master/build && + cmake -DCMAKE_INSTALL_PREFIX:PATH=$HOME/.kcov .. && make && make install && cd ../.. + fi + - export PATH=$HOME/.kcov/bin:$PATH + +script: + - cargo build + +cache: + cargo: true + apt: true + directories: + - target/debug/deps + - target/debug/build + +addons: + apt: + packages: + - libcurl4-openssl-dev + - libelf-dev + - libdw-dev + - binutils-dev + - libiberty-dev + +after_success: + - '[ $TRAVIS_RUST_VERSION = stable ] && + [ $TRAVIS_BRANCH = master ] && + [ $TRAVIS_PULL_REQUEST = false ] && + (ls target/debug && + RUSTFLAGS="-C link-dead-code" cargo test --no-run && + for file in target/debug/atty-*; do + if [[ "${file: -2}" != ".d" ]]; then + mkdir -p "target/cov/$(basename $file)"; + kcov --exclude-pattern=/.cargo,/usr/lib --verify "target/cov/$(basename $file)" "$file"; + fi; + done && + kcov --coveralls-id=$COVERALLS_REPO_TOKEN --merge target/cov target/cov/* && + echo "covered") || true' + - '[ $TRAVIS_RUST_VERSION = stable ] && + [ $TRAVIS_BRANCH = master ] && + [ $TRAVIS_PULL_REQUEST = false ] + && cargo doc --no-deps && + echo "" > target/doc/index.html && + pip install --user ghp-import && + /home/travis/.local/bin/ghp-import -n target/doc && + git push -fq https://${GH_TOKEN}@github.com/${TRAVIS_REPO_SLUG}.git gh-pages && + echo "documented"' \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..4e9673f --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,73 @@ +# 0.2.14 + +* add support for [RustyHermit](https://github.com/hermitcore/libhermit-rs), a Rust-based unikernel [#41](https://github.com/softprops/atty/pull/41) + +# 0.2.13 + +* support older versions of rust that do now support 2018 edition + +# 0.2.12 + +* Redox is now in the unix family so redox cfg is no longer needed [#35](https://github.com/softprops/atty/pull/35) + +# 0.2.11 + +* fix msys detection with `winapi@0.3.5` [#28](https://github.com/softprops/atty/pull/28) + +# 0.2.10 + +* fix wasm regression [#27](https://github.com/softprops/atty/pull/27) + +# 0.2.9 + +* Fix fix pty detection [#25](https://github.com/softprops/atty/pull/25) + +# 0.2.8 + +* Fix an inverted condition on MinGW [#22](https://github.com/softprops/atty/pull/22) + +# 0.2.7 + +* Change `||` to `&&` for whether MSYS is a tty [#24](https://github.com/softprops/atty/pull/24/) + +# 0.2.6 + +* updated winapi dependency to [0.3](https://retep998.github.io/blog/winapi-0.3/) [#18](https://github.com/softprops/atty/pull/18) + +# 0.2.5 + +* added support for Wasm compile targets [#17](https://github.com/softprops/atty/pull/17) + +# 0.2.4 + +* added support for Wasm compile targets [#17](https://github.com/softprops/atty/pull/17) + +# 0.2.3 + +* added support for Redox OS [#14](https://github.com/softprops/atty/pull/14) + +# 0.2.2 + +* use target specific dependencies [#11](https://github.com/softprops/atty/pull/11) +* Add tty detection for MSYS terminals [#12](https://github.com/softprops/atty/pull/12) + +# 0.2.1 + +* fix windows bug + +# 0.2.0 + +* support for various stream types + +# 0.1.2 + +* windows support (with automated testing) +* automated code coverage + +# 0.1.1 + +* bumped libc dep from `0.1` to `0.2` + +# 0.1.0 + +* initial release diff --git a/Cargo.toml b/Cargo.toml index ee06c7f..8073a71 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,43 +1,26 @@ [package] -name = "is-terminal" -version = "0.4.15" -authors = [ - "softprops ", - "Dan Gohman " -] -description = "Test whether a given stream is a terminal" -documentation = "https://docs.rs/is-terminal" -repository = "https://github.com/sunfishcode/is-terminal" +name = "atty" +version = "0.2.14" +authors = ["softprops "] +description = "A simple interface for querying atty" +documentation = "http://softprops.github.io/atty" +homepage = "https://github.com/softprops/atty" +repository = "https://github.com/softprops/atty" keywords = ["terminal", "tty", "isatty"] -categories = ["command-line-interface"] license = "MIT" -edition = "2018" -include = ["src", "build.rs", "Cargo.toml", "COPYRIGHT", "LICENSE*", "/*.md"] -rust-version = "1.63" +readme = "README.md" +exclude = ["/.travis.yml", "/appveyor.yml"] -[target.'cfg(any(unix, target_os = "wasi"))'.dependencies] -libc = "0.2" +[badges] +travis-ci = { repository = "softprops/atty" } + +[target.'cfg(unix)'.dependencies] +libc = { version = "0.2", default-features = false } [target.'cfg(target_os = "hermit")'.dependencies] -hermit-abi = "0.5.0" +hermit-abi = "0.1.6" [target.'cfg(windows)'.dependencies.windows-sys] -version = ">=0.52, <0.60" -features = [ - "Win32_Foundation", - "Win32_Storage_FileSystem", - "Win32_System_Console", -] - -[dev-dependencies] -atty = "0.2.14" - -[target.'cfg(any(unix, target_os = "wasi"))'.dev-dependencies] -rustix = { version = "1.0.0", features = ["termios"] } -libc = "0.2.110" - -[target.'cfg(not(any(windows, target_os = "hermit", target_os = "unknown")))'.dev-dependencies] -rustix = { version = "1.0.0", features = ["stdio"] } +version = "0.42" +features = ["Win32_System_Console", "Win32_Foundation", "Win32_Storage_FileSystem"] -[target.'cfg(windows)'.dev-dependencies] -tempfile = "3" diff --git a/LICENSE-MIT-atty b/LICENSE similarity index 90% rename from LICENSE-MIT-atty rename to LICENSE index b2319d8..b9da76b 100644 --- a/LICENSE-MIT-atty +++ b/LICENSE @@ -1,6 +1,3 @@ -Portions of this project are derived from atty, which bears the following -copyright notice and permission notice: - Copyright (c) 2015-2019 Doug Tangren Permission is hereby granted, free of charge, to any person obtaining diff --git a/LICENSE-MIT b/LICENSE-MIT deleted file mode 100644 index 31aa793..0000000 --- a/LICENSE-MIT +++ /dev/null @@ -1,23 +0,0 @@ -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. diff --git a/README.md b/README.md index 0d9b4aa..cd85593 100644 --- a/README.md +++ b/README.md @@ -1,116 +1,74 @@ -
-

is-terminal

+# atty -

- Test whether a given stream is a terminal -

+[![Build Status](https://travis-ci.org/softprops/atty.svg?branch=master)](https://travis-ci.org/softprops/atty) [![Build status](https://ci.appveyor.com/api/projects/status/geggrsnsjsuse8cv?svg=true)](https://ci.appveyor.com/project/softprops/atty) [![Coverage Status](https://coveralls.io/repos/softprops/atty/badge.svg?branch=master&service=github)](https://coveralls.io/github/softprops/atty?branch=master) [![crates.io](https://img.shields.io/crates/v/atty.svg)](https://crates.io/crates/atty) [![Released API docs](https://docs.rs/atty/badge.svg)](http://docs.rs/atty) [![Master API docs](https://img.shields.io/badge/docs-master-green.svg)](https://softprops.github.io/atty) -

- Github Actions CI Status - crates.io page - docs.rs docs -

-
+> are you or are you not a tty? -As of Rust 1.70, most users should use the [`IsTerminal`] trait in the Rust -standard library instead of this crate. -On Unix platforms, this crate now uses libc, so that the implementation -matches what's in std. Users wishing to use the rustix-based implementation -can use the [rustix-is-terminal] crate instead. +## install -[rustix-is-terminal]: https://crates.io/crates/rustix-is-terminal +Add the following to your `Cargo.toml` -
- -is-terminal is a simple utility that answers one question: - -> Is this a terminal? - -A "terminal", also known as a "tty", is an I/O device which may be interactive -and may support color and other special features. This crate doesn't provide -any of those features; it just answers this one question. - -On Unix-family platforms, this is effectively the same as the [`isatty`] -function for testing whether a given stream is a terminal, though it accepts -high-level stream types instead of raw file descriptors. - -On Windows, it uses a variety of techniques to determine whether the given -stream is a terminal. - -This crate is derived from [the atty crate] with [PR \#51] bug fix and -[PR \#54] port to windows-sys applied. The only additional difference is that -the atty crate only accepts stdin, stdout, or stderr, while this crate accepts -any stream. In particular, this crate does not access any stream that is not -passed to it, in accordance with [I/O safety]. - -[PR \#51]: https://github.com/softprops/atty/pull/51 -[PR \#54]: https://github.com/softprops/atty/pull/54 +```toml +[dependencies] +atty = "0.2" +``` -## Example +## usage ```rust -use is_terminal::IsTerminal; +use atty::Stream; fn main() { - if std::io::stdout().is_terminal() { - println!("Stdout is a terminal"); - } else { - println!("Stdout is not a terminal"); - } + if atty::is(Stream::Stdout) { + println!("I'm a terminal"); + } else { + println!("I'm not"); + } } ``` -## Testing +## testing + +This library has been unit tested on both unix and windows platforms (via appveyor). + -This library is tested on both Unix-family and Windows platforms. +A simple example program is provided in this repo to test various tty's. By default. -To test it on a platform manually, use the provided `stdio` example program. -When run normally, it prints this: +It prints ```bash -$ cargo run --example stdio -stdin? true +$ cargo run --example atty stdout? true stderr? true +stdin? true ``` -To test stdin, pipe some text to the program: +To test std in, pipe some text to the program ```bash -$ cat | cargo run --example stdio -stdin? false +$ echo "test" | cargo run --example atty stdout? true stderr? true +stdin? false ``` -To test stdout, pipe the program to something: +To test std out, pipe the program to something ```bash -$ cargo run --example stdio | cat -stdin? true +$ cargo run --example atty | grep std stdout? false stderr? true +stdin? true ``` -To test stderr, pipe the program to something redirecting stderr: +To test std err, pipe the program to something redirecting std err ```bash -$ cargo run --example stdio 2>&1 | cat -stdin? true +$ cargo run --example atty 2>&1 | grep std stdout? false stderr? false +stdin? true ``` -# Minimum Supported Rust Version (MSRV) - -This crate currently works on the version of [Rust on Debian stable], which is -currently Rust 1.63. This policy may change in the future, in minor version -releases, so users using a fixed version of Rust should pin to a specific -version of this crate. - -[`isatty`]: https://man7.org/linux/man-pages/man3/isatty.3.html -[the atty crate]: https://crates.io/crates/atty -[I/O safety]: https://github.com/rust-lang/rfcs/blob/master/text/3128-io-safety.md -[Rust on Debian stable]: https://packages.debian.org/stable/rust/rustc -[`IsTerminal`]: https://doc.rust-lang.org/stable/std/io/trait.IsTerminal.html +Doug Tangren (softprops) 2015-2019 diff --git a/appveyor.yml b/appveyor.yml new file mode 100644 index 0000000..d7fb127 --- /dev/null +++ b/appveyor.yml @@ -0,0 +1,16 @@ +environment: + matrix: + - TARGET: nightly-x86_64-pc-windows-msvc + - TARGET: nightly-i686-pc-windows-msvc + - TARGET: nightly-x86_64-pc-windows-gnu + - TARGET: nightly-i686-pc-windows-gnu +install: + - ps: Start-FileDownload "https://static.rust-lang.org/dist/rust-${env:TARGET}.exe" -FileName "rust-install.exe" + - ps: .\rust-install.exe /VERYSILENT /NORESTART /DIR="C:\rust" | Out-Null + - ps: $env:PATH="$env:PATH;C:\rust\bin" + - call "%VCVARS%" || ver>nul + - rustc -vV + - cargo -vV +build: false +test_script: + - cargo build diff --git a/examples/atty.rs b/examples/atty.rs new file mode 100644 index 0000000..3b3635e --- /dev/null +++ b/examples/atty.rs @@ -0,0 +1,9 @@ +extern crate atty; + +use atty::{is, Stream}; + +fn main() { + println!("stdout? {}", is(Stream::Stdout)); + println!("stderr? {}", is(Stream::Stderr)); + println!("stdin? {}", is(Stream::Stdin)); +} diff --git a/examples/stdio.rs b/examples/stdio.rs deleted file mode 100644 index f895635..0000000 --- a/examples/stdio.rs +++ /dev/null @@ -1,7 +0,0 @@ -use is_terminal::IsTerminal; - -fn main() { - println!("stdin? {}", std::io::stdin().is_terminal()); - println!("stdout? {}", std::io::stdout().is_terminal()); - println!("stderr? {}", std::io::stderr().is_terminal()); -} diff --git a/rustfmt.toml b/rustfmt.toml new file mode 100644 index 0000000..899a094 --- /dev/null +++ b/rustfmt.toml @@ -0,0 +1,4 @@ +# https://github.com/rust-lang/rustfmt/blob/master/Configurations.md#fn_args_layout +fn_args_layout = "Vertical" +# https://github.com/rust-lang/rustfmt/blob/master/Configurations.md#merge_imports +merge_imports = true \ No newline at end of file diff --git a/src/lib.rs b/src/lib.rs index fda959c..418f617 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,154 +1,126 @@ -//! is-terminal is a simple utility that answers one question: +//! atty is a simple utility that answers one question +//! > is this a tty? //! -//! > Is this a terminal? +//! usage is just as simple //! -//! A "terminal", also known as a "tty", is an I/O device which may be -//! interactive and may support color and other special features. This crate -//! doesn't provide any of those features; it just answers this one question. -//! -//! On Unix-family platforms, this is effectively the same as the [`isatty`] -//! function for testing whether a given stream is a terminal, though it -//! accepts high-level stream types instead of raw file descriptors. -//! -//! On Windows, it uses a variety of techniques to determine whether the -//! given stream is a terminal. -//! -//! # Example -//! -//! ```rust -//! use is_terminal::IsTerminal; -//! -//! if std::io::stdout().is_terminal() { -//! println!("stdout is a terminal") +//! ``` +//! if atty::is(atty::Stream::Stdout) { +//! println!("i'm a tty") //! } //! ``` //! -//! [`isatty`]: https://man7.org/linux/man-pages/man3/isatty.3.html +//! ``` +//! if atty::isnt(atty::Stream::Stdout) { +//! println!("i'm not a tty") +//! } +//! ``` -#![cfg_attr( - not(any( - unix, - windows, - target_os = "wasi", - target_os = "hermit", - target_os = "unknown" - )), - no_std -)] +#![cfg_attr(unix, no_std)] -#[cfg(target_os = "wasi")] -use std::os::fd::{AsFd, AsRawFd}; -#[cfg(target_os = "hermit")] -use std::os::hermit::io::AsFd; #[cfg(unix)] -use std::os::unix::io::{AsFd, AsRawFd}; +extern crate libc; #[cfg(windows)] -use std::os::windows::io::{AsHandle, AsRawHandle, BorrowedHandle}; +extern crate windows_sys; + #[cfg(windows)] -use windows_sys::Win32::Foundation::HANDLE; +use windows_sys::Win32::System::Console::STD_HANDLE; -/// Extension trait to check whether something is a terminal. -pub trait IsTerminal { - /// Returns true if this is a terminal. - /// - /// # Example - /// - /// ``` - /// use is_terminal::IsTerminal; - /// - /// if std::io::stdout().is_terminal() { - /// println!("stdout is a terminal") - /// } - /// ``` - fn is_terminal(&self) -> bool; +/// possible stream sources +#[derive(Clone, Copy, Debug)] +pub enum Stream { + Stdout, + Stderr, + Stdin, } -/// Returns `true` if `this` is a terminal. -/// -/// This is equivalent to calling `this.is_terminal()` and exists only as a -/// convenience to calling the trait method [`IsTerminal::is_terminal`] -/// without importing the trait. -/// -/// # Example -/// -/// ``` -/// if is_terminal::is_terminal(&std::io::stdout()) { -/// println!("stdout is a terminal") -/// } -/// ``` -pub fn is_terminal(this: T) -> bool { - this.is_terminal() +/// returns true if this is a tty +#[cfg(all(unix, not(target_arch = "wasm32")))] +pub fn is(stream: Stream) -> bool { + extern crate libc; + + let fd = match stream { + Stream::Stdout => libc::STDOUT_FILENO, + Stream::Stderr => libc::STDERR_FILENO, + Stream::Stdin => libc::STDIN_FILENO, + }; + unsafe { libc::isatty(fd) != 0 } } -#[cfg(not(any(windows, target_os = "unknown")))] -impl IsTerminal for Stream { - #[inline] - fn is_terminal(&self) -> bool { - #[cfg(any(unix, target_os = "wasi"))] - { - let fd = self.as_fd(); - unsafe { libc::isatty(fd.as_raw_fd()) != 0 } - } +/// returns true if this is a tty +#[cfg(target_os = "hermit")] +pub fn is(stream: Stream) -> bool { + extern crate hermit_abi; - #[cfg(target_os = "hermit")] - { - use std::os::hermit::io::AsRawFd; - hermit_abi::isatty(self.as_fd().as_fd().as_raw_fd()) - } - } + let fd = match stream { + Stream::Stdout => hermit_abi::STDOUT_FILENO, + Stream::Stderr => hermit_abi::STDERR_FILENO, + Stream::Stdin => hermit_abi::STDIN_FILENO, + }; + hermit_abi::isatty(fd) } +/// returns true if this is a tty #[cfg(windows)] -impl IsTerminal for Stream { - #[inline] - fn is_terminal(&self) -> bool { - handle_is_console(self.as_handle()) +pub fn is(stream: Stream) -> bool { + use windows_sys::Win32::System::Console::{ + STD_ERROR_HANDLE as STD_ERROR, STD_INPUT_HANDLE as STD_INPUT, + STD_OUTPUT_HANDLE as STD_OUTPUT, + }; + + 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]) } { + // False positives aren't possible. If we got a console then + // we definitely have a tty on stdin. + return true; } -} -// The Windows implementation here is copied from `handle_is_console` in -// library/std/src/sys/pal/windows/io.rs in Rust at revision -// e74c667a53c6368579867a74494e6fb7a7f17d13. + // At this point, we *could* have a false negative. We can determine that + // 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; + } -#[cfg(windows)] -fn handle_is_console(handle: BorrowedHandle<'_>) -> bool { - use windows_sys::Win32::System::Console::GetConsoleMode; + // 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) } +} - let handle = handle.as_raw_handle(); +/// returns true if this is _not_ a tty +pub fn isnt(stream: Stream) -> bool { + !is(stream) +} - // A null handle means the process has no console. - if handle.is_null() { - return false; - } +/// Returns true if any of the given fds are on a console. +#[cfg(windows)] +unsafe fn console_on_any(fds: &[STD_HANDLE]) -> bool { + use windows_sys::Win32::System::Console::{GetConsoleMode, GetStdHandle}; - unsafe { + for &fd in fds { let mut out = 0; - if GetConsoleMode(handle as HANDLE, &mut out) != 0 { - // False positives aren't possible. If we got a console then we definitely have a console. + let handle = GetStdHandle(fd); + if GetConsoleMode(handle, &mut out) != 0 { return true; } - - // Otherwise, we fall back to an msys hack to see if we can detect the presence of a pty. - msys_tty_on(handle as HANDLE) } + false } /// Returns true if there is an MSYS tty on the given handle. #[cfg(windows)] -unsafe fn msys_tty_on(handle: HANDLE) -> bool { +unsafe fn msys_tty_on(fd: STD_HANDLE) -> bool { use std::ffi::c_void; use windows_sys::Win32::{ Foundation::MAX_PATH, - Storage::FileSystem::{ - FileNameInfo, GetFileInformationByHandleEx, GetFileType, FILE_TYPE_PIPE, - }, + Storage::FileSystem::{FileNameInfo, GetFileInformationByHandleEx}, + System::Console::GetStdHandle, }; - // Early return if the handle is not a pipe. - if GetFileType(handle) != FILE_TYPE_PIPE { - return false; - } - /// Mirrors windows_sys::Win32::Storage::FileSystem::FILE_NAME_INFO, giving /// it a fixed length that we can stack allocate #[repr(C)] @@ -161,222 +133,86 @@ unsafe fn msys_tty_on(handle: HANDLE) -> bool { FileNameLength: 0, FileName: [0; MAX_PATH as usize], }; - // Safety: buffer length is fixed. - let res = GetFileInformationByHandleEx( - handle, - FileNameInfo, - &mut name_info as *mut _ as *mut c_void, - std::mem::size_of::() as u32, - ); + let handle = unsafe { + // Safety: function has no invariants. an invalid handle id will cause + // GetFileInformationByHandleEx to return an error + GetStdHandle(fd) + }; + let res = unsafe { + // Safety: handle is valid, and buffer length is fixed + GetFileInformationByHandleEx( + handle, + FileNameInfo, + &mut name_info as *mut _ as *mut c_void, + std::mem::size_of::() as u32, + ) + }; if res == 0 { return false; } - - // Use `get` because `FileNameLength` can be out of range. - let s = match name_info - .FileName - .get(..name_info.FileNameLength as usize / 2) - { - None => return false, - Some(s) => s, - }; + let s = &name_info.FileName[..name_info.FileNameLength as usize]; let name = String::from_utf16_lossy(s); - // Get the file name only. - let name = name.rsplit('\\').next().unwrap_or(&name); // This checks whether 'pty' exists in the file name, which indicates that // a pseudo-terminal is attached. To mitigate against false positives // (e.g., an actual file name that contains 'pty'), we also require that - // the file name begins with either the strings 'msys-' or 'cygwin-'.) - let is_msys = name.starts_with("msys-") || name.starts_with("cygwin-"); + // either the strings 'msys-' or 'cygwin-' are in the file name as well.) + let is_msys = name.contains("msys-") || name.contains("cygwin-"); let is_pty = name.contains("-pty"); is_msys && is_pty } -#[cfg(target_os = "unknown")] -impl IsTerminal for std::io::Stdin { - #[inline] - fn is_terminal(&self) -> bool { - false - } -} - -#[cfg(target_os = "unknown")] -impl IsTerminal for std::io::Stdout { - #[inline] - fn is_terminal(&self) -> bool { - false - } -} - -#[cfg(target_os = "unknown")] -impl IsTerminal for std::io::Stderr { - #[inline] - fn is_terminal(&self) -> bool { - false - } -} - -#[cfg(target_os = "unknown")] -impl<'a> IsTerminal for std::io::StdinLock<'a> { - #[inline] - fn is_terminal(&self) -> bool { - false - } -} - -#[cfg(target_os = "unknown")] -impl<'a> IsTerminal for std::io::StdoutLock<'a> { - #[inline] - fn is_terminal(&self) -> bool { - false - } -} - -#[cfg(target_os = "unknown")] -impl<'a> IsTerminal for std::io::StderrLock<'a> { - #[inline] - fn is_terminal(&self) -> bool { - false - } -} - -#[cfg(target_os = "unknown")] -impl<'a> IsTerminal for std::fs::File { - #[inline] - fn is_terminal(&self) -> bool { - false - } -} - -#[cfg(target_os = "unknown")] -impl IsTerminal for std::process::ChildStdin { - #[inline] - fn is_terminal(&self) -> bool { - false - } -} - -#[cfg(target_os = "unknown")] -impl IsTerminal for std::process::ChildStdout { - #[inline] - fn is_terminal(&self) -> bool { - false - } -} - -#[cfg(target_os = "unknown")] -impl IsTerminal for std::process::ChildStderr { - #[inline] - fn is_terminal(&self) -> bool { - false - } +/// returns true if this is a tty +#[cfg(any(target_arch = "wasm32", target_env = "sgx"))] +pub fn is(_stream: Stream) -> bool { + false } #[cfg(test)] mod tests { - #[cfg(not(target_os = "unknown"))] - use super::IsTerminal; + use super::{is, Stream}; #[test] #[cfg(windows)] - fn stdin() { - assert_eq!( - atty::is(atty::Stream::Stdin), - std::io::stdin().is_terminal() - ) + fn is_err() { + // appveyor pipes its output + assert!(!is(Stream::Stderr)) } #[test] #[cfg(windows)] - fn stdout() { - assert_eq!( - atty::is(atty::Stream::Stdout), - std::io::stdout().is_terminal() - ) + fn is_out() { + // appveyor pipes its output + assert!(!is(Stream::Stdout)) } #[test] #[cfg(windows)] - fn stderr() { - assert_eq!( - atty::is(atty::Stream::Stderr), - std::io::stderr().is_terminal() - ) - } - - #[test] - #[cfg(any(unix, target_os = "wasi"))] - fn stdin() { - assert_eq!( - atty::is(atty::Stream::Stdin), - rustix::stdio::stdin().is_terminal() - ) - } - - #[test] - #[cfg(any(unix, target_os = "wasi"))] - fn stdout() { - assert_eq!( - atty::is(atty::Stream::Stdout), - rustix::stdio::stdout().is_terminal() - ) + fn is_in() { + assert!(is(Stream::Stdin)) } #[test] - #[cfg(any(unix, target_os = "wasi"))] - fn stderr() { - assert_eq!( - atty::is(atty::Stream::Stderr), - rustix::stdio::stderr().is_terminal() - ) + #[cfg(unix)] + fn is_err() { + assert!(is(Stream::Stderr)) } #[test] - #[cfg(any(unix, target_os = "wasi"))] - fn stdin_vs_libc() { - unsafe { - assert_eq!( - libc::isatty(libc::STDIN_FILENO) != 0, - rustix::stdio::stdin().is_terminal() - ) - } + #[cfg(unix)] + fn is_out() { + assert!(is(Stream::Stdout)) } #[test] - #[cfg(any(unix, target_os = "wasi"))] - fn stdout_vs_libc() { - unsafe { - assert_eq!( - libc::isatty(libc::STDOUT_FILENO) != 0, - rustix::stdio::stdout().is_terminal() - ) - } + #[cfg(target_os = "macos")] + fn is_in() { + // macos on travis seems to pipe its input + assert!(is(Stream::Stdin)) } #[test] - #[cfg(any(unix, target_os = "wasi"))] - fn stderr_vs_libc() { - unsafe { - assert_eq!( - libc::isatty(libc::STDERR_FILENO) != 0, - rustix::stdio::stderr().is_terminal() - ) - } - } - - // Verify that the msys_tty_on function works with long path. - #[test] - #[cfg(windows)] - fn msys_tty_on_path_length() { - use std::{fs::File, os::windows::io::AsRawHandle}; - use windows_sys::Win32::Foundation::MAX_PATH; - - let dir = tempfile::tempdir().expect("Unable to create temporary directory"); - let file_path = dir.path().join("ten_chars_".repeat(25)); - // Ensure that the path is longer than MAX_PATH. - assert!(file_path.to_string_lossy().len() > MAX_PATH as usize); - let file = File::create(file_path).expect("Unable to create file"); - - assert!(!unsafe { crate::msys_tty_on(file.as_raw_handle()) }); + #[cfg(all(not(target_os = "macos"), unix))] + fn is_in() { + assert!(is(Stream::Stdin)) } }