diff --git a/crates/core/args.rs b/crates/core/args.rs index 9984a592..0f8d1f18 100644 --- a/crates/core/args.rs +++ b/crates/core/args.rs @@ -18,8 +18,9 @@ use grep::pcre2::{ RegexMatcherBuilder as PCRE2RegexMatcherBuilder, }; use grep::printer::{ - default_color_specs, ColorSpecs, HyperlinkPattern, JSONBuilder, Standard, - StandardBuilder, Stats, Summary, SummaryBuilder, SummaryKind, JSON, + default_color_specs, ColorSpecs, HyperlinkPattern, JSONBuilder, + PathPrinter, PathPrinterBuilder, Standard, StandardBuilder, Stats, + Summary, SummaryBuilder, SummaryKind, JSON, }; use grep::regex::{ RegexMatcher as RustRegexMatcher, @@ -38,7 +39,6 @@ use crate::app; use crate::config; use crate::logger::Logger; use crate::messages::{set_ignore_messages, set_messages}; -use crate::path_printer::{PathPrinter, PathPrinterBuilder}; use crate::search::{ PatternMatcher, Printer, SearchWorker, SearchWorkerBuilder, }; diff --git a/crates/core/main.rs b/crates/core/main.rs index 45230a20..5088cf08 100644 --- a/crates/core/main.rs +++ b/crates/core/main.rs @@ -16,7 +16,6 @@ mod app; mod args; mod config; mod logger; -mod path_printer; mod search; mod subject; @@ -248,7 +247,7 @@ fn files(args: &Args) -> Result { if quit_after_match { break; } - if let Err(err) = path_printer.write_path(subject.path()) { + if let Err(err) = path_printer.write(subject.path()) { // A broken pipe means graceful termination. if err.kind() == io::ErrorKind::BrokenPipe { break; @@ -293,7 +292,7 @@ fn files_parallel(args: &Args) -> Result { let print_thread = thread::spawn(move || -> io::Result<()> { for subject in rx.iter() { - path_printer.write_path(subject.path())?; + path_printer.write(subject.path())?; } Ok(()) }); diff --git a/crates/core/path_printer.rs b/crates/core/path_printer.rs deleted file mode 100644 index 44b624ad..00000000 --- a/crates/core/path_printer.rs +++ /dev/null @@ -1,134 +0,0 @@ -use std::io; -use std::path::Path; - -use grep::printer::{ - ColorSpecs, HyperlinkPattern, HyperlinkSpan, PrinterPath, -}; -use termcolor::WriteColor; - -/// A configuration for describing how paths should be written. -#[derive(Clone, Debug)] -struct Config { - colors: ColorSpecs, - hyperlink_pattern: HyperlinkPattern, - separator: Option, - terminator: u8, -} - -impl Default for Config { - fn default() -> Config { - Config { - colors: ColorSpecs::default(), - hyperlink_pattern: HyperlinkPattern::default(), - separator: None, - terminator: b'\n', - } - } -} - -/// A builder for constructing things to search over. -#[derive(Clone, Debug)] -pub struct PathPrinterBuilder { - config: Config, -} - -impl PathPrinterBuilder { - /// Return a new subject builder with a default configuration. - pub fn new() -> PathPrinterBuilder { - PathPrinterBuilder { config: Config::default() } - } - - /// Create a new path printer with the current configuration that writes - /// paths to the given writer. - pub fn build(&self, wtr: W) -> PathPrinter { - PathPrinter { config: self.config.clone(), wtr, buf: vec![] } - } - - /// Set the color specification for this printer. - /// - /// Currently, only the `path` component of the given specification is - /// used. - pub fn color_specs( - &mut self, - specs: ColorSpecs, - ) -> &mut PathPrinterBuilder { - self.config.colors = specs; - self - } - - /// Set the hyperlink pattern to use for hyperlinks output by this printer. - /// - /// Colors need to be enabled for hyperlinks to be output. - pub fn hyperlink_pattern( - &mut self, - pattern: HyperlinkPattern, - ) -> &mut PathPrinterBuilder { - self.config.hyperlink_pattern = pattern; - self - } - - /// A path separator. - /// - /// When provided, the path's default separator will be replaced with - /// the given separator. - /// - /// This is not set by default, and the system's default path separator - /// will be used. - pub fn separator(&mut self, sep: Option) -> &mut PathPrinterBuilder { - self.config.separator = sep; - self - } - - /// A path terminator. - /// - /// When printing a path, it will be by terminated by the given byte. - /// - /// This is set to `\n` by default. - pub fn terminator(&mut self, terminator: u8) -> &mut PathPrinterBuilder { - self.config.terminator = terminator; - self - } -} - -/// A printer for emitting paths to a writer, with optional color support. -#[derive(Debug)] -pub struct PathPrinter { - config: Config, - wtr: W, - buf: Vec, -} - -impl PathPrinter { - /// Write the given path to the underlying writer. - pub fn write_path(&mut self, path: &Path) -> io::Result<()> { - let ppath = PrinterPath::with_separator(path, self.config.separator); - if !self.wtr.supports_color() { - self.wtr.write_all(ppath.as_bytes())?; - } else { - let mut hyperlink = self.start_hyperlink_span(&ppath)?; - self.wtr.set_color(self.config.colors.path())?; - self.wtr.write_all(ppath.as_bytes())?; - self.wtr.reset()?; - hyperlink.end(&mut self.wtr)?; - } - self.wtr.write_all(&[self.config.terminator]) - } - - /// Starts a hyperlink span when applicable. - fn start_hyperlink_span( - &mut self, - path: &PrinterPath, - ) -> io::Result { - if self.wtr.supports_hyperlinks() { - if let Some(spec) = path.create_hyperlink_spec( - &self.config.hyperlink_pattern, - None, - None, - &mut self.buf, - ) { - return Ok(HyperlinkSpan::start(&mut self.wtr, &spec)?); - } - } - Ok(HyperlinkSpan::default()) - } -} diff --git a/crates/printer/src/hyperlink.rs b/crates/printer/src/hyperlink.rs index 136ec64e..fa38b5c2 100644 --- a/crates/printer/src/hyperlink.rs +++ b/crates/printer/src/hyperlink.rs @@ -59,7 +59,7 @@ pub enum HyperlinkPatternError { /// The values to replace the pattern placeholders with. #[derive(Clone, Debug)] -pub struct HyperlinkValues<'a> { +pub(crate) struct HyperlinkValues<'a> { file: &'a HyperlinkPath, line: u64, column: u64, @@ -70,7 +70,7 @@ pub struct HyperlinkValues<'a> { /// This is the value to use as-is in the hyperlink, converted from an OS file /// path. #[derive(Clone, Debug)] -pub struct HyperlinkPath(Vec); +pub(crate) struct HyperlinkPath(Vec); impl HyperlinkPatternBuilder { /// Creates a new hyperlink pattern builder. @@ -222,7 +222,7 @@ impl HyperlinkPattern { } /// Renders this pattern with the given values to the given output. - pub fn render( + pub(crate) fn render( &self, values: &HyperlinkValues, output: &mut impl Write, @@ -353,7 +353,7 @@ impl std::error::Error for HyperlinkPatternError {} impl<'a> HyperlinkValues<'a> { /// Creates a new set of hyperlink values. - pub fn new( + pub(crate) fn new( file: &'a HyperlinkPath, line: Option, column: Option, @@ -369,7 +369,7 @@ impl<'a> HyperlinkValues<'a> { impl HyperlinkPath { /// Returns a hyperlink path from an OS path. #[cfg(unix)] - pub fn from_path(path: &Path) -> Option { + pub(crate) fn from_path(path: &Path) -> Option { // On Unix, this function returns the absolute file path without the // leading slash, as it makes for more natural hyperlink patterns, for // instance: @@ -506,14 +506,14 @@ impl std::fmt::Display for HyperlinkPath { /// A simple abstraction over a hyperlink span written to the terminal. This /// helps tracking whether a hyperlink has been started, and should be ended. #[derive(Debug, Default)] -pub struct HyperlinkSpan { +pub(crate) struct HyperlinkSpan { active: bool, } impl HyperlinkSpan { /// Starts a hyperlink and returns a span which tracks whether it is still /// in effect. - pub fn start( + pub(crate) fn start( wtr: &mut impl WriteColor, hyperlink: &HyperlinkSpec, ) -> io::Result { @@ -526,7 +526,7 @@ impl HyperlinkSpan { } /// Ends the hyperlink span if it is active. - pub fn end(&mut self, wtr: &mut impl WriteColor) -> io::Result<()> { + pub(crate) fn end(&mut self, wtr: &mut impl WriteColor) -> io::Result<()> { if self.is_active() { wtr.set_hyperlink(&HyperlinkSpec::close())?; self.active = false; @@ -535,7 +535,7 @@ impl HyperlinkSpan { } /// Returns true if there is currently an active hyperlink. - pub fn is_active(&self) -> bool { + pub(crate) fn is_active(&self) -> bool { self.active } } diff --git a/crates/printer/src/lib.rs b/crates/printer/src/lib.rs index 6a2ac727..b2869d99 100644 --- a/crates/printer/src/lib.rs +++ b/crates/printer/src/lib.rs @@ -65,13 +65,12 @@ assert_eq!(output, expected); pub use crate::{ color::{default_color_specs, ColorError, ColorSpecs, UserColorSpec}, hyperlink::{ - HyperlinkPath, HyperlinkPattern, HyperlinkPatternBuilder, - HyperlinkPatternError, HyperlinkSpan, HyperlinkValues, + HyperlinkPattern, HyperlinkPatternBuilder, HyperlinkPatternError, }, + path::{PathPrinter, PathPrinterBuilder}, standard::{Standard, StandardBuilder, StandardSink}, stats::Stats, summary::{Summary, SummaryBuilder, SummaryKind, SummarySink}, - util::PrinterPath, }; #[cfg(feature = "serde")] @@ -99,6 +98,7 @@ mod hyperlink_aliases; mod json; #[cfg(feature = "serde")] mod jsont; +mod path; mod standard; mod stats; mod summary; diff --git a/crates/printer/src/path.rs b/crates/printer/src/path.rs new file mode 100644 index 00000000..c25956bc --- /dev/null +++ b/crates/printer/src/path.rs @@ -0,0 +1,179 @@ +use std::{io, path::Path}; + +use termcolor::WriteColor; + +use crate::{ + color::ColorSpecs, + hyperlink::{HyperlinkPattern, HyperlinkSpan}, + util::PrinterPath, +}; + +/// A configuration for describing how paths should be written. +#[derive(Clone, Debug)] +struct Config { + colors: ColorSpecs, + hyperlink_pattern: HyperlinkPattern, + separator: Option, + terminator: u8, +} + +impl Default for Config { + fn default() -> Config { + Config { + colors: ColorSpecs::default(), + hyperlink_pattern: HyperlinkPattern::default(), + separator: None, + terminator: b'\n', + } + } +} + +/// A builder for a printer that emits file paths. +#[derive(Clone, Debug)] +pub struct PathPrinterBuilder { + config: Config, +} + +impl PathPrinterBuilder { + /// Return a new path printer builder with a default configuration. + pub fn new() -> PathPrinterBuilder { + PathPrinterBuilder { config: Config::default() } + } + + /// Create a new path printer with the current configuration that writes + /// paths to the given writer. + pub fn build(&self, wtr: W) -> PathPrinter { + PathPrinter { config: self.config.clone(), wtr, buf: vec![] } + } + + /// Set the user color specifications to use for coloring in this printer. + /// + /// A [`UserColorSpec`](crate::UserColorSpec) can be constructed from + /// a string in accordance with the color specification format. See + /// the `UserColorSpec` type documentation for more details on the + /// format. A [`ColorSpecs`] can then be generated from zero or more + /// `UserColorSpec`s. + /// + /// Regardless of the color specifications provided here, whether color + /// is actually used or not is determined by the implementation of + /// `WriteColor` provided to `build`. For example, if `termcolor::NoColor` + /// is provided to `build`, then no color will ever be printed regardless + /// of the color specifications provided here. + /// + /// This completely overrides any previous color specifications. This does + /// not add to any previously provided color specifications on this + /// builder. + /// + /// The default color specifications provide no styling. + pub fn color_specs( + &mut self, + specs: ColorSpecs, + ) -> &mut PathPrinterBuilder { + self.config.colors = specs; + self + } + + /// Set the hyperlink pattern to use for hyperlinks output by this printer. + /// + /// Regardless of the hyperlink format provided here, whether hyperlinks + /// are actually used or not is determined by the implementation of + /// `WriteColor` provided to `build`. For example, if `termcolor::NoColor` + /// is provided to `build`, then no hyperlinks will ever be printed + /// regardless of the format provided here. + /// + /// This completely overrides any previous hyperlink format. + /// + /// The default pattern format results in not emitting any hyperlinks. + pub fn hyperlink_pattern( + &mut self, + pattern: HyperlinkPattern, + ) -> &mut PathPrinterBuilder { + self.config.hyperlink_pattern = pattern; + self + } + + /// Set the path separator used when printing file paths. + /// + /// Typically, printing is done by emitting the file path as is. However, + /// this setting provides the ability to use a different path separator + /// from what the current environment has configured. + /// + /// A typical use for this option is to permit cygwin users on Windows to + /// set the path separator to `/` instead of using the system default of + /// `\`. + /// + /// This is disabled by default. + pub fn separator(&mut self, sep: Option) -> &mut PathPrinterBuilder { + self.config.separator = sep; + self + } + + /// Set the path terminator used. + /// + /// The path terminator is a byte that is printed after every file path + /// emitted by this printer. + /// + /// The default path terminator is `\n`. + pub fn terminator(&mut self, terminator: u8) -> &mut PathPrinterBuilder { + self.config.terminator = terminator; + self + } +} + +/// A printer file paths, with optional color and hyperlink support. +/// +/// This printer is very similar to [`Summary`](crate::Summary) in that it +/// principally only emits file paths. The main difference is that this printer +/// doesn't actually execute any search via a `Sink` implementation, and instead +/// just provides a way for the caller to print paths. +/// +/// A caller could just print the paths themselves, but this printer handles +/// a few details: +/// +/// * It can normalize path separators. +/// * It permits configuring the terminator. +/// * It allows setting the color configuration in a way that is consistent +/// with the other printers in this crate. +/// * It allows setting the hyperlink format in a way that is consistent +/// with the other printers in this crate. +#[derive(Debug)] +pub struct PathPrinter { + config: Config, + wtr: W, + buf: Vec, +} + +impl PathPrinter { + /// Write the given path to the underlying writer. + pub fn write(&mut self, path: &Path) -> io::Result<()> { + let ppath = PrinterPath::with_separator(path, self.config.separator); + if !self.wtr.supports_color() { + self.wtr.write_all(ppath.as_bytes())?; + } else { + let mut hyperlink = self.start_hyperlink_span(&ppath)?; + self.wtr.set_color(self.config.colors.path())?; + self.wtr.write_all(ppath.as_bytes())?; + self.wtr.reset()?; + hyperlink.end(&mut self.wtr)?; + } + self.wtr.write_all(&[self.config.terminator]) + } + + /// Starts a hyperlink span when applicable. + fn start_hyperlink_span( + &mut self, + path: &PrinterPath, + ) -> io::Result { + if self.wtr.supports_hyperlinks() { + if let Some(spec) = path.create_hyperlink_spec( + &self.config.hyperlink_pattern, + None, + None, + &mut self.buf, + ) { + return Ok(HyperlinkSpan::start(&mut self.wtr, &spec)?); + } + } + Ok(HyperlinkSpan::default()) + } +} diff --git a/crates/printer/src/standard.rs b/crates/printer/src/standard.rs index 4dd55ac9..aa925546 100644 --- a/crates/printer/src/standard.rs +++ b/crates/printer/src/standard.rs @@ -172,7 +172,15 @@ impl StandardBuilder { /// Set the hyperlink pattern to use for hyperlinks output by this printer. /// - /// Colors need to be enabled for hyperlinks to be output. + /// Regardless of the hyperlink format provided here, whether hyperlinks + /// are actually used or not is determined by the implementation of + /// `WriteColor` provided to `build`. For example, if `termcolor::NoColor` + /// is provided to `build`, then no hyperlinks will ever be printed + /// regardless of the format provided here. + /// + /// This completely overrides any previous hyperlink format. + /// + /// The default pattern format results in not emitting any hyperlinks. pub fn hyperlink_pattern( &mut self, pattern: HyperlinkPattern, diff --git a/crates/printer/src/summary.rs b/crates/printer/src/summary.rs index f62eba90..4875bb7e 100644 --- a/crates/printer/src/summary.rs +++ b/crates/printer/src/summary.rs @@ -218,7 +218,15 @@ impl SummaryBuilder { /// Set the hyperlink pattern to use for hyperlinks output by this printer. /// - /// Colors need to be enabled for hyperlinks to be output. + /// Regardless of the hyperlink format provided here, whether hyperlinks + /// are actually used or not is determined by the implementation of + /// `WriteColor` provided to `build`. For example, if `termcolor::NoColor` + /// is provided to `build`, then no hyperlinks will ever be printed + /// regardless of the format provided here. + /// + /// This completely overrides any previous hyperlink format. + /// + /// The default pattern format results in not emitting any hyperlinks. pub fn hyperlink_pattern( &mut self, pattern: HyperlinkPattern, diff --git a/crates/printer/src/util.rs b/crates/printer/src/util.rs index bfa8551b..a042e754 100644 --- a/crates/printer/src/util.rs +++ b/crates/printer/src/util.rs @@ -280,7 +280,7 @@ impl<'a> Sunk<'a> { /// portability with a small cost: on Windows, paths that are not valid UTF-16 /// will not roundtrip correctly. #[derive(Clone, Debug)] -pub struct PrinterPath<'a> { +pub(crate) struct PrinterPath<'a> { path: &'a Path, bytes: Cow<'a, [u8]>, hyperlink_path: std::cell::OnceCell>, @@ -288,7 +288,7 @@ pub struct PrinterPath<'a> { impl<'a> PrinterPath<'a> { /// Create a new path suitable for printing. - pub fn new(path: &'a Path) -> PrinterPath<'a> { + pub(crate) fn new(path: &'a Path) -> PrinterPath<'a> { PrinterPath { path, bytes: Vec::from_path_lossy(path), @@ -301,7 +301,10 @@ impl<'a> PrinterPath<'a> { /// /// If the given separator is present, then any separators in `path` are /// replaced with it. - pub fn with_separator(path: &'a Path, sep: Option) -> PrinterPath<'a> { + pub(crate) fn with_separator( + path: &'a Path, + sep: Option, + ) -> PrinterPath<'a> { let mut ppath = PrinterPath::new(path); if let Some(sep) = sep { ppath.replace_separator(sep); @@ -329,13 +332,13 @@ impl<'a> PrinterPath<'a> { } /// Return the raw bytes for this path. - pub fn as_bytes(&self) -> &[u8] { + pub(crate) fn as_bytes(&self) -> &[u8] { &self.bytes } /// Creates a hyperlink for this path and the given line and column, using /// the specified pattern. Uses the given buffer to store the hyperlink. - pub fn create_hyperlink_spec<'b>( + pub(crate) fn create_hyperlink_spec<'b>( &self, pattern: &HyperlinkPattern, line_number: Option,