2016-08-28 07:37:12 +02:00
|
|
|
use std::path::Path;
|
2016-09-05 23:36:41 +02:00
|
|
|
|
|
|
|
use regex::bytes::Regex;
|
2016-09-09 03:46:14 +02:00
|
|
|
use term::{Attr, Terminal};
|
|
|
|
use term::color;
|
2016-08-28 07:37:12 +02:00
|
|
|
|
2016-09-25 01:18:48 +02:00
|
|
|
use pathutil::strip_prefix;
|
2016-09-05 06:52:23 +02:00
|
|
|
use types::FileTypeDef;
|
2016-08-30 04:44:15 +02:00
|
|
|
|
2016-09-05 06:52:23 +02:00
|
|
|
/// Printer encapsulates all output logic for searching.
|
|
|
|
///
|
|
|
|
/// Note that we currently ignore all write errors. It's probably worthwhile
|
|
|
|
/// to fix this, but printers are only ever used for writes to stdout or
|
|
|
|
/// writes to memory, neither of which commonly fail.
|
2016-08-28 07:37:12 +02:00
|
|
|
pub struct Printer<W> {
|
2016-09-05 06:52:23 +02:00
|
|
|
/// The underlying writer.
|
2016-09-09 03:46:14 +02:00
|
|
|
wtr: W,
|
2016-09-05 06:52:23 +02:00
|
|
|
/// Whether anything has been printed to wtr yet.
|
2016-09-02 03:56:23 +02:00
|
|
|
has_printed: bool,
|
2016-09-07 01:50:27 +02:00
|
|
|
/// Whether to show column numbers for the first match or not.
|
|
|
|
column: bool,
|
2016-09-05 06:52:23 +02:00
|
|
|
/// The string to use to separate non-contiguous runs of context lines.
|
|
|
|
context_separator: Vec<u8>,
|
|
|
|
/// The end-of-line terminator used by the printer. In general, eols are
|
|
|
|
/// printed via the match directly, but occasionally we need to insert them
|
|
|
|
/// ourselves (for example, to print a context separator).
|
|
|
|
eol: u8,
|
2016-09-05 23:36:41 +02:00
|
|
|
/// 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,
|
2016-09-23 03:32:38 +02:00
|
|
|
/// Whether to show every match on its own line.
|
|
|
|
line_per_match: bool,
|
2016-09-05 06:52:23 +02:00
|
|
|
/// Whether to suppress all output.
|
|
|
|
quiet: bool,
|
2016-09-05 23:36:41 +02:00
|
|
|
/// A string to use as a replacement of each match in a matching line.
|
|
|
|
replace: Option<Vec<u8>>,
|
2016-09-05 06:52:23 +02:00
|
|
|
/// Whether to prefix each match with the corresponding file name.
|
|
|
|
with_filename: bool,
|
2016-08-28 07:37:12 +02:00
|
|
|
}
|
|
|
|
|
2016-09-14 03:11:46 +02:00
|
|
|
impl<W: Terminal + Send> Printer<W> {
|
2016-09-05 06:52:23 +02:00
|
|
|
/// Create a new printer that writes to wtr.
|
2016-09-09 03:46:14 +02:00
|
|
|
pub fn new(wtr: W) -> Printer<W> {
|
2016-08-28 07:37:12 +02:00
|
|
|
Printer {
|
2016-09-09 03:46:14 +02:00
|
|
|
wtr: wtr,
|
2016-09-02 03:56:23 +02:00
|
|
|
has_printed: false,
|
2016-09-07 01:50:27 +02:00
|
|
|
column: false,
|
2016-09-05 06:52:23 +02:00
|
|
|
context_separator: "--".to_string().into_bytes(),
|
|
|
|
eol: b'\n',
|
2016-09-05 23:36:41 +02:00
|
|
|
heading: false,
|
2016-09-23 03:32:38 +02:00
|
|
|
line_per_match: false,
|
2016-09-05 06:52:23 +02:00
|
|
|
quiet: false,
|
2016-09-05 23:36:41 +02:00
|
|
|
replace: None,
|
2016-09-05 06:52:23 +02:00
|
|
|
with_filename: false,
|
2016-08-28 07:37:12 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-09-07 01:50:27 +02:00
|
|
|
/// When set, column numbers will be printed for the first match on each
|
|
|
|
/// line.
|
|
|
|
pub fn column(mut self, yes: bool) -> Printer<W> {
|
|
|
|
self.column = yes;
|
|
|
|
self
|
|
|
|
}
|
|
|
|
|
2016-09-05 06:52:23 +02:00
|
|
|
/// Set the context separator. The default is `--`.
|
|
|
|
pub fn context_separator(mut self, sep: Vec<u8>) -> Printer<W> {
|
|
|
|
self.context_separator = sep;
|
|
|
|
self
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Set the end-of-line terminator. The default is `\n`.
|
|
|
|
pub fn eol(mut self, eol: u8) -> Printer<W> {
|
|
|
|
self.eol = eol;
|
|
|
|
self
|
|
|
|
}
|
|
|
|
|
2016-09-05 23:36:41 +02:00
|
|
|
/// 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
|
|
|
|
}
|
|
|
|
|
2016-09-23 03:32:38 +02:00
|
|
|
/// Whether to show every match on its own line.
|
|
|
|
pub fn line_per_match(mut self, yes: bool) -> Printer<W> {
|
|
|
|
self.line_per_match = yes;
|
|
|
|
self
|
|
|
|
}
|
|
|
|
|
2016-09-05 06:52:23 +02:00
|
|
|
/// When set, all output is suppressed.
|
|
|
|
pub fn quiet(mut self, yes: bool) -> Printer<W> {
|
|
|
|
self.quiet = yes;
|
|
|
|
self
|
|
|
|
}
|
|
|
|
|
2016-09-05 23:36:41 +02:00
|
|
|
/// 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
|
|
|
|
}
|
|
|
|
|
2016-09-05 06:52:23 +02:00
|
|
|
/// When set, each match is prefixed with the file name that it came from.
|
|
|
|
pub fn with_filename(mut self, yes: bool) -> Printer<W> {
|
|
|
|
self.with_filename = yes;
|
|
|
|
self
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Returns true if and only if something has been printed.
|
2016-09-02 03:56:23 +02:00
|
|
|
pub fn has_printed(&self) -> bool {
|
|
|
|
self.has_printed
|
|
|
|
}
|
|
|
|
|
2016-09-25 21:01:27 +02:00
|
|
|
/// Returns true if the printer has been configured to be quiet.
|
|
|
|
pub fn is_quiet(&self) -> bool {
|
|
|
|
self.quiet
|
|
|
|
}
|
|
|
|
|
2016-09-05 06:52:23 +02:00
|
|
|
/// Flushes the underlying writer and returns it.
|
2016-09-09 03:46:14 +02:00
|
|
|
pub fn into_inner(mut self) -> W {
|
2016-09-05 06:52:23 +02:00
|
|
|
let _ = self.wtr.flush();
|
2016-09-08 03:54:28 +02:00
|
|
|
self.wtr
|
2016-08-28 07:37:12 +02:00
|
|
|
}
|
|
|
|
|
2016-09-05 06:52:23 +02:00
|
|
|
/// Prints a type definition.
|
|
|
|
pub fn type_def(&mut self, def: &FileTypeDef) {
|
|
|
|
self.write(def.name().as_bytes());
|
|
|
|
self.write(b": ");
|
|
|
|
let mut first = true;
|
|
|
|
for pat in def.patterns() {
|
|
|
|
if !first {
|
|
|
|
self.write(b", ");
|
|
|
|
}
|
|
|
|
self.write(pat.as_bytes());
|
|
|
|
first = false;
|
|
|
|
}
|
|
|
|
self.write_eol();
|
2016-08-28 07:37:12 +02:00
|
|
|
}
|
|
|
|
|
2016-09-05 06:52:23 +02:00
|
|
|
/// Prints the given path.
|
|
|
|
pub fn path<P: AsRef<Path>>(&mut self, path: P) {
|
2016-09-25 01:18:48 +02:00
|
|
|
let path = strip_prefix("./", path.as_ref()).unwrap_or(path.as_ref());
|
2016-09-26 03:23:26 +02:00
|
|
|
self.write_path(path);
|
2016-09-05 06:52:23 +02:00
|
|
|
self.write_eol();
|
2016-08-29 02:18:34 +02:00
|
|
|
}
|
|
|
|
|
2016-09-05 06:52:23 +02:00
|
|
|
/// Prints the given path and a count of the number of matches found.
|
|
|
|
pub fn path_count<P: AsRef<Path>>(&mut self, path: P, count: u64) {
|
|
|
|
if self.with_filename {
|
2016-09-26 03:23:26 +02:00
|
|
|
self.write_path(path);
|
2016-09-05 06:52:23 +02:00
|
|
|
self.write(b":");
|
|
|
|
}
|
|
|
|
self.write(count.to_string().as_bytes());
|
|
|
|
self.write_eol();
|
2016-08-28 07:37:12 +02:00
|
|
|
}
|
|
|
|
|
2016-09-05 06:52:23 +02:00
|
|
|
/// Prints the context separator.
|
|
|
|
pub fn context_separate(&mut self) {
|
|
|
|
// N.B. We can't use `write` here because of borrowing restrictions.
|
|
|
|
if self.quiet {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
if self.context_separator.is_empty() {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
self.has_printed = true;
|
|
|
|
let _ = self.wtr.write_all(&self.context_separator);
|
|
|
|
let _ = self.wtr.write_all(&[self.eol]);
|
2016-09-02 03:56:23 +02:00
|
|
|
}
|
|
|
|
|
2016-08-28 07:37:12 +02:00
|
|
|
pub fn matched<P: AsRef<Path>>(
|
|
|
|
&mut self,
|
2016-09-05 23:36:41 +02:00
|
|
|
re: &Regex,
|
2016-08-28 07:37:12 +02:00
|
|
|
path: P,
|
|
|
|
buf: &[u8],
|
2016-08-30 04:44:15 +02:00
|
|
|
start: usize,
|
|
|
|
end: usize,
|
|
|
|
line_number: Option<u64>,
|
2016-09-23 03:32:38 +02:00
|
|
|
) {
|
|
|
|
if !self.line_per_match {
|
|
|
|
let column =
|
|
|
|
if self.column {
|
|
|
|
Some(re.find(&buf[start..end])
|
2016-09-26 03:23:26 +02:00
|
|
|
.map(|(s, _)| s + 1).unwrap_or(0) as u64)
|
2016-09-23 03:32:38 +02:00
|
|
|
} else {
|
|
|
|
None
|
|
|
|
};
|
|
|
|
return self.write_match(
|
|
|
|
re, path, buf, start, end, line_number, column);
|
|
|
|
}
|
|
|
|
for (s, _) in re.find_iter(&buf[start..end]) {
|
|
|
|
let column = if self.column { Some(s as u64) } else { None };
|
|
|
|
self.write_match(
|
|
|
|
re, path.as_ref(), buf, start, end, line_number, column);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
fn write_match<P: AsRef<Path>>(
|
|
|
|
&mut self,
|
|
|
|
re: &Regex,
|
|
|
|
path: P,
|
|
|
|
buf: &[u8],
|
|
|
|
start: usize,
|
|
|
|
end: usize,
|
|
|
|
line_number: Option<u64>,
|
|
|
|
column: Option<u64>,
|
2016-08-28 07:37:12 +02:00
|
|
|
) {
|
2016-09-05 23:36:41 +02:00
|
|
|
if self.heading && self.with_filename && !self.has_printed {
|
|
|
|
self.write_heading(path.as_ref());
|
|
|
|
} else if !self.heading && self.with_filename {
|
2016-09-26 03:23:26 +02:00
|
|
|
self.write_path(path.as_ref());
|
2016-09-05 06:52:23 +02:00
|
|
|
self.write(b":");
|
|
|
|
}
|
2016-08-30 04:44:15 +02:00
|
|
|
if let Some(line_number) = line_number {
|
2016-09-05 23:36:41 +02:00
|
|
|
self.line_number(line_number, b':');
|
|
|
|
}
|
2016-09-23 03:32:38 +02:00
|
|
|
if let Some(c) = column {
|
2016-09-23 23:11:09 +02:00
|
|
|
self.write((c + 1).to_string().as_bytes());
|
2016-09-07 01:50:27 +02:00
|
|
|
self.write(b":");
|
|
|
|
}
|
2016-09-05 23:36:41 +02:00
|
|
|
if self.replace.is_some() {
|
|
|
|
let line = re.replace_all(
|
|
|
|
&buf[start..end], &**self.replace.as_ref().unwrap());
|
|
|
|
self.write(&line);
|
|
|
|
} else {
|
2016-09-23 03:32:38 +02:00
|
|
|
self.write_matched_line(re, &buf[start..end]);
|
2016-08-30 04:44:15 +02:00
|
|
|
}
|
2016-09-05 06:52:23 +02:00
|
|
|
if buf[start..end].last() != Some(&self.eol) {
|
|
|
|
self.write_eol();
|
2016-09-04 03:48:23 +02:00
|
|
|
}
|
2016-08-28 07:37:12 +02:00
|
|
|
}
|
|
|
|
|
2016-09-23 03:32:38 +02:00
|
|
|
fn write_matched_line(&mut self, re: &Regex, buf: &[u8]) {
|
2016-09-09 03:46:14 +02:00
|
|
|
if !self.wtr.supports_color() {
|
2016-09-05 23:36:41 +02:00
|
|
|
self.write(buf);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
let mut last_written = 0;
|
|
|
|
for (s, e) in re.find_iter(buf) {
|
|
|
|
self.write(&buf[last_written..s]);
|
2016-09-09 03:46:14 +02:00
|
|
|
let _ = self.wtr.fg(color::BRIGHT_RED);
|
|
|
|
let _ = self.wtr.attr(Attr::Bold);
|
2016-09-05 23:36:41 +02:00
|
|
|
self.write(&buf[s..e]);
|
|
|
|
let _ = self.wtr.reset();
|
|
|
|
last_written = e;
|
|
|
|
}
|
|
|
|
self.write(&buf[last_written..]);
|
|
|
|
}
|
|
|
|
|
2016-09-01 02:02:59 +02:00
|
|
|
pub fn context<P: AsRef<Path>>(
|
|
|
|
&mut self,
|
|
|
|
path: P,
|
|
|
|
buf: &[u8],
|
|
|
|
start: usize,
|
|
|
|
end: usize,
|
|
|
|
line_number: Option<u64>,
|
|
|
|
) {
|
2016-09-05 23:36:41 +02:00
|
|
|
if self.heading && self.with_filename && !self.has_printed {
|
|
|
|
self.write_heading(path.as_ref());
|
|
|
|
} else if !self.heading && self.with_filename {
|
2016-09-26 03:23:26 +02:00
|
|
|
self.write_path(path.as_ref());
|
2016-09-05 06:52:23 +02:00
|
|
|
self.write(b"-");
|
|
|
|
}
|
2016-09-01 02:02:59 +02:00
|
|
|
if let Some(line_number) = line_number {
|
2016-09-05 23:36:41 +02:00
|
|
|
self.line_number(line_number, b'-');
|
2016-09-01 02:02:59 +02:00
|
|
|
}
|
|
|
|
self.write(&buf[start..end]);
|
2016-09-05 06:52:23 +02:00
|
|
|
if buf[start..end].last() != Some(&self.eol) {
|
|
|
|
self.write_eol();
|
2016-09-04 03:48:23 +02:00
|
|
|
}
|
2016-09-01 02:02:59 +02:00
|
|
|
}
|
|
|
|
|
2016-09-05 23:36:41 +02:00
|
|
|
fn write_heading<P: AsRef<Path>>(&mut self, path: P) {
|
2016-09-09 03:46:14 +02:00
|
|
|
if self.wtr.supports_color() {
|
|
|
|
let _ = self.wtr.fg(color::BRIGHT_GREEN);
|
|
|
|
let _ = self.wtr.attr(Attr::Bold);
|
2016-09-05 23:36:41 +02:00
|
|
|
}
|
2016-09-26 03:23:26 +02:00
|
|
|
self.write_path(path.as_ref());
|
2016-09-05 23:36:41 +02:00
|
|
|
self.write_eol();
|
2016-09-09 03:46:14 +02:00
|
|
|
if self.wtr.supports_color() {
|
2016-09-05 23:36:41 +02:00
|
|
|
let _ = self.wtr.reset();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
fn line_number(&mut self, n: u64, sep: u8) {
|
2016-09-09 03:46:14 +02:00
|
|
|
if self.wtr.supports_color() {
|
|
|
|
let _ = self.wtr.fg(color::BRIGHT_BLUE);
|
|
|
|
let _ = self.wtr.attr(Attr::Bold);
|
2016-09-05 23:36:41 +02:00
|
|
|
}
|
|
|
|
self.write(n.to_string().as_bytes());
|
2016-09-09 03:46:14 +02:00
|
|
|
if self.wtr.supports_color() {
|
2016-09-05 23:36:41 +02:00
|
|
|
let _ = self.wtr.reset();
|
|
|
|
}
|
|
|
|
self.write(&[sep]);
|
|
|
|
}
|
|
|
|
|
2016-09-26 03:23:26 +02:00
|
|
|
#[cfg(unix)]
|
|
|
|
fn write_path<P: AsRef<Path>>(&mut self, path: P) {
|
|
|
|
use std::os::unix::ffi::OsStrExt;
|
|
|
|
|
|
|
|
let path = path.as_ref().as_os_str().as_bytes();
|
|
|
|
self.write(path);
|
|
|
|
}
|
|
|
|
|
|
|
|
#[cfg(not(unix))]
|
|
|
|
fn write_path<P: AsRef<Path>>(&mut self, p: P) {
|
|
|
|
self.write(path.as_ref().to_string_lossy().as_bytes());
|
|
|
|
}
|
|
|
|
|
2016-08-30 04:44:15 +02:00
|
|
|
fn write(&mut self, buf: &[u8]) {
|
2016-09-05 06:52:23 +02:00
|
|
|
if self.quiet {
|
|
|
|
return;
|
|
|
|
}
|
2016-09-02 03:56:23 +02:00
|
|
|
self.has_printed = true;
|
2016-08-30 04:44:15 +02:00
|
|
|
let _ = self.wtr.write_all(buf);
|
|
|
|
}
|
2016-09-05 06:52:23 +02:00
|
|
|
|
|
|
|
fn write_eol(&mut self) {
|
|
|
|
let eol = self.eol;
|
|
|
|
self.write(&[eol]);
|
|
|
|
}
|
2016-08-28 07:37:12 +02:00
|
|
|
}
|