io: add IsTty API

Summary:
Add a new API to test if a stream is a tty. This is needed to replace the
Python `fin`, `fout` etc. to Rust objects, because the Python land requires the
`istty` API. It is also useful for properly implement color detection in the
pure Rust land.

Reviewed By: sfilipco

Differential Revision: D26612480

fbshipit-source-id: 5cf79447b1d74e0031a954788db342afd48dc288
This commit is contained in:
Jun Wu 2021-02-23 22:31:15 -08:00 committed by Facebook GitHub Bot
parent deb50bdef8
commit f3a5686a15
5 changed files with 134 additions and 7 deletions

View File

@ -12,6 +12,7 @@ python3 = ["python3-sys", "cpython/python3-sys"]
anyhow = "1.0.20"
cpython = { version = "0.5", default-features = false }
encoding = { path = "../encoding" }
io = { path = "../io" }
lazy_static = "1"
libc = "0.2"
parking_lot = "0.9"

View File

@ -61,6 +61,18 @@ impl io::Write for WrappedIO {
}
}
impl ::io::IsTty for WrappedIO {
fn is_tty(&self) -> bool {
(|| -> PyResult<bool> {
let gil = Python::acquire_gil();
let py = gil.python();
let result = self.0.call_method(py, "isatty", NoArgs, None)?;
result.extract(py)
})()
.unwrap_or(false)
}
}
/// Convert a Python `IOError` to Rust `io::Error`.
fn convert_ioerr(mut pyerr: PyErr) -> io::Error {
let gil = Python::acquire_gil();

View File

@ -5,6 +5,7 @@ authors = ["Facebook Source Control Team <sourcecontrol-dev@fb.com>"]
edition = "2018"
[dependencies]
atty = "0.2"
configparser = { path = "../configparser" }
once_cell = "1"
parking_lot = "0.10"

View File

@ -0,0 +1,99 @@
/*
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This software may be used and distributed according to the terms of the
* GNU General Public License version 2.
*/
use crate::IsTty;
use std::io::Cursor;
use std::sync::Weak;
impl IsTty for std::io::Stdin {
fn is_tty(&self) -> bool {
atty::is(atty::Stream::Stdin)
}
}
impl IsTty for std::io::Stdout {
fn is_tty(&self) -> bool {
atty::is(atty::Stream::Stdout)
}
}
impl IsTty for std::io::Stderr {
fn is_tty(&self) -> bool {
atty::is(atty::Stream::Stderr)
}
}
impl IsTty for Vec<u8> {
fn is_tty(&self) -> bool {
false
}
}
impl<'a> IsTty for &'a [u8] {
fn is_tty(&self) -> bool {
false
}
}
impl<T> IsTty for Cursor<T> {
fn is_tty(&self) -> bool {
false
}
}
impl IsTty for crate::IOOutput {
fn is_tty(&self) -> bool {
let inner = match Weak::upgrade(&self.0) {
Some(inner) => inner,
None => return false,
};
let inner = inner.lock();
inner.output.is_tty()
}
}
impl IsTty for crate::IOError {
fn is_tty(&self) -> bool {
let inner = match Weak::upgrade(&self.0) {
Some(inner) => inner,
None => return false,
};
let inner = inner.lock();
if let Some(error) = inner.error.as_ref() {
error.is_tty()
} else {
false
}
}
}
pub(crate) struct PipeWriterWithTty {
inner: pipe::PipeWriter,
is_tty: bool,
}
impl std::io::Write for PipeWriterWithTty {
fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
self.inner.write(buf)
}
fn flush(&mut self) -> std::io::Result<()> {
self.inner.flush()
}
}
impl IsTty for PipeWriterWithTty {
fn is_tty(&self) -> bool {
self.is_tty
}
}
impl PipeWriterWithTty {
pub fn new(inner: pipe::PipeWriter, is_tty: bool) -> Self {
Self { inner, is_tty }
}
}

View File

@ -56,7 +56,11 @@ struct Inner {
/// to obtain the "main" `IO`.
static MAIN_IO_REF: Lazy<RwLock<Option<Weak<Mutex<Inner>>>>> = Lazy::new(Default::default);
pub trait Read: io::Read + Any + Send + Sync {
pub trait IsTty {
fn is_tty(&self) -> bool;
}
pub trait Read: io::Read + IsTty + Any + Send + Sync {
fn as_any(&self) -> &dyn Any;
fn type_name(&self) -> &'static str {
@ -64,7 +68,7 @@ pub trait Read: io::Read + Any + Send + Sync {
}
}
pub trait Write: io::Write + Any + Send + Sync {
pub trait Write: io::Write + IsTty + Any + Send + Sync {
fn as_any(&self) -> &dyn Any;
fn type_name(&self) -> &'static str {
@ -72,18 +76,20 @@ pub trait Write: io::Write + Any + Send + Sync {
}
}
impl<T: io::Read + Any + Send + Sync> Read for T {
impl<T: io::Read + IsTty + Any + Send + Sync> Read for T {
fn as_any(&self) -> &dyn Any {
self
}
}
impl<T: io::Write + Any + Send + Sync> Write for T {
impl<T: io::Write + IsTty + Any + Send + Sync> Write for T {
fn as_any(&self) -> &dyn Any {
self
}
}
mod impls;
// Write to error.
impl io::Write for IOError {
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
@ -336,10 +342,18 @@ impl IO {
let (err_read, err_write) = pipe();
let (prg_read, prg_write) = pipe();
use impls::PipeWriterWithTty;
let out_is_tty = inner.output.is_tty();
let err_is_tty = inner
.error
.as_ref()
.map(|e| e.is_tty())
.unwrap_or_else(|| out_is_tty);
inner.flush()?;
inner.output = Box::new(out_write);
inner.error = Some(Box::new(err_write));
inner.progress = Some(Box::new(prg_write));
inner.output = Box::new(PipeWriterWithTty::new(out_write, out_is_tty));
inner.error = Some(Box::new(PipeWriterWithTty::new(err_write, err_is_tty)));
inner.progress = Some(Box::new(PipeWriterWithTty::new(prg_write, false)));
inner.pager_handle = Some(spawn(|| {
pager