230925.2 · 19a08bee8a99e8c725b9781568013a9f686605a3
cli: clean-up crate
This does a variety of polishing.
1. Deprecate the tty methods in favor of std's IsTerminal trait.
2. Trim down un-needed dependencies.
3. Use bstr to implement escaping.
4. Various aesthetic polishing.
I'm doing this as prep work before adding more to this crate. And as part of a general effort toward reducing ripgrep's dependencies.
reading declarations
@@ -187,10 +187,7 @@ version = "0.1.9" dependencies = [ "bstr", "globset",- "lazy_static", "log",- "regex",- "same-file", "termcolor", "winapi-util", ]@@ -612,9 +609,9 @@ checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" [[package]] name = "winapi-util"-version = "0.1.5"+version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index"-checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178"+checksum = "f29e6f9198ba0d26b4c9f07dbe6f9ed633e1f3d5b8b414090084349e46a52596" dependencies = [ "winapi", ]
@@ -11,16 +11,13 @@ repository = "https://github.com/BurntSushi/ripgrep/tree/master/crates/cli" readme = "README.md" keywords = ["regex", "grep", "cli", "utility", "util"] license = "Unlicense OR MIT"-edition = "2018"+edition = "2021" [dependencies]-bstr = "1.6.0"+bstr = { version = "1.6.2", features = ["std"] } globset = { version = "0.4.10", path = "../globset" }-lazy_static = "1.1.0"-log = "0.4.5"-regex = "1.1"-same-file = "1.0.4"-termcolor = "1.0.4"+log = "0.4.20"+termcolor = "1.3.0" [target.'cfg(windows)'.dependencies.winapi-util]-version = "0.1.1"+version = "0.1.6"
@@ -1,8 +1,10 @@-use std::ffi::{OsStr, OsString};-use std::fs::File;-use std::io;-use std::path::{Path, PathBuf};-use std::process::Command;+use std::{+ ffi::{OsStr, OsString},+ fs::File,+ io,+ path::{Path, PathBuf},+ process::Command,+}; use globset::{Glob, GlobSet, GlobSetBuilder}; @@ -161,7 +163,7 @@ impl DecompressionMatcher { /// Create a new matcher with default rules. /// /// To add more matching rules, build a matcher with- /// [`DecompressionMatcherBuilder`](struct.DecompressionMatcherBuilder.html).+ /// [`DecompressionMatcherBuilder`]. pub fn new() -> DecompressionMatcher { DecompressionMatcherBuilder::new() .build()@@ -221,9 +223,8 @@ impl DecompressionReaderBuilder { path: P, ) -> Result<DecompressionReader, CommandError> { let path = path.as_ref();- let mut cmd = match self.matcher.command(path) {- None => return DecompressionReader::new_passthru(path),- Some(cmd) => cmd,+ let Some(mut cmd) = self.matcher.command(path) else {+ return DecompressionReader::new_passthru(path); }; cmd.arg(path); @@ -302,9 +303,7 @@ impl DecompressionReaderBuilder { /// The default matching rules are probably good enough for most cases, and if /// they require revision, pull requests are welcome. In cases where they must /// be changed or extended, they can be customized through the use of-/// [`DecompressionMatcherBuilder`](struct.DecompressionMatcherBuilder.html)-/// and-/// [`DecompressionReaderBuilder`](struct.DecompressionReaderBuilder.html).+/// [`DecompressionMatcherBuilder`] and [`DecompressionReaderBuilder`]. /// /// By default, this reader will asynchronously read the processes' stderr. /// This prevents subtle deadlocking bugs for noisy processes that write a lot@@ -320,15 +319,14 @@ impl DecompressionReaderBuilder { /// matcher. /// /// ```no_run-/// use std::io::Read;-/// use std::process::Command;+/// use std::{io::Read, process::Command};+/// /// use grep_cli::DecompressionReader; ///-/// # fn example() -> Result<(), Box<::std::error::Error>> { /// let mut rdr = DecompressionReader::new("/usr/share/man/man1/ls.1.gz")?; /// let mut contents = vec![]; /// rdr.read_to_end(&mut contents)?;-/// # Ok(()) }+/// # Ok::<(), Box<dyn std::error::Error>>(()) /// ``` #[derive(Debug)] pub struct DecompressionReader {@@ -347,9 +345,7 @@ impl DecompressionReader { /// /// This uses the default matching rules for determining how to decompress /// the given file. To change those matching rules, use- /// [`DecompressionReaderBuilder`](struct.DecompressionReaderBuilder.html)- /// and- /// [`DecompressionMatcherBuilder`](struct.DecompressionMatcherBuilder.html).+ /// [`DecompressionReaderBuilder`] and [`DecompressionMatcherBuilder`]. /// /// When creating readers for many paths. it is better to use the builder /// since it will amortize the cost of constructing the matcher.@@ -453,10 +449,7 @@ fn try_resolve_binary<P: AsRef<Path>>( use std::env; fn is_exe(path: &Path) -> bool {- let md = match path.metadata() {- Err(_) => return false,- Ok(md) => md,- };+ let Ok(md) = path.metadata() else { return false }; !md.is_dir() } @@ -464,15 +457,12 @@ fn try_resolve_binary<P: AsRef<Path>>( if prog.is_absolute() { return Ok(prog.to_path_buf()); }- let syspaths = match env::var_os("PATH") {- Some(syspaths) => syspaths,- None => {- let msg = "system PATH environment variable not found";- return Err(CommandError::io(io::Error::new(- io::ErrorKind::Other,- msg,- )));- }+ let Some(syspaths) = env::var_os("PATH") else {+ let msg = "system PATH environment variable not found";+ return Err(CommandError::io(io::Error::new(+ io::ErrorKind::Other,+ msg,+ ))); }; for syspath in env::split_paths(&syspaths) { if syspath.as_os_str().is_empty() {
@@ -1,21 +1,7 @@ use std::ffi::OsStr;-use std::str; use bstr::{ByteSlice, ByteVec}; -/// A single state in the state machine used by `unescape`.-#[derive(Clone, Copy, Eq, PartialEq)]-enum State {- /// The state after seeing a `\`.- Escape,- /// The state after seeing a `\x`.- HexFirst,- /// The state after seeing a `\x[0-9A-Fa-f]`.- HexSecond(char),- /// Default state.- Literal,-}- /// Escapes arbitrary bytes into a human readable string. /// /// This converts `\t`, `\r` and `\n` into their escaped forms. It also@@ -38,17 +24,7 @@ enum State { /// assert_eq!(r"foo\nbar\xFFbaz", escape(b"foo\nbar\xFFbaz")); /// ``` pub fn escape(bytes: &[u8]) -> String {- let mut escaped = String::new();- for (s, e, ch) in bytes.char_indices() {- if ch == '\u{FFFD}' {- for b in bytes[s..e].bytes() {- escape_byte(b, &mut escaped);- }- } else {- escape_char(ch, &mut escaped);- }- }- escaped+ bytes.escape_bytes().to_string() } /// Escapes an OS string into a human readable string.@@ -89,76 +65,7 @@ pub fn escape_os(string: &OsStr) -> String { /// assert_eq!(&b"foo\nbar\xFFbaz"[..], &*unescape(r"foo\nbar\xFFbaz")); /// ``` pub fn unescape(s: &str) -> Vec<u8> {- use self::State::*;-- let mut bytes = vec![];- let mut state = Literal;- for c in s.chars() {- match state {- Escape => match c {- '\\' => {- bytes.push(b'\\');- state = Literal;- }- 'n' => {- bytes.push(b'\n');- state = Literal;- }- 'r' => {- bytes.push(b'\r');- state = Literal;- }- 't' => {- bytes.push(b'\t');- state = Literal;- }- 'x' => {- state = HexFirst;- }- c => {- bytes.extend(format!(r"\{}", c).into_bytes());- state = Literal;- }- },- HexFirst => match c {- '0'..='9' | 'A'..='F' | 'a'..='f' => {- state = HexSecond(c);- }- c => {- bytes.extend(format!(r"\x{}", c).into_bytes());- state = Literal;- }- },- HexSecond(first) => match c {- '0'..='9' | 'A'..='F' | 'a'..='f' => {- let ordinal = format!("{}{}", first, c);- let byte = u8::from_str_radix(&ordinal, 16).unwrap();- bytes.push(byte);- state = Literal;- }- c => {- let original = format!(r"\x{}{}", first, c);- bytes.extend(original.into_bytes());- state = Literal;- }- },- Literal => match c {- '\\' => {- state = Escape;- }- c => {- bytes.extend(c.to_string().as_bytes());- }- },- }- }- match state {- Escape => bytes.push(b'\\'),- HexFirst => bytes.extend(b"\\x"),- HexSecond(c) => bytes.extend(format!("\\x{}", c).into_bytes()),- Literal => {}- }- bytes+ Vec::unescape_bytes(s) } /// Unescapes an OS string.@@ -171,27 +78,6 @@ pub fn unescape_os(string: &OsStr) -> Vec<u8> { unescape(&string.to_string_lossy()) } -/// Adds the given codepoint to the given string, escaping it if necessary.-fn escape_char(cp: char, into: &mut String) {- if cp.is_ascii() {- escape_byte(cp as u8, into);- } else {- into.push(cp);- }-}--/// Adds the given byte to the given string, escaping it if necessary.-fn escape_byte(byte: u8, into: &mut String) {- match byte {- 0x21..=0x5B | 0x5D..=0x7D => into.push(byte as char),- b'\n' => into.push_str(r"\n"),- b'\r' => into.push_str(r"\r"),- b'\t' => into.push_str(r"\t"),- b'\\' => into.push_str(r"\\"),- _ => into.push_str(&format!(r"\x{:02X}", byte)),- }-}- #[cfg(test)] mod tests { use super::{escape, unescape};@@ -215,7 +101,8 @@ mod tests { #[test] fn nul() { assert_eq!(b(b"\x00"), unescape(r"\x00"));- assert_eq!(r"\x00", escape(b"\x00"));+ assert_eq!(b(b"\x00"), unescape(r"\0"));+ assert_eq!(r"\0", escape(b"\x00")); } #[test]
@@ -1,10 +1,3 @@-use std::error;-use std::fmt;-use std::io;-use std::num::ParseIntError;--use regex::Regex;- /// An error that occurs when parsing a human readable size description. /// /// This error provides an end user friendly message describing why the@@ -18,7 +11,7 @@ pub struct ParseSizeError { #[derive(Clone, Debug, Eq, PartialEq)] enum ParseSizeErrorKind { InvalidFormat,- InvalidInt(ParseIntError),+ InvalidInt(std::num::ParseIntError), Overflow, } @@ -30,7 +23,7 @@ impl ParseSizeError { } } - fn int(original: &str, err: ParseIntError) -> ParseSizeError {+ fn int(original: &str, err: std::num::ParseIntError) -> ParseSizeError { ParseSizeError { original: original.to_string(), kind: ParseSizeErrorKind::InvalidInt(err),@@ -45,22 +38,18 @@ impl ParseSizeError { } } -impl error::Error for ParseSizeError {- fn description(&self) -> &str {- "invalid size"- }-}+impl std::error::Error for ParseSizeError {} -impl fmt::Display for ParseSizeError {- fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {+impl std::fmt::Display for ParseSizeError {+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { use self::ParseSizeErrorKind::*; match self.kind { InvalidFormat => write!( f,- "invalid format for size '{}', which should be a sequence \- of digits followed by an optional 'K', 'M' or 'G' \- suffix",+ "invalid format for size '{}', which should be a non-empty \+ sequence of digits followed by an optional 'K', 'M' or 'G' \+ suffix", self.original ), InvalidInt(ref err) => write!(@@ -73,9 +62,9 @@ impl fmt::Display for ParseSizeError { } } -impl From<ParseSizeError> for io::Error {- fn from(size_err: ParseSizeError) -> io::Error {- io::Error::new(io::ErrorKind::Other, size_err)+impl From<ParseSizeError> for std::io::Error {+ fn from(size_err: ParseSizeError) -> std::io::Error {+ std::io::Error::new(std::io::ErrorKind::Other, size_err) } } @@ -88,29 +77,24 @@ impl From<ParseSizeError> for io::Error { /// /// Additional suffixes may be added over time. pub fn parse_human_readable_size(size: &str) -> Result<u64, ParseSizeError> {- lazy_static::lazy_static! {- // Normally I'd just parse something this simple by hand to avoid the- // regex dep, but we bring regex in any way for glob matching, so might- // as well use it.- static ref RE: Regex = Regex::new(r"^([0-9]+)([KMG])?$").unwrap();+ let digits_end =+ size.as_bytes().iter().take_while(|&b| b.is_ascii_digit()).count();+ let digits = &size[..digits_end];+ if digits.is_empty() {+ return Err(ParseSizeError::format(size)); }+ let value =+ digits.parse::<u64>().map_err(|e| ParseSizeError::int(size, e))?; - let caps = match RE.captures(size) {- Some(caps) => caps,- None => return Err(ParseSizeError::format(size)),- };- let value: u64 =- caps[1].parse().map_err(|err| ParseSizeError::int(size, err))?;- let suffix = match caps.get(2) {- None => return Ok(value),- Some(cap) => cap.as_str(),- };+ let suffix = &size[digits_end..];+ if suffix.is_empty() {+ return Ok(value);+ } let bytes = match suffix { "K" => value.checked_mul(1 << 10), "M" => value.checked_mul(1 << 20), "G" => value.checked_mul(1 << 30),- // Because if the regex matches this group, it must be [KMG].- _ => unreachable!(),+ _ => return Err(ParseSizeError::format(size)), }; bytes.ok_or_else(|| ParseSizeError::overflow(size)) }
@@ -11,27 +11,11 @@ and Linux. # Standard I/O -The-[`is_readable_stdin`](fn.is_readable_stdin.html),-[`is_tty_stderr`](fn.is_tty_stderr.html),-[`is_tty_stdin`](fn.is_tty_stdin.html)-and-[`is_tty_stdout`](fn.is_tty_stdout.html)-routines query aspects of standard I/O. `is_readable_stdin` determines whether-stdin can be usefully read from, while the `tty` methods determine whether a-tty is attached to stdin/stdout/stderr.--`is_readable_stdin` is useful when writing an application that changes behavior-based on whether the application was invoked with data on stdin. For example,-`rg foo` might recursively search the current working directory for-occurrences of `foo`, but `rg foo < file` might only search the contents of-`file`.--The `tty` methods are useful for similar reasons. Namely, commands like `ls`-will change their output depending on whether they are printing to a terminal-or not. For example, `ls` shows a file on each line when stdout is redirected-to a file or a pipe, but condenses the output to show possibly many files on-each line when stdout is connected to a tty.+[`is_readable_stdin`] determines whether stdin can be usefully read from. It+is useful when writing an application that changes behavior based on whether+the application was invoked with data on stdin. For example, `rg foo` might+recursively search the current working directory for occurrences of `foo`, but+`rg foo < file` might only search the contents of `file`. # Coloring and buffering@@ -165,21 +149,21 @@ mod pattern; mod process; mod wtr; -use std::io::IsTerminal;--pub use crate::decompress::{- resolve_binary, DecompressionMatcher, DecompressionMatcherBuilder,- DecompressionReader, DecompressionReaderBuilder,-};-pub use crate::escape::{escape, escape_os, unescape, unescape_os};-pub use crate::human::{parse_human_readable_size, ParseSizeError};-pub use crate::pattern::{- pattern_from_bytes, pattern_from_os, patterns_from_path,- patterns_from_reader, patterns_from_stdin, InvalidPatternError,-};-pub use crate::process::{CommandError, CommandReader, CommandReaderBuilder};-pub use crate::wtr::{- stdout, stdout_buffered_block, stdout_buffered_line, StandardStream,+pub use crate::{+ decompress::{+ resolve_binary, DecompressionMatcher, DecompressionMatcherBuilder,+ DecompressionReader, DecompressionReaderBuilder,+ },+ escape::{escape, escape_os, unescape, unescape_os},+ human::{parse_human_readable_size, ParseSizeError},+ pattern::{+ pattern_from_bytes, pattern_from_os, patterns_from_path,+ patterns_from_reader, patterns_from_stdin, InvalidPatternError,+ },+ process::{CommandError, CommandReader, CommandReaderBuilder},+ wtr::{+ stdout, stdout_buffered_block, stdout_buffered_line, StandardStream,+ }, }; /// Returns true if and only if stdin is believed to be readable.@@ -189,34 +173,60 @@ pub use crate::wtr::{ /// might search the current directory for occurrences of `foo` where as /// `command foo < some-file` or `cat some-file | command foo` might instead /// only search stdin for occurrences of `foo`.+///+/// Note that this isn't perfect and essentially corresponds to a heuristic.+/// When things are unclear (such as if an error occurs during introspection to+/// determine whether stdin is readable), this prefers to return `false`. That+/// means it's possible for an end user to pipe something into your program and+/// have this return `false` and thus potentially lead to ignoring the user's+/// stdin data. While not ideal, this is perhaps better than falsely assuming+/// stdin is readable, which would result in blocking forever on reading stdin.+/// Regardless, commands should always provide explicit fallbacks to override+/// behavior. For example, `rg foo -` will explicitly search stdin and `rg foo+/// ./` will explicitly search the current working directory. pub fn is_readable_stdin() -> bool {+ use std::io::IsTerminal;+ #[cfg(unix)] fn imp() -> bool {- use same_file::Handle;- use std::os::unix::fs::FileTypeExt;-- let ft = match Handle::stdin().and_then(|h| h.as_file().metadata()) {- Err(_) => return false,- Ok(md) => md.file_type(),+ use std::{+ fs::File,+ os::{fd::AsFd, unix::fs::FileTypeExt}, };++ let stdin = std::io::stdin();+ let Ok(fd) = stdin.as_fd().try_clone_to_owned() else { return false };+ let file = File::from(fd);+ let Ok(md) = file.metadata() else { return false };+ let ft = md.file_type(); ft.is_file() || ft.is_fifo() || ft.is_socket() } #[cfg(windows)] fn imp() -> bool {- use winapi_util as winutil;-- winutil::file::typ(winutil::HandleRef::stdin())+ winapi_util::file::typ(winapi_util::HandleRef::stdin()) .map(|t| t.is_disk() || t.is_pipe()) .unwrap_or(false) } - !is_tty_stdin() && imp()+ #[cfg(not(any(unix, windows)))]+ fn imp() -> bool {+ false+ }++ !std::io::stdin().is_terminal() && imp() } /// Returns true if and only if stdin is believed to be connected to a tty /// or a console.+///+/// Note that this is now just a wrapper around+/// [`std::io::IsTerminal`](https://doc.rust-lang.org/std/io/trait.IsTerminal.html).+/// Callers should prefer using the `IsTerminal` trait directly. This routine+/// is deprecated and will be removed in the next semver incompatible release.+#[deprecated(since = "0.1.10", note = "use std::io::IsTerminal instead")] pub fn is_tty_stdin() -> bool {+ use std::io::IsTerminal; std::io::stdin().is_terminal() } @@ -228,12 +238,26 @@ pub fn is_tty_stdin() -> bool { /// terminal or whether it's being redirected somewhere else. For example, /// implementations of `ls` will often show one item per line when stdout is /// redirected, but will condensed output when printing to a tty.+///+/// Note that this is now just a wrapper around+/// [`std::io::IsTerminal`](https://doc.rust-lang.org/std/io/trait.IsTerminal.html).+/// Callers should prefer using the `IsTerminal` trait directly. This routine+/// is deprecated and will be removed in the next semver incompatible release.+#[deprecated(since = "0.1.10", note = "use std::io::IsTerminal instead")] pub fn is_tty_stdout() -> bool {+ use std::io::IsTerminal; std::io::stdout().is_terminal() } /// Returns true if and only if stderr is believed to be connected to a tty /// or a console.+///+/// Note that this is now just a wrapper around+/// [`std::io::IsTerminal`](https://doc.rust-lang.org/std/io/trait.IsTerminal.html).+/// Callers should prefer using the `IsTerminal` trait directly. This routine+/// is deprecated and will be removed in the next semver incompatible release.+#[deprecated(since = "0.1.10", note = "use std::io::IsTerminal instead")] pub fn is_tty_stderr() -> bool {+ use std::io::IsTerminal; std::io::stderr().is_terminal() }
@@ -1,10 +1,4 @@-use std::error;-use std::ffi::OsStr;-use std::fmt;-use std::fs::File;-use std::io;-use std::path::Path;-use std::str;+use std::{ffi::OsStr, io, path::Path}; use bstr::io::BufReadExt; @@ -28,14 +22,10 @@ impl InvalidPatternError { } } -impl error::Error for InvalidPatternError {- fn description(&self) -> &str {- "invalid pattern"- }-}+impl std::error::Error for InvalidPatternError {} -impl fmt::Display for InvalidPatternError {- fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {+impl std::fmt::Display for InvalidPatternError {+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!( f, "found invalid UTF-8 in pattern at byte offset {}: {} \@@ -77,7 +67,7 @@ pub fn pattern_from_os(pattern: &OsStr) -> Result<&str, InvalidPatternError> { pub fn pattern_from_bytes( pattern: &[u8], ) -> Result<&str, InvalidPatternError> {- str::from_utf8(pattern).map_err(|err| InvalidPatternError {+ std::str::from_utf8(pattern).map_err(|err| InvalidPatternError { original: escape(pattern), valid_up_to: err.valid_up_to(), })@@ -91,7 +81,7 @@ pub fn pattern_from_bytes( /// path. pub fn patterns_from_path<P: AsRef<Path>>(path: P) -> io::Result<Vec<String>> { let path = path.as_ref();- let file = File::open(path).map_err(|err| {+ let file = std::fs::File::open(path).map_err(|err| { io::Error::new( io::ErrorKind::Other, format!("{}: {}", path.display(), err),@@ -135,7 +125,6 @@ pub fn patterns_from_stdin() -> io::Result<Vec<String>> { /// ``` /// use grep_cli::patterns_from_reader; ///-/// # fn example() -> Result<(), Box<::std::error::Error>> { /// let patterns = "\ /// foo /// bar\\s+foo@@ -147,7 +136,7 @@ pub fn patterns_from_stdin() -> io::Result<Vec<String>> { /// r"bar\s+foo", /// r"[a-z]{3}", /// ]);-/// # Ok(()) }+/// # Ok::<(), Box<dyn std::error::Error>>(()) /// ``` pub fn patterns_from_reader<R: io::Read>(rdr: R) -> io::Result<Vec<String>> { let mut patterns = vec![];
@@ -1,9 +1,7 @@-use std::error;-use std::fmt;-use std::io::{self, Read};-use std::iter;-use std::process;-use std::thread::{self, JoinHandle};+use std::{+ io::{self, Read},+ process,+}; /// An error that can occur while running a command and reading its output. ///@@ -40,14 +38,10 @@ impl CommandError { } } -impl error::Error for CommandError {- fn description(&self) -> &str {- "command error"- }-}+impl std::error::Error for CommandError {} -impl fmt::Display for CommandError {- fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {+impl std::fmt::Display for CommandError {+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self.kind { CommandErrorKind::Io(ref e) => e.fmt(f), CommandErrorKind::Stderr(ref bytes) => {@@ -55,7 +49,7 @@ impl fmt::Display for CommandError { if msg.trim().is_empty() { write!(f, "<stderr is empty>") } else {- let div = iter::repeat('-').take(79).collect::<String>();+ let div = "-".repeat(79); write!( f, "\n{div}\n{msg}\n{div}",@@ -161,18 +155,17 @@ impl CommandReaderBuilder { /// is returned as an error. /// /// ```no_run-/// use std::io::Read;-/// use std::process::Command;+/// use std::{io::Read, process::Command};+/// /// use grep_cli::CommandReader; ///-/// # fn example() -> Result<(), Box<::std::error::Error>> { /// let mut cmd = Command::new("gzip"); /// cmd.arg("-d").arg("-c").arg("/usr/share/man/man1/ls.1.gz"); /// /// let mut rdr = CommandReader::new(&mut cmd)?; /// let mut contents = vec![]; /// rdr.read_to_end(&mut contents)?;-/// # Ok(()) }+/// # Ok::<(), Box<dyn std::error::Error>>(()) /// ``` #[derive(Debug)] pub struct CommandReader {@@ -279,7 +272,7 @@ impl io::Read for CommandReader { /// stderr. #[derive(Debug)] enum StderrReader {- Async(Option<JoinHandle<CommandError>>),+ Async(Option<std::thread::JoinHandle<CommandError>>), Sync(process::ChildStderr), } @@ -287,7 +280,7 @@ impl StderrReader { /// Create a reader for stderr that reads contents asynchronously. fn r#async(mut stderr: process::ChildStderr) -> StderrReader { let handle =- thread::spawn(move || stderr_to_command_error(&mut stderr));+ std::thread::spawn(move || stderr_to_command_error(&mut stderr)); StderrReader::Async(Some(handle)) }
@@ -1,9 +1,6 @@-use std::io;+use std::io::{self, IsTerminal}; -use termcolor;-use termcolor::HyperlinkSpec;--use crate::is_tty_stdout;+use termcolor::{self, HyperlinkSpec}; /// A writer that supports coloring with either line or block buffering. pub struct StandardStream(StandardStreamKind);@@ -23,7 +20,7 @@ pub struct StandardStream(StandardStreamKind); /// The color choice given is passed along to the underlying writer. To /// completely disable colors in all cases, use `ColorChoice::Never`. pub fn stdout(color_choice: termcolor::ColorChoice) -> StandardStream {- if is_tty_stdout() {+ if std::io::stdout().is_terminal() { stdout_buffered_line(color_choice) } else { stdout_buffered_block(color_choice)
crates/core/args.rs
@@ -2,7 +2,7 @@ use std::cmp; use std::env; use std::ffi::{OsStr, OsString}; use std::fs;-use std::io::{self, Write};+use std::io::{self, IsTerminal, Write}; use std::path::{Path, PathBuf}; use std::process; use std::str::FromStr;@@ -976,7 +976,7 @@ impl ArgMatches { } else if preference == "ansi" { ColorChoice::AlwaysAnsi } else if preference == "auto" {- if cli::is_tty_stdout() || self.is_present("pretty") {+ if std::io::stdout().is_terminal() || self.is_present("pretty") { ColorChoice::Auto } else { ColorChoice::Never@@ -1110,7 +1110,7 @@ impl ArgMatches { if self.is_present("no-heading") || self.is_present("vimgrep") { false } else {- cli::is_tty_stdout()+ std::io::stdout().is_terminal() || self.is_present("heading") || self.is_present("pretty") }@@ -1178,7 +1178,7 @@ impl ArgMatches { // generally want to show line numbers by default when printing to a // tty for human consumption, except for one interesting case: when // we're only searching stdin. This makes pipelines work as expected.- (cli::is_tty_stdout() && !self.is_only_stdin(paths))+ (std::io::stdout().is_terminal() && !self.is_only_stdin(paths)) || self.is_present("line-number") || self.is_present("column") || self.is_present("pretty")
crates/grep/examples/simplegrep.rs
@@ -1,14 +1,15 @@-use std::env;-use std::error::Error;-use std::ffi::OsString;-use std::process;+use std::{env, error::Error, ffi::OsString, io::IsTerminal, process}; -use grep::cli;-use grep::printer::{ColorSpecs, StandardBuilder};-use grep::regex::RegexMatcher;-use grep::searcher::{BinaryDetection, SearcherBuilder};-use termcolor::ColorChoice;-use walkdir::WalkDir;+use {+ grep::{+ cli,+ printer::{ColorSpecs, StandardBuilder},+ regex::RegexMatcher,+ searcher::{BinaryDetection, SearcherBuilder},+ },+ termcolor::ColorChoice,+ walkdir::WalkDir,+}; fn main() { if let Err(err) = try_main() {@@ -36,7 +37,7 @@ fn search(pattern: &str, paths: &[OsString]) -> Result<(), Box<dyn Error>> { .build(); let mut printer = StandardBuilder::new() .color_specs(ColorSpecs::default_with_color())- .build(cli::stdout(if cli::is_tty_stdout() {+ .build(cli::stdout(if std::io::stdout().is_terminal() { ColorChoice::Auto } else { ColorChoice::Never