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}