on 3338699 help sign in
bat/src/pretty_printer.rs
351 lines 11 kB history raw
1use std::io::Read;
2use std::path::Path;
4use console::Term;
5use syntect::parsing::SyntaxReference;
7use crate::{
8 assets::HighlightingAssets,
9 config::{Config, VisibleLines},
10 controller::Controller,
11 error::Result,
12 input,
13 line_range::{HighlightedLineRanges, LineRange, LineRanges},
14 style::{StyleComponent, StyleComponents},
15 SyntaxMapping, WrappingMode,
16};
18#[cfg(feature = "paging")]
19use crate::paging::PagingMode;
21#[derive(Default)]
22struct ActiveStyleComponents {
23 header: bool,
24 vcs_modification_markers: bool,
25 grid: bool,
26 rule: bool,
27 line_numbers: bool,
28 snip: bool,
31pub struct PrettyPrinter<'a> {
32 inputs: Vec<Input<'a>>,
33 config: Config<'a>,
34 assets: HighlightingAssets,
36 highlighted_lines: Vec<LineRange>,
37 term_width: Option<usize>,
38 active_style_components: ActiveStyleComponents,
41impl<'a> PrettyPrinter<'a> {
42 pub fn new() -> Self {
43 let config = Config {
44 colored_output: true,
45 true_color: true,
46 ..Default::default()
47 };
49 PrettyPrinter {
50 inputs: vec![],
51 config,
52 assets: HighlightingAssets::from_binary(),
54 highlighted_lines: vec![],
55 term_width: None,
56 active_style_components: ActiveStyleComponents::default(),
57 }
58 }
60 /// Add an input which should be pretty-printed
61 pub fn input(&mut self, input: Input<'a>) -> &mut Self {
62 self.inputs.push(input);
63 self
64 }
66 /// Adds multiple inputs which should be pretty-printed
67 pub fn inputs(&mut self, inputs: impl IntoIterator<Item = Input<'a>>) -> &mut Self {
68 for input in inputs {
69 self.inputs.push(input);
70 }
71 self
72 }
74 /// Add a file which should be pretty-printed
75 pub fn input_file(&mut self, path: impl AsRef<Path>) -> &mut Self {
76 self.input(Input::from_file(path).kind("File"))
77 }
79 /// Add multiple files which should be pretty-printed
80 pub fn input_files<I, P>(&mut self, paths: I) -> &mut Self
81 where
82 I: IntoIterator<Item = P>,
83 P: AsRef<Path>,
84 {
85 self.inputs(paths.into_iter().map(Input::from_file))
86 }
88 /// Add STDIN as an input
89 pub fn input_stdin(&mut self) -> &mut Self {
90 self.inputs.push(Input::from_stdin());
91 self
92 }
94 /// Add a byte string as an input
95 pub fn input_from_bytes(&mut self, content: &'a [u8]) -> &mut Self {
96 self.input_from_reader(content)
97 }
99 /// Add a custom reader as an input
100 pub fn input_from_reader<R: Read + 'a>(&mut self, reader: R) -> &mut Self {
101 self.inputs.push(Input::from_reader(reader));
102 self
103 }
105 /// Specify the syntax file which should be used (default: auto-detect)
106 pub fn language(&mut self, language: &'a str) -> &mut Self {
107 self.config.language = Some(language);
108 self
109 }
111 /// The character width of the terminal (default: autodetect)
112 pub fn term_width(&mut self, width: usize) -> &mut Self {
113 self.term_width = Some(width);
114 self
115 }
117 /// The width of tab characters (default: None - do not turn tabs to spaces)
118 pub fn tab_width(&mut self, tab_width: Option<usize>) -> &mut Self {
119 self.config.tab_width = tab_width.unwrap_or(0);
120 self
121 }
123 /// Whether or not the output should be colorized (default: true)
124 pub fn colored_output(&mut self, yes: bool) -> &mut Self {
125 self.config.colored_output = yes;
126 self
127 }
129 /// Whether or not to output 24bit colors (default: true)
130 pub fn true_color(&mut self, yes: bool) -> &mut Self {
131 self.config.true_color = yes;
132 self
133 }
135 /// Whether to show a header with the file name
136 pub fn header(&mut self, yes: bool) -> &mut Self {
137 self.active_style_components.header = yes;
138 self
139 }
141 /// Whether to show line numbers
142 pub fn line_numbers(&mut self, yes: bool) -> &mut Self {
143 self.active_style_components.line_numbers = yes;
144 self
145 }
147 /// Whether to paint a grid, separating line numbers, git changes and the code
148 pub fn grid(&mut self, yes: bool) -> &mut Self {
149 self.active_style_components.grid = yes;
150 self
151 }
153 /// Whether to paint a horizontal rule to delimit files
154 pub fn rule(&mut self, yes: bool) -> &mut Self {
155 self.active_style_components.rule = yes;
156 self
157 }
159 /// Whether to show modification markers for VCS changes. This has no effect if
160 /// the `git` feature is not activated.
161 #[cfg(feature = "git")]
162 pub fn vcs_modification_markers(&mut self, yes: bool) -> &mut Self {
163 self.active_style_components.vcs_modification_markers = yes;
164 self
165 }
167 /// Whether to show "snip" markers between visible line ranges (default: no)
168 pub fn snip(&mut self, yes: bool) -> &mut Self {
169 self.active_style_components.snip = yes;
170 self
171 }
173 /// Text wrapping mode (default: do not wrap)
174 pub fn wrapping_mode(&mut self, mode: WrappingMode) -> &mut Self {
175 self.config.wrapping_mode = mode;
176 self
177 }
179 /// Whether or not to use ANSI italics (default: off)
180 pub fn use_italics(&mut self, yes: bool) -> &mut Self {
181 self.config.use_italic_text = yes;
182 self
183 }
185 /// If and how to use a pager (default: no paging)
186 #[cfg(feature = "paging")]
187 pub fn paging_mode(&mut self, mode: PagingMode) -> &mut Self {
188 self.config.paging_mode = mode;
189 self
190 }
192 /// Specify the command to start the pager (default: use "less")
193 #[cfg(feature = "paging")]
194 pub fn pager(&mut self, cmd: &'a str) -> &mut Self {
195 self.config.pager = Some(cmd);
196 self
197 }
199 /// Specify the lines that should be printed (default: all)
200 pub fn line_ranges(&mut self, ranges: LineRanges) -> &mut Self {
201 self.config.visible_lines = VisibleLines::Ranges(ranges);
202 self
203 }
205 /// Specify a line that should be highlighted (default: none).
206 /// This can be called multiple times to highlight more than one
207 /// line. See also: highlight_range.
208 pub fn highlight(&mut self, line: usize) -> &mut Self {
209 self.highlighted_lines.push(LineRange::new(line, line));
210 self
211 }
213 /// Specify a range of lines that should be highlighted (default: none).
214 /// This can be called multiple times to highlight more than one range
215 /// of lines.
216 pub fn highlight_range(&mut self, from: usize, to: usize) -> &mut Self {
217 self.highlighted_lines.push(LineRange::new(from, to));
218 self
219 }
221 /// Specify the highlighting theme
222 pub fn theme(&mut self, theme: impl AsRef<str>) -> &mut Self {
223 self.config.theme = theme.as_ref().to_owned();
224 self
225 }
227 /// Specify custom file extension / file name to syntax mappings
228 pub fn syntax_mapping(&mut self, mapping: SyntaxMapping<'a>) -> &mut Self {
229 self.config.syntax_mapping = mapping;
230 self
231 }
233 pub fn themes(&self) -> impl Iterator<Item = &str> {
234 self.assets.themes()
235 }
237 pub fn syntaxes(&self) -> impl Iterator<Item = &SyntaxReference> {
238 // We always use assets from the binary, which are guaranteed to always
239 // be valid, so get_syntaxes() can never fail here
240 self.assets.get_syntaxes().unwrap().iter()
241 }
243 /// Pretty-print all specified inputs. This method will "use" all stored inputs.
244 /// If you want to call 'print' multiple times, you have to call the appropriate
245 /// input_* methods again.
246 pub fn print(&mut self) -> Result<bool> {
247 self.config.highlighted_lines =
248 HighlightedLineRanges(LineRanges::from(self.highlighted_lines.clone()));
249 self.config.term_width = self
250 .term_width
251 .unwrap_or_else(|| Term::stdout().size().1 as usize);
253 let mut style_components = vec![];
254 if self.active_style_components.grid {
255 style_components.push(StyleComponent::Grid);
256 }
257 if self.active_style_components.rule {
258 style_components.push(StyleComponent::Rule);
259 }
260 if self.active_style_components.header {
261 style_components.push(StyleComponent::Header);
262 }
263 if self.active_style_components.line_numbers {
264 style_components.push(StyleComponent::LineNumbers);
265 }
266 if self.active_style_components.snip {
267 style_components.push(StyleComponent::Snip);
268 }
269 if self.active_style_components.vcs_modification_markers {
270 #[cfg(feature = "git")]
271 style_components.push(StyleComponent::Changes);
272 }
273 self.config.style_components = StyleComponents::new(&style_components);
275 // Collect the inputs to print
276 let mut inputs: Vec<Input> = vec![];
277 std::mem::swap(&mut inputs, &mut self.inputs);
279 // Run the controller
280 let controller = Controller::new(&self.config, &self.assets);
281 controller.run(inputs.into_iter().map(|i| i.into()).collect())
282 }
285impl Default for PrettyPrinter<'_> {
286 fn default() -> Self {
287 Self::new()
288 }
291/// An input source for the pretty printer.
292pub struct Input<'a> {
293 input: input::Input<'a>,
296impl<'a> Input<'a> {
297 /// A new input from a reader.
298 pub fn from_reader<R: Read + 'a>(reader: R) -> Self {
299 input::Input::from_reader(Box::new(reader)).into()
300 }
302 /// A new input from a file.
303 pub fn from_file(path: impl AsRef<Path>) -> Self {
304 input::Input::ordinary_file(path).into()
305 }
307 /// A new input from bytes.
308 pub fn from_bytes(bytes: &'a [u8]) -> Self {
309 Input::from_reader(bytes)
310 }
312 /// A new input from STDIN.
313 pub fn from_stdin() -> Self {
314 input::Input::stdin().into()
315 }
317 /// The filename of the input.
318 /// This affects syntax detection and changes the default header title.
319 pub fn name(mut self, name: impl AsRef<Path>) -> Self {
320 self.input = self.input.with_name(Some(name));
321 self
322 }
324 /// The description for the type of input (e.g. "File")
325 pub fn kind(mut self, kind: impl Into<String>) -> Self {
326 let kind = kind.into();
327 self.input
328 .description_mut()
329 .set_kind(if kind.is_empty() { None } else { Some(kind) });
330 self
331 }
333 /// The title for the input (e.g. "Descriptive title")
334 /// This defaults to the file name.
335 pub fn title(mut self, title: impl Into<String>) -> Self {
336 self.input.description_mut().set_title(Some(title.into()));
337 self
338 }
341impl<'a> From<input::Input<'a>> for Input<'a> {
342 fn from(input: input::Input<'a>) -> Self {
343 Self { input }
344 }
347impl<'a> From<Input<'a>> for input::Input<'a> {
348 fn from(Input { input }: Input<'a>) -> Self {
349 input
350 }