mirror of
https://github.com/BurntSushi/ripgrep.git
synced 2025-04-24 17:12:16 +02:00
More progress. With coloring!
This commit is contained in:
parent
d8d7560fd0
commit
7a149c20fe
@ -23,6 +23,8 @@ crossbeam = "0.2"
|
|||||||
docopt = "0.6"
|
docopt = "0.6"
|
||||||
env_logger = "0.3"
|
env_logger = "0.3"
|
||||||
grep = { version = "0.1", path = "grep" }
|
grep = { version = "0.1", path = "grep" }
|
||||||
|
lazy_static = "0.2"
|
||||||
|
libc = "0.2"
|
||||||
log = "0.3"
|
log = "0.3"
|
||||||
memchr = "0.1"
|
memchr = "0.1"
|
||||||
memmap = "0.2"
|
memmap = "0.2"
|
||||||
@ -31,15 +33,19 @@ parking_lot = "0.3"
|
|||||||
regex = { version = "0.1", path = "/home/andrew/rust/regex" }
|
regex = { version = "0.1", path = "/home/andrew/rust/regex" }
|
||||||
regex-syntax = { version = "0.3.1", path = "/home/andrew/rust/regex/regex-syntax" }
|
regex-syntax = { version = "0.3.1", path = "/home/andrew/rust/regex/regex-syntax" }
|
||||||
rustc-serialize = "0.3"
|
rustc-serialize = "0.3"
|
||||||
|
term = { version = "0.4", path = "/home/andrew/clones/term" }
|
||||||
thread_local = "0.2"
|
thread_local = "0.2"
|
||||||
walkdir = "0.1"
|
walkdir = "0.1"
|
||||||
|
|
||||||
|
[target.'cfg(windows)'.dependencies]
|
||||||
|
kernel32-sys = "0.2"
|
||||||
|
winapi = "0.2"
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
simd-accel = ["regex/simd-accel"]
|
simd-accel = ["regex/simd-accel"]
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
glob = "0.2"
|
glob = "0.2"
|
||||||
lazy_static = "0.2"
|
|
||||||
|
|
||||||
[profile.release]
|
[profile.release]
|
||||||
debug = true
|
debug = true
|
||||||
|
91
src/args.rs
91
src/args.rs
@ -16,6 +16,7 @@ use ignore::Ignore;
|
|||||||
use out::Out;
|
use out::Out;
|
||||||
use printer::Printer;
|
use printer::Printer;
|
||||||
use search::{InputBuffer, Searcher};
|
use search::{InputBuffer, Searcher};
|
||||||
|
use sys;
|
||||||
use types::{FileTypeDef, Types, TypesBuilder};
|
use types::{FileTypeDef, Types, TypesBuilder};
|
||||||
use walk;
|
use walk;
|
||||||
|
|
||||||
@ -37,6 +38,9 @@ xrep is like the silver searcher and grep, but faster than both.
|
|||||||
Common options:
|
Common options:
|
||||||
-a, --text Search binary files as if they were text.
|
-a, --text Search binary files as if they were text.
|
||||||
-c, --count Only show count of line matches for each file.
|
-c, --count Only show count of line matches for each file.
|
||||||
|
--color WHEN Whether to use coloring in match.
|
||||||
|
Valid values are never, always or auto.
|
||||||
|
[default: auto]
|
||||||
-g, --glob GLOB ... Include or exclude files for searching that
|
-g, --glob GLOB ... Include or exclude files for searching that
|
||||||
match the given glob. This always overrides any
|
match the given glob. This always overrides any
|
||||||
other ignore logic. Multiple glob flags may be
|
other ignore logic. Multiple glob flags may be
|
||||||
@ -44,8 +48,13 @@ Common options:
|
|||||||
Precede a glob with a '!' to exclude it.
|
Precede a glob with a '!' to exclude it.
|
||||||
-h, --help Show this usage message.
|
-h, --help Show this usage message.
|
||||||
-i, --ignore-case Case insensitive search.
|
-i, --ignore-case Case insensitive search.
|
||||||
-n, --line-number Show line numbers (1-based).
|
-n, --line-number Show line numbers (1-based). This is enabled
|
||||||
|
by default at a tty.
|
||||||
|
-N, --no-line-number Suppress line numbers.
|
||||||
-q, --quiet Do not print anything to stdout.
|
-q, --quiet Do not print anything to stdout.
|
||||||
|
-r, --replace ARG Replace every match with the string given.
|
||||||
|
Capture group indices (e.g., $5) and names
|
||||||
|
(e.g., $foo) are supported.
|
||||||
-t, --type TYPE ... Only search files matching TYPE. Multiple type
|
-t, --type TYPE ... Only search files matching TYPE. Multiple type
|
||||||
flags may be provided. Use the --type-list flag
|
flags may be provided. Use the --type-list flag
|
||||||
to list all available types.
|
to list all available types.
|
||||||
@ -80,6 +89,13 @@ Less common options:
|
|||||||
Prefix each match with the file name that contains it. This is the
|
Prefix each match with the file name that contains it. This is the
|
||||||
default when more than one file is searched.
|
default when more than one file is searched.
|
||||||
|
|
||||||
|
--heading
|
||||||
|
Show the file name above clusters of matches from each file.
|
||||||
|
This is the default mode at a tty.
|
||||||
|
|
||||||
|
--no-heading
|
||||||
|
Don't show any file name heading.
|
||||||
|
|
||||||
--hidden
|
--hidden
|
||||||
Search hidden directories and files.
|
Search hidden directories and files.
|
||||||
|
|
||||||
@ -93,10 +109,16 @@ Less common options:
|
|||||||
--no-ignore
|
--no-ignore
|
||||||
Don't respect ignore files (.gitignore, .xrepignore, etc.)
|
Don't respect ignore files (.gitignore, .xrepignore, etc.)
|
||||||
|
|
||||||
|
--no-ignore-parent
|
||||||
|
Don't respect ignore files in parent directories.
|
||||||
|
|
||||||
|
-p, --pretty
|
||||||
|
Alias for --color=always --heading -n.
|
||||||
|
|
||||||
-Q, --literal
|
-Q, --literal
|
||||||
Treat the pattern as a literal string instead of a regular expression.
|
Treat the pattern as a literal string instead of a regular expression.
|
||||||
|
|
||||||
--threads ARG
|
-j, --threads ARG
|
||||||
The number of threads to use. Defaults to the number of logical CPUs
|
The number of threads to use. Defaults to the number of logical CPUs
|
||||||
(capped at 6). [default: 0]
|
(capped at 6). [default: 0]
|
||||||
|
|
||||||
@ -123,6 +145,7 @@ pub struct RawArgs {
|
|||||||
arg_path: Vec<String>,
|
arg_path: Vec<String>,
|
||||||
flag_after_context: usize,
|
flag_after_context: usize,
|
||||||
flag_before_context: usize,
|
flag_before_context: usize,
|
||||||
|
flag_color: String,
|
||||||
flag_context: usize,
|
flag_context: usize,
|
||||||
flag_context_separator: String,
|
flag_context_separator: String,
|
||||||
flag_count: bool,
|
flag_count: bool,
|
||||||
@ -130,14 +153,20 @@ pub struct RawArgs {
|
|||||||
flag_files: bool,
|
flag_files: bool,
|
||||||
flag_follow: bool,
|
flag_follow: bool,
|
||||||
flag_glob: Vec<String>,
|
flag_glob: Vec<String>,
|
||||||
|
flag_heading: bool,
|
||||||
flag_hidden: bool,
|
flag_hidden: bool,
|
||||||
flag_ignore_case: bool,
|
flag_ignore_case: bool,
|
||||||
flag_invert_match: bool,
|
flag_invert_match: bool,
|
||||||
flag_line_number: bool,
|
flag_line_number: bool,
|
||||||
flag_line_terminator: String,
|
flag_line_terminator: String,
|
||||||
flag_literal: bool,
|
flag_literal: bool,
|
||||||
|
flag_no_heading: bool,
|
||||||
flag_no_ignore: bool,
|
flag_no_ignore: bool,
|
||||||
|
flag_no_ignore_parent: bool,
|
||||||
|
flag_no_line_number: bool,
|
||||||
|
flag_pretty: bool,
|
||||||
flag_quiet: bool,
|
flag_quiet: bool,
|
||||||
|
flag_replace: Option<String>,
|
||||||
flag_text: bool,
|
flag_text: bool,
|
||||||
flag_threads: usize,
|
flag_threads: usize,
|
||||||
flag_type: Vec<String>,
|
flag_type: Vec<String>,
|
||||||
@ -156,18 +185,22 @@ pub struct Args {
|
|||||||
paths: Vec<PathBuf>,
|
paths: Vec<PathBuf>,
|
||||||
after_context: usize,
|
after_context: usize,
|
||||||
before_context: usize,
|
before_context: usize,
|
||||||
|
color: bool,
|
||||||
context_separator: Vec<u8>,
|
context_separator: Vec<u8>,
|
||||||
count: bool,
|
count: bool,
|
||||||
eol: u8,
|
eol: u8,
|
||||||
files: bool,
|
files: bool,
|
||||||
follow: bool,
|
follow: bool,
|
||||||
glob_overrides: Option<Gitignore>,
|
glob_overrides: Option<Gitignore>,
|
||||||
|
heading: bool,
|
||||||
hidden: bool,
|
hidden: bool,
|
||||||
ignore_case: bool,
|
ignore_case: bool,
|
||||||
invert_match: bool,
|
invert_match: bool,
|
||||||
line_number: bool,
|
line_number: bool,
|
||||||
no_ignore: bool,
|
no_ignore: bool,
|
||||||
|
no_ignore_parent: bool,
|
||||||
quiet: bool,
|
quiet: bool,
|
||||||
|
replace: Option<Vec<u8>>,
|
||||||
text: bool,
|
text: bool,
|
||||||
threads: usize,
|
threads: usize,
|
||||||
type_defs: Vec<FileTypeDef>,
|
type_defs: Vec<FileTypeDef>,
|
||||||
@ -194,7 +227,11 @@ impl RawArgs {
|
|||||||
};
|
};
|
||||||
let paths =
|
let paths =
|
||||||
if self.arg_path.is_empty() {
|
if self.arg_path.is_empty() {
|
||||||
|
if sys::stdin_is_atty() {
|
||||||
vec![Path::new("./").to_path_buf()]
|
vec![Path::new("./").to_path_buf()]
|
||||||
|
} else {
|
||||||
|
vec![Path::new("-").to_path_buf()]
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
self.arg_path.iter().map(|p| {
|
self.arg_path.iter().map(|p| {
|
||||||
Path::new(p).to_path_buf()
|
Path::new(p).to_path_buf()
|
||||||
@ -232,6 +269,12 @@ impl RawArgs {
|
|||||||
} else {
|
} else {
|
||||||
self.flag_threads
|
self.flag_threads
|
||||||
};
|
};
|
||||||
|
let color =
|
||||||
|
if self.flag_color == "auto" {
|
||||||
|
sys::stdout_is_atty() || self.flag_pretty
|
||||||
|
} else {
|
||||||
|
self.flag_color == "always"
|
||||||
|
};
|
||||||
let mut with_filename = self.flag_with_filename;
|
let mut with_filename = self.flag_with_filename;
|
||||||
if !with_filename {
|
if !with_filename {
|
||||||
with_filename = paths.len() > 1 || paths[0].is_dir();
|
with_filename = paths.len() > 1 || paths[0].is_dir();
|
||||||
@ -240,30 +283,44 @@ impl RawArgs {
|
|||||||
btypes.add_defaults();
|
btypes.add_defaults();
|
||||||
try!(self.add_types(&mut btypes));
|
try!(self.add_types(&mut btypes));
|
||||||
let types = try!(btypes.build());
|
let types = try!(btypes.build());
|
||||||
Ok(Args {
|
let mut args = Args {
|
||||||
pattern: pattern,
|
pattern: pattern,
|
||||||
paths: paths,
|
paths: paths,
|
||||||
after_context: after_context,
|
after_context: after_context,
|
||||||
before_context: before_context,
|
before_context: before_context,
|
||||||
|
color: color,
|
||||||
context_separator: unescape(&self.flag_context_separator),
|
context_separator: unescape(&self.flag_context_separator),
|
||||||
count: self.flag_count,
|
count: self.flag_count,
|
||||||
eol: eol,
|
eol: eol,
|
||||||
files: self.flag_files,
|
files: self.flag_files,
|
||||||
follow: self.flag_follow,
|
follow: self.flag_follow,
|
||||||
glob_overrides: glob_overrides,
|
glob_overrides: glob_overrides,
|
||||||
|
heading: !self.flag_no_heading && self.flag_heading,
|
||||||
hidden: self.flag_hidden,
|
hidden: self.flag_hidden,
|
||||||
ignore_case: self.flag_ignore_case,
|
ignore_case: self.flag_ignore_case,
|
||||||
invert_match: self.flag_invert_match,
|
invert_match: self.flag_invert_match,
|
||||||
line_number: self.flag_line_number,
|
line_number: !self.flag_no_line_number && self.flag_line_number,
|
||||||
no_ignore: self.flag_no_ignore,
|
no_ignore: self.flag_no_ignore,
|
||||||
|
no_ignore_parent: self.flag_no_ignore_parent,
|
||||||
quiet: self.flag_quiet,
|
quiet: self.flag_quiet,
|
||||||
|
replace: self.flag_replace.clone().map(|s| s.into_bytes()),
|
||||||
text: self.flag_text,
|
text: self.flag_text,
|
||||||
threads: threads,
|
threads: threads,
|
||||||
type_defs: btypes.definitions(),
|
type_defs: btypes.definitions(),
|
||||||
type_list: self.flag_type_list,
|
type_list: self.flag_type_list,
|
||||||
types: types,
|
types: types,
|
||||||
with_filename: with_filename,
|
with_filename: with_filename,
|
||||||
})
|
};
|
||||||
|
// If stdout is a tty, then apply some special default options.
|
||||||
|
if sys::stdout_is_atty() || self.flag_pretty {
|
||||||
|
if !self.flag_no_line_number && !args.count {
|
||||||
|
args.line_number = true;
|
||||||
|
}
|
||||||
|
if !self.flag_no_heading {
|
||||||
|
args.heading = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(args)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn add_types(&self, types: &mut TypesBuilder) -> Result<()> {
|
fn add_types(&self, types: &mut TypesBuilder) -> Result<()> {
|
||||||
@ -338,19 +395,26 @@ impl Args {
|
|||||||
|
|
||||||
/// Create a new printer of individual search results that writes to the
|
/// Create a new printer of individual search results that writes to the
|
||||||
/// writer given.
|
/// writer given.
|
||||||
pub fn printer<W: io::Write>(&self, wtr: W) -> Printer<W> {
|
pub fn printer<W: Send + io::Write>(&self, wtr: W) -> Printer<W> {
|
||||||
Printer::new(wtr)
|
let mut p = Printer::new(wtr, self.color)
|
||||||
.context_separator(self.context_separator.clone())
|
.context_separator(self.context_separator.clone())
|
||||||
.eol(self.eol)
|
.eol(self.eol)
|
||||||
|
.heading(self.heading)
|
||||||
.quiet(self.quiet)
|
.quiet(self.quiet)
|
||||||
.with_filename(self.with_filename)
|
.with_filename(self.with_filename);
|
||||||
|
if let Some(ref rep) = self.replace {
|
||||||
|
p = p.replace(rep.clone());
|
||||||
|
}
|
||||||
|
p
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Create a new printer of search results for an entire file that writes
|
/// Create a new printer of search results for an entire file that writes
|
||||||
/// to the writer given.
|
/// to the writer given.
|
||||||
pub fn out<W: io::Write>(&self, wtr: W) -> Out<W> {
|
pub fn out<W: io::Write>(&self, wtr: W) -> Out<W> {
|
||||||
let mut out = Out::new(wtr);
|
let mut out = Out::new(wtr);
|
||||||
if self.before_context > 0 || self.after_context > 0 {
|
if self.heading && !self.count {
|
||||||
|
out = out.file_separator(b"".to_vec());
|
||||||
|
} else if self.before_context > 0 || self.after_context > 0 {
|
||||||
out = out.file_separator(self.context_separator.clone());
|
out = out.file_separator(self.context_separator.clone());
|
||||||
}
|
}
|
||||||
out
|
out
|
||||||
@ -364,7 +428,7 @@ impl Args {
|
|||||||
/// Create a new line based searcher whose configuration is taken from the
|
/// Create a new line based searcher whose configuration is taken from the
|
||||||
/// command line. This searcher supports a dizzying array of features:
|
/// command line. This searcher supports a dizzying array of features:
|
||||||
/// inverted matching, line counting, context control and more.
|
/// inverted matching, line counting, context control and more.
|
||||||
pub fn searcher<'a, R: io::Read, W: io::Write>(
|
pub fn searcher<'a, R: io::Read, W: Send + io::Write>(
|
||||||
&self,
|
&self,
|
||||||
inp: &'a mut InputBuffer,
|
inp: &'a mut InputBuffer,
|
||||||
printer: &'a mut Printer<W>,
|
printer: &'a mut Printer<W>,
|
||||||
@ -399,16 +463,19 @@ impl Args {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Create a new recursive directory iterator at the path given.
|
/// Create a new recursive directory iterator at the path given.
|
||||||
pub fn walker(&self, path: &Path) -> walk::Iter {
|
pub fn walker(&self, path: &Path) -> Result<walk::Iter> {
|
||||||
let wd = WalkDir::new(path).follow_links(self.follow);
|
let wd = WalkDir::new(path).follow_links(self.follow);
|
||||||
let mut ig = Ignore::new();
|
let mut ig = Ignore::new();
|
||||||
ig.ignore_hidden(!self.hidden);
|
ig.ignore_hidden(!self.hidden);
|
||||||
ig.no_ignore(self.no_ignore);
|
ig.no_ignore(self.no_ignore);
|
||||||
ig.add_types(self.types.clone());
|
ig.add_types(self.types.clone());
|
||||||
|
if !self.no_ignore_parent {
|
||||||
|
try!(ig.push_parents(path));
|
||||||
|
}
|
||||||
if let Some(ref overrides) = self.glob_overrides {
|
if let Some(ref overrides) = self.glob_overrides {
|
||||||
ig.add_override(overrides.clone());
|
ig.add_override(overrides.clone());
|
||||||
}
|
}
|
||||||
walk::Iter::new(ig, wd)
|
Ok(walk::Iter::new(ig, wd))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -84,6 +84,8 @@ pub struct Gitignore {
|
|||||||
set: glob::Set,
|
set: glob::Set,
|
||||||
root: PathBuf,
|
root: PathBuf,
|
||||||
patterns: Vec<Pattern>,
|
patterns: Vec<Pattern>,
|
||||||
|
num_ignores: u64,
|
||||||
|
num_whitelist: u64,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Gitignore {
|
impl Gitignore {
|
||||||
@ -152,6 +154,16 @@ impl Gitignore {
|
|||||||
}
|
}
|
||||||
Match::None
|
Match::None
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns the total number of ignore patterns.
|
||||||
|
pub fn num_ignores(&self) -> u64 {
|
||||||
|
self.num_ignores
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the total number of whitelisted patterns.
|
||||||
|
pub fn num_whitelist(&self) -> u64 {
|
||||||
|
self.num_whitelist
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The result of a glob match.
|
/// The result of a glob match.
|
||||||
@ -238,10 +250,14 @@ impl GitignoreBuilder {
|
|||||||
///
|
///
|
||||||
/// Once a matcher is built, no new glob patterns can be added to it.
|
/// Once a matcher is built, no new glob patterns can be added to it.
|
||||||
pub fn build(self) -> Result<Gitignore, Error> {
|
pub fn build(self) -> Result<Gitignore, Error> {
|
||||||
|
let nignores = self.patterns.iter().filter(|p| !p.whitelist).count();
|
||||||
|
let nwhitelist = self.patterns.iter().filter(|p| p.whitelist).count();
|
||||||
Ok(Gitignore {
|
Ok(Gitignore {
|
||||||
set: try!(self.builder.build()),
|
set: try!(self.builder.build()),
|
||||||
root: self.root,
|
root: self.root,
|
||||||
patterns: self.patterns,
|
patterns: self.patterns,
|
||||||
|
num_ignores: nignores as u64,
|
||||||
|
num_whitelist: nwhitelist as u64,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
162
src/ignore.rs
162
src/ignore.rs
@ -15,21 +15,39 @@ of `IgnoreDir`s for use during directory traversal.
|
|||||||
|
|
||||||
use std::error::Error as StdError;
|
use std::error::Error as StdError;
|
||||||
use std::fmt;
|
use std::fmt;
|
||||||
|
use std::io;
|
||||||
use std::path::{Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
|
|
||||||
use gitignore::{self, Gitignore, GitignoreBuilder, Match};
|
use gitignore::{self, Gitignore, GitignoreBuilder, Match, Pattern};
|
||||||
use types::Types;
|
use types::Types;
|
||||||
|
|
||||||
|
const IGNORE_NAMES: &'static [&'static str] = &[
|
||||||
|
".gitignore",
|
||||||
|
".agignore",
|
||||||
|
".xrepignore",
|
||||||
|
];
|
||||||
|
|
||||||
/// Represents an error that can occur when parsing a gitignore file.
|
/// Represents an error that can occur when parsing a gitignore file.
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub enum Error {
|
pub enum Error {
|
||||||
Gitignore(gitignore::Error),
|
Gitignore(gitignore::Error),
|
||||||
|
Io {
|
||||||
|
path: PathBuf,
|
||||||
|
err: io::Error,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Error {
|
||||||
|
fn from_io<P: AsRef<Path>>(path: P, err: io::Error) -> Error {
|
||||||
|
Error::Io { path: path.as_ref().to_path_buf(), err: err }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl StdError for Error {
|
impl StdError for Error {
|
||||||
fn description(&self) -> &str {
|
fn description(&self) -> &str {
|
||||||
match *self {
|
match *self {
|
||||||
Error::Gitignore(ref err) => err.description(),
|
Error::Gitignore(ref err) => err.description(),
|
||||||
|
Error::Io { ref err, .. } => err.description(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -38,6 +56,9 @@ impl fmt::Display for Error {
|
|||||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||||
match *self {
|
match *self {
|
||||||
Error::Gitignore(ref err) => err.fmt(f),
|
Error::Gitignore(ref err) => err.fmt(f),
|
||||||
|
Error::Io { ref path, ref err } => {
|
||||||
|
write!(f, "{}: {}", path.display(), err)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -59,9 +80,9 @@ pub struct Ignore {
|
|||||||
stack: Vec<Option<IgnoreDir>>,
|
stack: Vec<Option<IgnoreDir>>,
|
||||||
/// A set of override globs that are always checked first. A match (whether
|
/// A set of override globs that are always checked first. A match (whether
|
||||||
/// it's whitelist or blacklist) trumps anything in stack.
|
/// it's whitelist or blacklist) trumps anything in stack.
|
||||||
overrides: Option<Gitignore>,
|
overrides: Overrides,
|
||||||
/// A file type matcher.
|
/// A file type matcher.
|
||||||
types: Option<Types>,
|
types: Types,
|
||||||
ignore_hidden: bool,
|
ignore_hidden: bool,
|
||||||
no_ignore: bool,
|
no_ignore: bool,
|
||||||
}
|
}
|
||||||
@ -71,8 +92,8 @@ impl Ignore {
|
|||||||
pub fn new() -> Ignore {
|
pub fn new() -> Ignore {
|
||||||
Ignore {
|
Ignore {
|
||||||
stack: vec![],
|
stack: vec![],
|
||||||
overrides: None,
|
overrides: Overrides::new(None),
|
||||||
types: None,
|
types: Types::empty(),
|
||||||
ignore_hidden: true,
|
ignore_hidden: true,
|
||||||
no_ignore: false,
|
no_ignore: false,
|
||||||
}
|
}
|
||||||
@ -92,17 +113,51 @@ impl Ignore {
|
|||||||
|
|
||||||
/// Add a set of globs that overrides all other match logic.
|
/// Add a set of globs that overrides all other match logic.
|
||||||
pub fn add_override(&mut self, gi: Gitignore) -> &mut Ignore {
|
pub fn add_override(&mut self, gi: Gitignore) -> &mut Ignore {
|
||||||
self.overrides = Some(gi);
|
self.overrides = Overrides::new(Some(gi));
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Add a file type matcher. The file type matcher has the lowest
|
/// Add a file type matcher. The file type matcher has the lowest
|
||||||
/// precedence.
|
/// precedence.
|
||||||
pub fn add_types(&mut self, types: Types) -> &mut Ignore {
|
pub fn add_types(&mut self, types: Types) -> &mut Ignore {
|
||||||
self.types = Some(types);
|
self.types = types;
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Push parent directories of `path` on to the stack.
|
||||||
|
pub fn push_parents<P: AsRef<Path>>(
|
||||||
|
&mut self,
|
||||||
|
path: P,
|
||||||
|
) -> Result<(), Error> {
|
||||||
|
let path = try!(path.as_ref().canonicalize().map_err(|err| {
|
||||||
|
Error::from_io(path.as_ref(), err)
|
||||||
|
}));
|
||||||
|
let mut path = &*path;
|
||||||
|
let mut saw_git = path.join(".git").is_dir();
|
||||||
|
let mut ignore_names = IGNORE_NAMES.to_vec();
|
||||||
|
let mut ignore_dir_results = vec![];
|
||||||
|
while let Some(parent) = path.parent() {
|
||||||
|
if self.no_ignore {
|
||||||
|
ignore_dir_results.push(Ok(None));
|
||||||
|
} else {
|
||||||
|
if saw_git {
|
||||||
|
ignore_names.retain(|&name| name != ".gitignore");
|
||||||
|
} else {
|
||||||
|
saw_git = parent.join(".git").is_dir();
|
||||||
|
}
|
||||||
|
let ignore_dir_result =
|
||||||
|
IgnoreDir::with_ignore_names(parent, ignore_names.iter());
|
||||||
|
ignore_dir_results.push(ignore_dir_result);
|
||||||
|
}
|
||||||
|
path = parent;
|
||||||
|
}
|
||||||
|
|
||||||
|
for ignore_dir_result in ignore_dir_results.into_iter().rev() {
|
||||||
|
try!(self.push_ignore_dir(ignore_dir_result));
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
/// Add a directory to the stack.
|
/// Add a directory to the stack.
|
||||||
///
|
///
|
||||||
/// Note that even if this returns an error, the directory is added to the
|
/// Note that even if this returns an error, the directory is added to the
|
||||||
@ -112,7 +167,17 @@ impl Ignore {
|
|||||||
self.stack.push(None);
|
self.stack.push(None);
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
match IgnoreDir::new(path) {
|
self.push_ignore_dir(IgnoreDir::new(path))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Pushes the result of building a directory matcher on to the stack.
|
||||||
|
///
|
||||||
|
/// If the result given contains an error, then it is returned.
|
||||||
|
pub fn push_ignore_dir(
|
||||||
|
&mut self,
|
||||||
|
result: Result<Option<IgnoreDir>, Error>,
|
||||||
|
) -> Result<(), Error> {
|
||||||
|
match result {
|
||||||
Ok(id) => {
|
Ok(id) => {
|
||||||
self.stack.push(id);
|
self.stack.push(id);
|
||||||
Ok(())
|
Ok(())
|
||||||
@ -135,12 +200,10 @@ impl Ignore {
|
|||||||
/// Returns true if and only if the given file path should be ignored.
|
/// Returns true if and only if the given file path should be ignored.
|
||||||
pub fn ignored<P: AsRef<Path>>(&self, path: P, is_dir: bool) -> bool {
|
pub fn ignored<P: AsRef<Path>>(&self, path: P, is_dir: bool) -> bool {
|
||||||
let path = path.as_ref();
|
let path = path.as_ref();
|
||||||
if let Some(ref overrides) = self.overrides {
|
let mat = self.overrides.matched(path, is_dir);
|
||||||
let mat = overrides.matched(path, is_dir).invert();
|
|
||||||
if let Some(is_ignored) = self.ignore_match(path, mat) {
|
if let Some(is_ignored) = self.ignore_match(path, mat) {
|
||||||
return is_ignored;
|
return is_ignored;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
if self.ignore_hidden && is_hidden(&path) {
|
if self.ignore_hidden && is_hidden(&path) {
|
||||||
debug!("{} ignored because it is hidden", path.display());
|
debug!("{} ignored because it is hidden", path.display());
|
||||||
return true;
|
return true;
|
||||||
@ -156,12 +219,10 @@ impl Ignore {
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if let Some(ref types) = self.types {
|
let mat = self.types.matched(path, is_dir);
|
||||||
let mat = types.matched(path, is_dir);
|
|
||||||
if let Some(is_ignored) = self.ignore_match(path, mat) {
|
if let Some(is_ignored) = self.ignore_match(path, mat) {
|
||||||
return is_ignored;
|
return is_ignored;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -210,6 +271,23 @@ impl IgnoreDir {
|
|||||||
/// If no ignore glob patterns could be found in the directory then `None`
|
/// If no ignore glob patterns could be found in the directory then `None`
|
||||||
/// is returned.
|
/// is returned.
|
||||||
pub fn new<P: AsRef<Path>>(path: P) -> Result<Option<IgnoreDir>, Error> {
|
pub fn new<P: AsRef<Path>>(path: P) -> Result<Option<IgnoreDir>, Error> {
|
||||||
|
IgnoreDir::with_ignore_names(path, IGNORE_NAMES.iter())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Create a new matcher for the given directory using only the ignore
|
||||||
|
/// patterns found in the file names given.
|
||||||
|
///
|
||||||
|
/// If no ignore glob patterns could be found in the directory then `None`
|
||||||
|
/// is returned.
|
||||||
|
///
|
||||||
|
/// Note that the order of the names given is meaningful. Names appearing
|
||||||
|
/// later in the list have precedence over names appearing earlier in the
|
||||||
|
/// list.
|
||||||
|
pub fn with_ignore_names<P: AsRef<Path>, S, I>(
|
||||||
|
path: P,
|
||||||
|
names: I,
|
||||||
|
) -> Result<Option<IgnoreDir>, Error>
|
||||||
|
where P: AsRef<Path>, S: AsRef<str>, I: Iterator<Item=S> {
|
||||||
let mut id = IgnoreDir {
|
let mut id = IgnoreDir {
|
||||||
path: path.as_ref().to_path_buf(),
|
path: path.as_ref().to_path_buf(),
|
||||||
gi: None,
|
gi: None,
|
||||||
@ -217,9 +295,9 @@ impl IgnoreDir {
|
|||||||
let mut ok = false;
|
let mut ok = false;
|
||||||
let mut builder = GitignoreBuilder::new(&id.path);
|
let mut builder = GitignoreBuilder::new(&id.path);
|
||||||
// The ordering here is important. Later globs have higher precedence.
|
// The ordering here is important. Later globs have higher precedence.
|
||||||
ok = builder.add_path(id.path.join(".gitignore")).is_ok() || ok;
|
for name in names {
|
||||||
ok = builder.add_path(id.path.join(".agignore")).is_ok() || ok;
|
ok = builder.add_path(id.path.join(name.as_ref())).is_ok() || ok;
|
||||||
ok = builder.add_path(id.path.join(".xrepignore")).is_ok() || ok;
|
}
|
||||||
if !ok {
|
if !ok {
|
||||||
Ok(None)
|
Ok(None)
|
||||||
} else {
|
} else {
|
||||||
@ -246,6 +324,56 @@ impl IgnoreDir {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Manages a set of overrides provided explicitly by the end user.
|
||||||
|
struct Overrides {
|
||||||
|
gi: Option<Gitignore>,
|
||||||
|
unmatched_pat: Pattern,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Overrides {
|
||||||
|
/// Creates a new set of overrides from the gitignore matcher provided.
|
||||||
|
/// If no matcher is provided, then the resulting overrides have no effect.
|
||||||
|
fn new(gi: Option<Gitignore>) -> Overrides {
|
||||||
|
Overrides {
|
||||||
|
gi: gi,
|
||||||
|
unmatched_pat: Pattern {
|
||||||
|
from: Path::new("<argv>").to_path_buf(),
|
||||||
|
original: "<none>".to_string(),
|
||||||
|
pat: "<none>".to_string(),
|
||||||
|
whitelist: false,
|
||||||
|
only_dir: false,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns a match for the given path against this set of overrides.
|
||||||
|
///
|
||||||
|
/// If there are no overrides, then this always returns Match::None.
|
||||||
|
///
|
||||||
|
/// If there is at least one positive override, then this never returns
|
||||||
|
/// Match::None (and interpreting non-matches as ignored) unless is_dir
|
||||||
|
/// is true.
|
||||||
|
pub fn matched<P: AsRef<Path>>(&self, path: P, is_dir: bool) -> Match {
|
||||||
|
// File types don't apply to directories.
|
||||||
|
if is_dir {
|
||||||
|
return Match::None;
|
||||||
|
}
|
||||||
|
let path = path.as_ref();
|
||||||
|
self.gi.as_ref()
|
||||||
|
.map(|gi| {
|
||||||
|
let path = &*path.to_string_lossy();
|
||||||
|
let mat = gi.matched_utf8(path, is_dir).invert();
|
||||||
|
if mat.is_none() && !is_dir {
|
||||||
|
if gi.num_ignores() > 0 {
|
||||||
|
return Match::Ignored(&self.unmatched_pat);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
mat
|
||||||
|
})
|
||||||
|
.unwrap_or(Match::None)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn is_hidden<P: AsRef<Path>>(path: P) -> bool {
|
fn is_hidden<P: AsRef<Path>>(path: P) -> bool {
|
||||||
if let Some(name) = path.as_ref().file_name() {
|
if let Some(name) = path.as_ref().file_name() {
|
||||||
name.to_str().map(|s| s.starts_with(".")).unwrap_or(false)
|
name.to_str().map(|s| s.starts_with(".")).unwrap_or(false)
|
||||||
|
24
src/main.rs
24
src/main.rs
@ -4,9 +4,11 @@ extern crate crossbeam;
|
|||||||
extern crate docopt;
|
extern crate docopt;
|
||||||
extern crate env_logger;
|
extern crate env_logger;
|
||||||
extern crate grep;
|
extern crate grep;
|
||||||
#[cfg(test)]
|
#[cfg(windows)]
|
||||||
|
extern crate kernel32;
|
||||||
#[macro_use]
|
#[macro_use]
|
||||||
extern crate lazy_static;
|
extern crate lazy_static;
|
||||||
|
extern crate libc;
|
||||||
#[macro_use]
|
#[macro_use]
|
||||||
extern crate log;
|
extern crate log;
|
||||||
extern crate memchr;
|
extern crate memchr;
|
||||||
@ -16,12 +18,15 @@ extern crate parking_lot;
|
|||||||
extern crate regex;
|
extern crate regex;
|
||||||
extern crate regex_syntax as syntax;
|
extern crate regex_syntax as syntax;
|
||||||
extern crate rustc_serialize;
|
extern crate rustc_serialize;
|
||||||
|
extern crate term;
|
||||||
extern crate thread_local;
|
extern crate thread_local;
|
||||||
extern crate walkdir;
|
extern crate walkdir;
|
||||||
|
#[cfg(windows)]
|
||||||
|
extern crate winapi;
|
||||||
|
|
||||||
use std::error::Error;
|
use std::error::Error;
|
||||||
use std::fs::File;
|
use std::fs::File;
|
||||||
use std::io::{self, Write};
|
use std::io;
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
use std::process;
|
use std::process;
|
||||||
use std::result;
|
use std::result;
|
||||||
@ -58,6 +63,7 @@ mod ignore;
|
|||||||
mod out;
|
mod out;
|
||||||
mod printer;
|
mod printer;
|
||||||
mod search;
|
mod search;
|
||||||
|
mod sys;
|
||||||
mod types;
|
mod types;
|
||||||
mod walk;
|
mod walk;
|
||||||
|
|
||||||
@ -68,7 +74,7 @@ fn main() {
|
|||||||
Ok(count) if count == 0 => process::exit(1),
|
Ok(count) if count == 0 => process::exit(1),
|
||||||
Ok(count) => process::exit(0),
|
Ok(count) => process::exit(0),
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
let _ = writeln!(&mut io::stderr(), "{}", err);
|
eprintln!("{}", err);
|
||||||
process::exit(1);
|
process::exit(1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -105,7 +111,7 @@ fn run(args: Args) -> Result<u64> {
|
|||||||
if p == Path::new("-") {
|
if p == Path::new("-") {
|
||||||
workq.push(Work::Stdin)
|
workq.push(Work::Stdin)
|
||||||
} else {
|
} else {
|
||||||
for ent in args.walker(p) {
|
for ent in try!(args.walker(p)) {
|
||||||
workq.push(Work::File(ent));
|
workq.push(Work::File(ent));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -121,14 +127,14 @@ fn run(args: Args) -> Result<u64> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn run_files(args: Args) -> Result<u64> {
|
fn run_files(args: Args) -> Result<u64> {
|
||||||
let mut printer = Printer::new(io::BufWriter::new(io::stdout()));
|
let mut printer = args.printer(io::BufWriter::new(io::stdout()));
|
||||||
let mut file_count = 0;
|
let mut file_count = 0;
|
||||||
for p in args.paths() {
|
for p in args.paths() {
|
||||||
if p == Path::new("-") {
|
if p == Path::new("-") {
|
||||||
printer.path(&Path::new("<stdin>"));
|
printer.path(&Path::new("<stdin>"));
|
||||||
file_count += 1;
|
file_count += 1;
|
||||||
} else {
|
} else {
|
||||||
for ent in args.walker(p) {
|
for ent in try!(args.walker(p)) {
|
||||||
printer.path(ent.path());
|
printer.path(ent.path());
|
||||||
file_count += 1;
|
file_count += 1;
|
||||||
}
|
}
|
||||||
@ -138,7 +144,7 @@ fn run_files(args: Args) -> Result<u64> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn run_types(args: Args) -> Result<u64> {
|
fn run_types(args: Args) -> Result<u64> {
|
||||||
let mut printer = Printer::new(io::BufWriter::new(io::stdout()));
|
let mut printer = args.printer(io::BufWriter::new(io::stdout()));
|
||||||
let mut ty_count = 0;
|
let mut ty_count = 0;
|
||||||
for def in args.type_defs() {
|
for def in args.type_defs() {
|
||||||
printer.type_def(def);
|
printer.type_def(def);
|
||||||
@ -200,7 +206,7 @@ impl Worker {
|
|||||||
self.match_count
|
self.match_count
|
||||||
}
|
}
|
||||||
|
|
||||||
fn do_work<W: io::Write>(
|
fn do_work<W: Send + io::Write>(
|
||||||
&mut self,
|
&mut self,
|
||||||
printer: &mut Printer<W>,
|
printer: &mut Printer<W>,
|
||||||
work: WorkReady,
|
work: WorkReady,
|
||||||
@ -229,7 +235,7 @@ impl Worker {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn search<R: io::Read, W: io::Write>(
|
fn search<R: io::Read, W: Send + io::Write>(
|
||||||
&mut self,
|
&mut self,
|
||||||
printer: &mut Printer<W>,
|
printer: &mut Printer<W>,
|
||||||
path: &Path,
|
path: &Path,
|
||||||
|
12
src/out.rs
12
src/out.rs
@ -9,7 +9,7 @@ use std::io::{self, Write};
|
|||||||
pub struct Out<W: io::Write> {
|
pub struct Out<W: io::Write> {
|
||||||
wtr: io::BufWriter<W>,
|
wtr: io::BufWriter<W>,
|
||||||
printed: bool,
|
printed: bool,
|
||||||
file_separator: Vec<u8>,
|
file_separator: Option<Vec<u8>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<W: io::Write> Out<W> {
|
impl<W: io::Write> Out<W> {
|
||||||
@ -18,7 +18,7 @@ impl<W: io::Write> Out<W> {
|
|||||||
Out {
|
Out {
|
||||||
wtr: io::BufWriter::new(wtr),
|
wtr: io::BufWriter::new(wtr),
|
||||||
printed: false,
|
printed: false,
|
||||||
file_separator: vec![],
|
file_separator: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -27,17 +27,19 @@ impl<W: io::Write> Out<W> {
|
|||||||
///
|
///
|
||||||
/// If sep is empty, then no file separator is printed.
|
/// If sep is empty, then no file separator is printed.
|
||||||
pub fn file_separator(mut self, sep: Vec<u8>) -> Out<W> {
|
pub fn file_separator(mut self, sep: Vec<u8>) -> Out<W> {
|
||||||
self.file_separator = sep;
|
self.file_separator = Some(sep);
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Write the search results of a single file to the underlying wtr and
|
/// Write the search results of a single file to the underlying wtr and
|
||||||
/// flush wtr.
|
/// flush wtr.
|
||||||
pub fn write(&mut self, buf: &[u8]) {
|
pub fn write(&mut self, buf: &[u8]) {
|
||||||
if self.printed && !self.file_separator.is_empty() {
|
if let Some(ref sep) = self.file_separator {
|
||||||
let _ = self.wtr.write_all(&self.file_separator);
|
if self.printed {
|
||||||
|
let _ = self.wtr.write_all(sep);
|
||||||
let _ = self.wtr.write_all(b"\n");
|
let _ = self.wtr.write_all(b"\n");
|
||||||
}
|
}
|
||||||
|
}
|
||||||
let _ = self.wtr.write_all(buf);
|
let _ = self.wtr.write_all(buf);
|
||||||
let _ = self.wtr.flush();
|
let _ = self.wtr.flush();
|
||||||
self.printed = true;
|
self.printed = true;
|
||||||
|
265
src/printer.rs
265
src/printer.rs
@ -1,8 +1,16 @@
|
|||||||
use std::io;
|
use std::io::{self, Write};
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
use regex::bytes::Regex;
|
||||||
|
use term::{self, Terminal};
|
||||||
|
use term::color::*;
|
||||||
|
use term::terminfo::TermInfo;
|
||||||
|
|
||||||
use types::FileTypeDef;
|
use types::FileTypeDef;
|
||||||
|
|
||||||
|
use self::Writer::*;
|
||||||
|
|
||||||
/// Printer encapsulates all output logic for searching.
|
/// Printer encapsulates all output logic for searching.
|
||||||
///
|
///
|
||||||
/// Note that we currently ignore all write errors. It's probably worthwhile
|
/// Note that we currently ignore all write errors. It's probably worthwhile
|
||||||
@ -10,7 +18,7 @@ use types::FileTypeDef;
|
|||||||
/// writes to memory, neither of which commonly fail.
|
/// writes to memory, neither of which commonly fail.
|
||||||
pub struct Printer<W> {
|
pub struct Printer<W> {
|
||||||
/// The underlying writer.
|
/// The underlying writer.
|
||||||
wtr: W,
|
wtr: Writer<W>,
|
||||||
/// Whether anything has been printed to wtr yet.
|
/// Whether anything has been printed to wtr yet.
|
||||||
has_printed: bool,
|
has_printed: bool,
|
||||||
/// The string to use to separate non-contiguous runs of context lines.
|
/// The string to use to separate non-contiguous runs of context lines.
|
||||||
@ -19,21 +27,31 @@ pub struct Printer<W> {
|
|||||||
/// printed via the match directly, but occasionally we need to insert them
|
/// printed via the match directly, but occasionally we need to insert them
|
||||||
/// ourselves (for example, to print a context separator).
|
/// ourselves (for example, to print a context separator).
|
||||||
eol: u8,
|
eol: u8,
|
||||||
|
/// Whether to show file name as a heading or not.
|
||||||
|
///
|
||||||
|
/// N.B. If with_filename is false, then this setting has no effect.
|
||||||
|
heading: bool,
|
||||||
/// Whether to suppress all output.
|
/// Whether to suppress all output.
|
||||||
quiet: bool,
|
quiet: bool,
|
||||||
|
/// A string to use as a replacement of each match in a matching line.
|
||||||
|
replace: Option<Vec<u8>>,
|
||||||
/// Whether to prefix each match with the corresponding file name.
|
/// Whether to prefix each match with the corresponding file name.
|
||||||
with_filename: bool,
|
with_filename: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<W: io::Write> Printer<W> {
|
impl<W: Send + io::Write> Printer<W> {
|
||||||
/// Create a new printer that writes to wtr.
|
/// Create a new printer that writes to wtr.
|
||||||
pub fn new(wtr: W) -> Printer<W> {
|
///
|
||||||
|
/// `color` should be true if the printer should try to use coloring.
|
||||||
|
pub fn new(wtr: W, color: bool) -> Printer<W> {
|
||||||
Printer {
|
Printer {
|
||||||
wtr: wtr,
|
wtr: Writer::new(wtr, color),
|
||||||
has_printed: false,
|
has_printed: false,
|
||||||
context_separator: "--".to_string().into_bytes(),
|
context_separator: "--".to_string().into_bytes(),
|
||||||
eol: b'\n',
|
eol: b'\n',
|
||||||
|
heading: false,
|
||||||
quiet: false,
|
quiet: false,
|
||||||
|
replace: None,
|
||||||
with_filename: false,
|
with_filename: false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -50,12 +68,30 @@ impl<W: io::Write> Printer<W> {
|
|||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Whether to show file name as a heading or not.
|
||||||
|
///
|
||||||
|
/// N.B. If with_filename is false, then this setting has no effect.
|
||||||
|
pub fn heading(mut self, yes: bool) -> Printer<W> {
|
||||||
|
self.heading = yes;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
/// When set, all output is suppressed.
|
/// When set, all output is suppressed.
|
||||||
pub fn quiet(mut self, yes: bool) -> Printer<W> {
|
pub fn quiet(mut self, yes: bool) -> Printer<W> {
|
||||||
self.quiet = yes;
|
self.quiet = yes;
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Replace every match in each matching line with the replacement string
|
||||||
|
/// given.
|
||||||
|
///
|
||||||
|
/// The replacement string syntax is documented here:
|
||||||
|
/// https://doc.rust-lang.org/regex/regex/bytes/struct.Captures.html#method.expand
|
||||||
|
pub fn replace(mut self, replacement: Vec<u8>) -> Printer<W> {
|
||||||
|
self.replace = Some(replacement);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
/// When set, each match is prefixed with the file name that it came from.
|
/// When set, each match is prefixed with the file name that it came from.
|
||||||
pub fn with_filename(mut self, yes: bool) -> Printer<W> {
|
pub fn with_filename(mut self, yes: bool) -> Printer<W> {
|
||||||
self.with_filename = yes;
|
self.with_filename = yes;
|
||||||
@ -70,7 +106,7 @@ impl<W: io::Write> Printer<W> {
|
|||||||
/// Flushes the underlying writer and returns it.
|
/// Flushes the underlying writer and returns it.
|
||||||
pub fn into_inner(mut self) -> W {
|
pub fn into_inner(mut self) -> W {
|
||||||
let _ = self.wtr.flush();
|
let _ = self.wtr.flush();
|
||||||
self.wtr
|
self.wtr.into_inner()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Prints a type definition.
|
/// Prints a type definition.
|
||||||
@ -120,26 +156,51 @@ impl<W: io::Write> Printer<W> {
|
|||||||
|
|
||||||
pub fn matched<P: AsRef<Path>>(
|
pub fn matched<P: AsRef<Path>>(
|
||||||
&mut self,
|
&mut self,
|
||||||
|
re: &Regex,
|
||||||
path: P,
|
path: P,
|
||||||
buf: &[u8],
|
buf: &[u8],
|
||||||
start: usize,
|
start: usize,
|
||||||
end: usize,
|
end: usize,
|
||||||
line_number: Option<u64>,
|
line_number: Option<u64>,
|
||||||
) {
|
) {
|
||||||
if self.with_filename {
|
if self.heading && self.with_filename && !self.has_printed {
|
||||||
|
self.write_heading(path.as_ref());
|
||||||
|
} else if !self.heading && self.with_filename {
|
||||||
self.write(path.as_ref().to_string_lossy().as_bytes());
|
self.write(path.as_ref().to_string_lossy().as_bytes());
|
||||||
self.write(b":");
|
self.write(b":");
|
||||||
}
|
}
|
||||||
if let Some(line_number) = line_number {
|
if let Some(line_number) = line_number {
|
||||||
self.write(line_number.to_string().as_bytes());
|
self.line_number(line_number, b':');
|
||||||
self.write(b":");
|
}
|
||||||
|
if self.replace.is_some() {
|
||||||
|
let line = re.replace_all(
|
||||||
|
&buf[start..end], &**self.replace.as_ref().unwrap());
|
||||||
|
self.write(&line);
|
||||||
|
} else {
|
||||||
|
self.write_match(re, &buf[start..end]);
|
||||||
}
|
}
|
||||||
self.write(&buf[start..end]);
|
|
||||||
if buf[start..end].last() != Some(&self.eol) {
|
if buf[start..end].last() != Some(&self.eol) {
|
||||||
self.write_eol();
|
self.write_eol();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn write_match(&mut self, re: &Regex, buf: &[u8]) {
|
||||||
|
if !self.wtr.is_color() {
|
||||||
|
self.write(buf);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let mut last_written = 0;
|
||||||
|
for (s, e) in re.find_iter(buf) {
|
||||||
|
self.write(&buf[last_written..s]);
|
||||||
|
let _ = self.wtr.fg(BRIGHT_RED);
|
||||||
|
let _ = self.wtr.attr(term::Attr::Bold);
|
||||||
|
self.write(&buf[s..e]);
|
||||||
|
let _ = self.wtr.reset();
|
||||||
|
last_written = e;
|
||||||
|
}
|
||||||
|
self.write(&buf[last_written..]);
|
||||||
|
}
|
||||||
|
|
||||||
pub fn context<P: AsRef<Path>>(
|
pub fn context<P: AsRef<Path>>(
|
||||||
&mut self,
|
&mut self,
|
||||||
path: P,
|
path: P,
|
||||||
@ -148,13 +209,18 @@ impl<W: io::Write> Printer<W> {
|
|||||||
end: usize,
|
end: usize,
|
||||||
line_number: Option<u64>,
|
line_number: Option<u64>,
|
||||||
) {
|
) {
|
||||||
if self.with_filename {
|
if self.heading && self.with_filename && !self.has_printed {
|
||||||
|
self.write_heading(path.as_ref());
|
||||||
|
} else if !self.heading && self.with_filename {
|
||||||
|
self.write(path.as_ref().to_string_lossy().as_bytes());
|
||||||
|
self.write(b":");
|
||||||
|
}
|
||||||
|
if !self.heading && self.with_filename {
|
||||||
self.write(path.as_ref().to_string_lossy().as_bytes());
|
self.write(path.as_ref().to_string_lossy().as_bytes());
|
||||||
self.write(b"-");
|
self.write(b"-");
|
||||||
}
|
}
|
||||||
if let Some(line_number) = line_number {
|
if let Some(line_number) = line_number {
|
||||||
self.write(line_number.to_string().as_bytes());
|
self.line_number(line_number, b'-');
|
||||||
self.write(b"-");
|
|
||||||
}
|
}
|
||||||
self.write(&buf[start..end]);
|
self.write(&buf[start..end]);
|
||||||
if buf[start..end].last() != Some(&self.eol) {
|
if buf[start..end].last() != Some(&self.eol) {
|
||||||
@ -162,6 +228,28 @@ impl<W: io::Write> Printer<W> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn write_heading<P: AsRef<Path>>(&mut self, path: P) {
|
||||||
|
if self.wtr.is_color() {
|
||||||
|
let _ = self.wtr.fg(GREEN);
|
||||||
|
}
|
||||||
|
self.write(path.as_ref().to_string_lossy().as_bytes());
|
||||||
|
self.write_eol();
|
||||||
|
if self.wtr.is_color() {
|
||||||
|
let _ = self.wtr.reset();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn line_number(&mut self, n: u64, sep: u8) {
|
||||||
|
if self.wtr.is_color() {
|
||||||
|
let _ = self.wtr.fg(YELLOW);
|
||||||
|
}
|
||||||
|
self.write(n.to_string().as_bytes());
|
||||||
|
if self.wtr.is_color() {
|
||||||
|
let _ = self.wtr.reset();
|
||||||
|
}
|
||||||
|
self.write(&[sep]);
|
||||||
|
}
|
||||||
|
|
||||||
fn write(&mut self, buf: &[u8]) {
|
fn write(&mut self, buf: &[u8]) {
|
||||||
if self.quiet {
|
if self.quiet {
|
||||||
return;
|
return;
|
||||||
@ -175,3 +263,154 @@ impl<W: io::Write> Printer<W> {
|
|||||||
self.write(&[eol]);
|
self.write(&[eol]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
enum Writer<W> {
|
||||||
|
Colored(term::TerminfoTerminal<W>),
|
||||||
|
NoColor(W),
|
||||||
|
}
|
||||||
|
|
||||||
|
lazy_static! {
|
||||||
|
static ref TERMINFO: Option<Arc<TermInfo>> = {
|
||||||
|
match term::terminfo::TermInfo::from_env() {
|
||||||
|
Ok(info) => Some(Arc::new(info)),
|
||||||
|
Err(err) => {
|
||||||
|
debug!("error loading terminfo for coloring: {}", err);
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<W: Send + io::Write> Writer<W> {
|
||||||
|
fn new(wtr: W, color: bool) -> Writer<W> {
|
||||||
|
// If we want color, build a TerminfoTerminal and see if the current
|
||||||
|
// environment supports coloring. If not, bail with NoColor. To avoid
|
||||||
|
// losing our writer (ownership), do this the long way.
|
||||||
|
if !color || TERMINFO.is_none() {
|
||||||
|
return NoColor(wtr);
|
||||||
|
}
|
||||||
|
// Why doesn't TERMINFO.as_ref().unwrap().clone() work?
|
||||||
|
let info = TERMINFO.clone().unwrap();
|
||||||
|
// names: TERMINFO.as_ref().unwrap().names.clone(),
|
||||||
|
// bools: TERMINFO.as_ref().unwrap().bools.clone(),
|
||||||
|
// numbers: TERMINFO.as_ref().unwrap().numbers.clone(),
|
||||||
|
// strings: TERMINFO.as_ref().unwrap().strings.clone(),
|
||||||
|
// };
|
||||||
|
let tt = term::TerminfoTerminal::new_with_terminfo(wtr, info);
|
||||||
|
if !tt.supports_color() {
|
||||||
|
debug!("environment doesn't support coloring");
|
||||||
|
return NoColor(tt.into_inner());
|
||||||
|
}
|
||||||
|
Colored(tt)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn is_color(&self) -> bool {
|
||||||
|
match *self {
|
||||||
|
Colored(_) => true,
|
||||||
|
NoColor(_) => false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn map_result<F>(
|
||||||
|
&mut self,
|
||||||
|
mut f: F,
|
||||||
|
) -> term::Result<()>
|
||||||
|
where F: FnMut(&mut term::TerminfoTerminal<W>) -> term::Result<()> {
|
||||||
|
match *self {
|
||||||
|
Colored(ref mut w) => f(w),
|
||||||
|
NoColor(_) => Err(term::Error::NotSupported),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn map_bool<F>(
|
||||||
|
&self,
|
||||||
|
mut f: F,
|
||||||
|
) -> bool
|
||||||
|
where F: FnMut(&term::TerminfoTerminal<W>) -> bool {
|
||||||
|
match *self {
|
||||||
|
Colored(ref w) => f(w),
|
||||||
|
NoColor(_) => false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<W: Send + io::Write> io::Write for Writer<W> {
|
||||||
|
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
|
||||||
|
match *self {
|
||||||
|
Colored(ref mut w) => w.write(buf),
|
||||||
|
NoColor(ref mut w) => w.write(buf),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn flush(&mut self) -> io::Result<()> {
|
||||||
|
match *self {
|
||||||
|
Colored(ref mut w) => w.flush(),
|
||||||
|
NoColor(ref mut w) => w.flush(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<W: Send + io::Write> term::Terminal for Writer<W> {
|
||||||
|
type Output = W;
|
||||||
|
|
||||||
|
fn fg(&mut self, fg: term::color::Color) -> term::Result<()> {
|
||||||
|
self.map_result(|w| w.fg(fg))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn bg(&mut self, bg: term::color::Color) -> term::Result<()> {
|
||||||
|
self.map_result(|w| w.bg(bg))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn attr(&mut self, attr: term::Attr) -> term::Result<()> {
|
||||||
|
self.map_result(|w| w.attr(attr))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn supports_attr(&self, attr: term::Attr) -> bool {
|
||||||
|
self.map_bool(|w| w.supports_attr(attr))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn reset(&mut self) -> term::Result<()> {
|
||||||
|
self.map_result(|w| w.reset())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn supports_reset(&self) -> bool {
|
||||||
|
self.map_bool(|w| w.supports_reset())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn supports_color(&self) -> bool {
|
||||||
|
self.map_bool(|w| w.supports_color())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn cursor_up(&mut self) -> term::Result<()> {
|
||||||
|
self.map_result(|w| w.cursor_up())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn delete_line(&mut self) -> term::Result<()> {
|
||||||
|
self.map_result(|w| w.delete_line())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn carriage_return(&mut self) -> term::Result<()> {
|
||||||
|
self.map_result(|w| w.carriage_return())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_ref(&self) -> &W {
|
||||||
|
match *self {
|
||||||
|
Colored(ref w) => w.get_ref(),
|
||||||
|
NoColor(ref w) => w,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_mut(&mut self) -> &mut W {
|
||||||
|
match *self {
|
||||||
|
Colored(ref mut w) => w.get_mut(),
|
||||||
|
NoColor(ref mut w) => w,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn into_inner(self) -> W {
|
||||||
|
match self {
|
||||||
|
Colored(w) => w.into_inner(),
|
||||||
|
NoColor(w) => w,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -98,7 +98,7 @@ impl Default for Options {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a, R: io::Read, W: io::Write> Searcher<'a, R, W> {
|
impl<'a, R: io::Read, W: Send + io::Write> Searcher<'a, R, W> {
|
||||||
/// Create a new searcher.
|
/// Create a new searcher.
|
||||||
///
|
///
|
||||||
/// `inp` is a reusable input buffer that is used as scratch space by this
|
/// `inp` is a reusable input buffer that is used as scratch space by this
|
||||||
@ -329,7 +329,8 @@ impl<'a, R: io::Read, W: io::Write> Searcher<'a, R, W> {
|
|||||||
self.count_lines(start);
|
self.count_lines(start);
|
||||||
self.add_line(end);
|
self.add_line(end);
|
||||||
self.printer.matched(
|
self.printer.matched(
|
||||||
&self.path, &self.inp.buf, start, end, self.line_count);
|
&self.grep.regex(), &self.path,
|
||||||
|
&self.inp.buf, start, end, self.line_count);
|
||||||
self.last_printed = end;
|
self.last_printed = end;
|
||||||
self.after_context_remaining = self.opts.after_context;
|
self.after_context_remaining = self.opts.after_context;
|
||||||
}
|
}
|
||||||
@ -739,7 +740,7 @@ fn main() {
|
|||||||
mut map: F,
|
mut map: F,
|
||||||
) -> (u64, String) {
|
) -> (u64, String) {
|
||||||
let mut inp = InputBuffer::with_capacity(1);
|
let mut inp = InputBuffer::with_capacity(1);
|
||||||
let mut pp = Printer::new(vec![]).with_filename(true);
|
let mut pp = Printer::new(vec![], false).with_filename(true);
|
||||||
let grep = GrepBuilder::new(pat).build().unwrap();
|
let grep = GrepBuilder::new(pat).build().unwrap();
|
||||||
let count = {
|
let count = {
|
||||||
let searcher = Searcher::new(
|
let searcher = Searcher::new(
|
||||||
@ -755,7 +756,7 @@ fn main() {
|
|||||||
mut map: F,
|
mut map: F,
|
||||||
) -> (u64, String) {
|
) -> (u64, String) {
|
||||||
let mut inp = InputBuffer::with_capacity(4096);
|
let mut inp = InputBuffer::with_capacity(4096);
|
||||||
let mut pp = Printer::new(vec![]).with_filename(true);
|
let mut pp = Printer::new(vec![], false).with_filename(true);
|
||||||
let grep = GrepBuilder::new(pat).build().unwrap();
|
let grep = GrepBuilder::new(pat).build().unwrap();
|
||||||
let count = {
|
let count = {
|
||||||
let searcher = Searcher::new(
|
let searcher = Searcher::new(
|
||||||
|
139
src/sys.rs
Normal file
139
src/sys.rs
Normal file
@ -0,0 +1,139 @@
|
|||||||
|
/*!
|
||||||
|
This io module contains various platform specific functions for detecting
|
||||||
|
how xrep is being used. e.g., Is stdin being piped into it? Is stdout being
|
||||||
|
redirected to a file? etc... We use this information to tweak various default
|
||||||
|
configuration parameters such as colors and match formatting.
|
||||||
|
*/
|
||||||
|
|
||||||
|
use std::fs::{File, Metadata};
|
||||||
|
use std::io;
|
||||||
|
|
||||||
|
use libc;
|
||||||
|
|
||||||
|
#[cfg(unix)]
|
||||||
|
pub fn stdin_is_atty() -> bool {
|
||||||
|
0 < unsafe { libc::isatty(libc::STDIN_FILENO) }
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(unix)]
|
||||||
|
pub fn stdout_is_atty() -> bool {
|
||||||
|
0 < unsafe { libc::isatty(libc::STDOUT_FILENO) }
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(windows)]
|
||||||
|
pub fn stdin_is_atty() -> bool {
|
||||||
|
use kernel32;
|
||||||
|
use winapi;
|
||||||
|
|
||||||
|
unsafe {
|
||||||
|
let fd = winapi::winbase::STD_INPUT_HANDLE;
|
||||||
|
let mut out = 0;
|
||||||
|
kernel32::GetConsoleMode(kernel32::GetStdHandle(fd), &mut out) != 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(windows)]
|
||||||
|
pub fn stdout_is_atty() -> bool {
|
||||||
|
use kernel32;
|
||||||
|
use winapi;
|
||||||
|
|
||||||
|
unsafe {
|
||||||
|
let fd = winapi::winbase::STD_OUTPUT_HANDLE;
|
||||||
|
let mut out = 0;
|
||||||
|
kernel32::GetConsoleMode(handle, &mut out) != 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Probably everything below isn't actually needed. ---AG
|
||||||
|
|
||||||
|
#[cfg(unix)]
|
||||||
|
pub fn metadata(fd: libc::c_int) -> Result<Metadata, io::Error> {
|
||||||
|
use std::os::unix::io::{FromRawFd, IntoRawFd};
|
||||||
|
|
||||||
|
let f = unsafe { File::from_raw_fd(fd) };
|
||||||
|
let md = f.metadata();
|
||||||
|
// Be careful to transfer ownership back to a simple descriptor. Dropping
|
||||||
|
// the File itself would close the descriptor, which would be quite bad!
|
||||||
|
drop(f.into_raw_fd());
|
||||||
|
md
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(unix)]
|
||||||
|
pub fn stdin_is_file() -> bool {
|
||||||
|
metadata(libc::STDIN_FILENO)
|
||||||
|
.map(|md| md.file_type().is_file())
|
||||||
|
.unwrap_or(false)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(unix)]
|
||||||
|
pub fn stdout_is_file() -> bool {
|
||||||
|
metadata(libc::STDOUT_FILENO)
|
||||||
|
.map(|md| md.file_type().is_file())
|
||||||
|
.unwrap_or(false)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(unix)]
|
||||||
|
pub fn stdin_is_char_device() -> bool {
|
||||||
|
use std::os::unix::fs::FileTypeExt;
|
||||||
|
|
||||||
|
metadata(libc::STDIN_FILENO)
|
||||||
|
.map(|md| md.file_type().is_char_device())
|
||||||
|
.unwrap_or(false)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(unix)]
|
||||||
|
pub fn stdout_is_char_device() -> bool {
|
||||||
|
use std::os::unix::fs::FileTypeExt;
|
||||||
|
|
||||||
|
metadata(libc::STDOUT_FILENO)
|
||||||
|
.map(|md| md.file_type().is_char_device())
|
||||||
|
.unwrap_or(false)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(unix)]
|
||||||
|
pub fn stdin_is_fifo() -> bool {
|
||||||
|
use std::os::unix::fs::FileTypeExt;
|
||||||
|
|
||||||
|
metadata(libc::STDIN_FILENO)
|
||||||
|
.map(|md| md.file_type().is_fifo())
|
||||||
|
.unwrap_or(false)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(unix)]
|
||||||
|
pub fn stdout_is_fifo() -> bool {
|
||||||
|
use std::os::unix::fs::FileTypeExt;
|
||||||
|
|
||||||
|
metadata(libc::STDOUT_FILENO)
|
||||||
|
.map(|md| md.file_type().is_fifo())
|
||||||
|
.unwrap_or(false)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(windows)]
|
||||||
|
pub fn stdin_is_file() -> bool {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(windows)]
|
||||||
|
pub fn stdout_is_file() -> bool {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(windows)]
|
||||||
|
pub fn stdin_is_char_device() -> bool {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(windows)]
|
||||||
|
pub fn stdout_is_char_device() -> bool {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(windows)]
|
||||||
|
pub fn stdin_is_fifo() -> bool {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(windows)]
|
||||||
|
pub fn stdout_is_fifo() -> bool {
|
||||||
|
false
|
||||||
|
}
|
@ -163,6 +163,11 @@ impl Types {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Creates a new file type matcher that never matches.
|
||||||
|
pub fn empty() -> Types {
|
||||||
|
Types::new(None, false)
|
||||||
|
}
|
||||||
|
|
||||||
/// Returns a match for the given path against this file type matcher.
|
/// Returns a match for the given path against this file type matcher.
|
||||||
///
|
///
|
||||||
/// The path is considered whitelisted if it matches a selected file type.
|
/// The path is considered whitelisted if it matches a selected file type.
|
||||||
|
Loading…
x
Reference in New Issue
Block a user