mirror of
https://github.com/BurntSushi/ripgrep.git
synced 2025-02-04 06:08:39 +02:00
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.
This commit is contained in:
parent
1a50324013
commit
19a08bee8a
7
Cargo.lock
generated
7
Cargo.lock
generated
@ -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)
|
||||
|
@ -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")
|
||||
|
@ -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
|
||||
|
Loading…
x
Reference in New Issue
Block a user