grep_printer/
path.rs

1use std::{io, path::Path};
2
3use termcolor::WriteColor;
4
5use crate::{
6    color::ColorSpecs,
7    hyperlink::{self, HyperlinkConfig},
8    util::PrinterPath,
9};
10
11/// A configuration for describing how paths should be written.
12#[derive(Clone, Debug)]
13struct Config {
14    colors: ColorSpecs,
15    hyperlink: HyperlinkConfig,
16    separator: Option<u8>,
17    terminator: u8,
18}
19
20impl Default for Config {
21    fn default() -> Config {
22        Config {
23            colors: ColorSpecs::default(),
24            hyperlink: HyperlinkConfig::default(),
25            separator: None,
26            terminator: b'\n',
27        }
28    }
29}
30
31/// A builder for a printer that emits file paths.
32#[derive(Clone, Debug)]
33pub struct PathPrinterBuilder {
34    config: Config,
35}
36
37impl PathPrinterBuilder {
38    /// Return a new path printer builder with a default configuration.
39    pub fn new() -> PathPrinterBuilder {
40        PathPrinterBuilder { config: Config::default() }
41    }
42
43    /// Create a new path printer with the current configuration that writes
44    /// paths to the given writer.
45    pub fn build<W: WriteColor>(&self, wtr: W) -> PathPrinter<W> {
46        let interpolator =
47            hyperlink::Interpolator::new(&self.config.hyperlink);
48        PathPrinter { config: self.config.clone(), wtr, interpolator }
49    }
50
51    /// Set the user color specifications to use for coloring in this printer.
52    ///
53    /// A [`UserColorSpec`](crate::UserColorSpec) can be constructed from
54    /// a string in accordance with the color specification format. See
55    /// the `UserColorSpec` type documentation for more details on the
56    /// format. A [`ColorSpecs`] can then be generated from zero or more
57    /// `UserColorSpec`s.
58    ///
59    /// Regardless of the color specifications provided here, whether color
60    /// is actually used or not is determined by the implementation of
61    /// `WriteColor` provided to `build`. For example, if `termcolor::NoColor`
62    /// is provided to `build`, then no color will ever be printed regardless
63    /// of the color specifications provided here.
64    ///
65    /// This completely overrides any previous color specifications. This does
66    /// not add to any previously provided color specifications on this
67    /// builder.
68    ///
69    /// The default color specifications provide no styling.
70    pub fn color_specs(
71        &mut self,
72        specs: ColorSpecs,
73    ) -> &mut PathPrinterBuilder {
74        self.config.colors = specs;
75        self
76    }
77
78    /// Set the configuration to use for hyperlinks output by this printer.
79    ///
80    /// Regardless of the hyperlink format provided here, whether hyperlinks
81    /// are actually used or not is determined by the implementation of
82    /// `WriteColor` provided to `build`. For example, if `termcolor::NoColor`
83    /// is provided to `build`, then no hyperlinks will ever be printed
84    /// regardless of the format provided here.
85    ///
86    /// This completely overrides any previous hyperlink format.
87    ///
88    /// The default configuration results in not emitting any hyperlinks.
89    pub fn hyperlink(
90        &mut self,
91        config: HyperlinkConfig,
92    ) -> &mut PathPrinterBuilder {
93        self.config.hyperlink = config;
94        self
95    }
96
97    /// Set the path separator used when printing file paths.
98    ///
99    /// Typically, printing is done by emitting the file path as is. However,
100    /// this setting provides the ability to use a different path separator
101    /// from what the current environment has configured.
102    ///
103    /// A typical use for this option is to permit cygwin users on Windows to
104    /// set the path separator to `/` instead of using the system default of
105    /// `\`.
106    ///
107    /// This is disabled by default.
108    pub fn separator(&mut self, sep: Option<u8>) -> &mut PathPrinterBuilder {
109        self.config.separator = sep;
110        self
111    }
112
113    /// Set the path terminator used.
114    ///
115    /// The path terminator is a byte that is printed after every file path
116    /// emitted by this printer.
117    ///
118    /// The default path terminator is `\n`.
119    pub fn terminator(&mut self, terminator: u8) -> &mut PathPrinterBuilder {
120        self.config.terminator = terminator;
121        self
122    }
123}
124
125/// A printer file paths, with optional color and hyperlink support.
126///
127/// This printer is very similar to [`Summary`](crate::Summary) in that it
128/// principally only emits file paths. The main difference is that this printer
129/// doesn't actually execute any search via a `Sink` implementation, and instead
130/// just provides a way for the caller to print paths.
131///
132/// A caller could just print the paths themselves, but this printer handles
133/// a few details:
134///
135/// * It can normalize path separators.
136/// * It permits configuring the terminator.
137/// * It allows setting the color configuration in a way that is consistent
138/// with the other printers in this crate.
139/// * It allows setting the hyperlink format in a way that is consistent
140/// with the other printers in this crate.
141#[derive(Debug)]
142pub struct PathPrinter<W> {
143    config: Config,
144    wtr: W,
145    interpolator: hyperlink::Interpolator,
146}
147
148impl<W: WriteColor> PathPrinter<W> {
149    /// Write the given path to the underlying writer.
150    pub fn write(&mut self, path: &Path) -> io::Result<()> {
151        let ppath = PrinterPath::new(path.as_ref())
152            .with_separator(self.config.separator);
153        if !self.wtr.supports_color() {
154            self.wtr.write_all(ppath.as_bytes())?;
155        } else {
156            let status = self.start_hyperlink(&ppath)?;
157            self.wtr.set_color(self.config.colors.path())?;
158            self.wtr.write_all(ppath.as_bytes())?;
159            self.wtr.reset()?;
160            self.interpolator.finish(status, &mut self.wtr)?;
161        }
162        self.wtr.write_all(&[self.config.terminator])
163    }
164
165    /// Starts a hyperlink span when applicable.
166    fn start_hyperlink(
167        &mut self,
168        path: &PrinterPath,
169    ) -> io::Result<hyperlink::InterpolatorStatus> {
170        let Some(hyperpath) = path.as_hyperlink() else {
171            return Ok(hyperlink::InterpolatorStatus::inactive());
172        };
173        let values = hyperlink::Values::new(hyperpath);
174        self.interpolator.begin(&values, &mut self.wtr)
175    }
176}