1use std::io::Read;
2use std::path::Path;
3
4use console::Term;
5
6use crate::{
7 assets::HighlightingAssets,
8 config::{Config, VisibleLines},
9 controller::Controller,
10 error::Result,
11 input,
12 line_range::{HighlightedLineRanges, LineRange, LineRanges},
13 output::OutputHandle,
14 style::StyleComponent,
15 StripAnsiMode, SyntaxMapping, WrappingMode,
16};
17
18#[cfg(feature = "paging")]
19use crate::paging::PagingMode;
20
21#[derive(Default)]
22struct ActiveStyleComponents {
23 header_filename: bool,
24 #[cfg(feature = "git")]
25 vcs_modification_markers: bool,
26 grid: bool,
27 rule: bool,
28 line_numbers: bool,
29 snip: bool,
30}
31
32#[non_exhaustive]
33pub struct Syntax {
34 pub name: String,
35 pub file_extensions: Vec<String>,
36}
37
38pub struct PrettyPrinter<'a> {
39 inputs: Vec<Input<'a>>,
40 config: Config<'a>,
41 assets: HighlightingAssets,
42
43 highlighted_lines: Vec<LineRange>,
44 term_width: Option<usize>,
45 active_style_components: ActiveStyleComponents,
46}
47
48impl<'a> PrettyPrinter<'a> {
49 pub fn new() -> Self {
50 let config = Config {
51 colored_output: true,
52 true_color: true,
53 ..Default::default()
54 };
55
56 PrettyPrinter {
57 inputs: vec![],
58 config,
59 assets: HighlightingAssets::from_binary(),
60
61 highlighted_lines: vec![],
62 term_width: None,
63 active_style_components: ActiveStyleComponents::default(),
64 }
65 }
66
67 pub fn input(&mut self, input: Input<'a>) -> &mut Self {
69 self.inputs.push(input);
70 self
71 }
72
73 pub fn inputs(&mut self, inputs: impl IntoIterator<Item = Input<'a>>) -> &mut Self {
75 for input in inputs {
76 self.inputs.push(input);
77 }
78 self
79 }
80
81 pub fn input_file(&mut self, path: impl AsRef<Path>) -> &mut Self {
83 self.input(Input::from_file(path).kind("File"))
84 }
85
86 pub fn input_files<I, P>(&mut self, paths: I) -> &mut Self
88 where
89 I: IntoIterator<Item = P>,
90 P: AsRef<Path>,
91 {
92 self.inputs(paths.into_iter().map(Input::from_file))
93 }
94
95 pub fn input_stdin(&mut self) -> &mut Self {
97 self.inputs.push(Input::from_stdin());
98 self
99 }
100
101 pub fn input_from_bytes(&mut self, content: &'a [u8]) -> &mut Self {
103 self.input_from_reader(content)
104 }
105
106 pub fn input_from_reader<R: Read + 'a>(&mut self, reader: R) -> &mut Self {
108 self.inputs.push(Input::from_reader(reader));
109 self
110 }
111
112 pub fn language(&mut self, language: &'a str) -> &mut Self {
114 self.config.language = Some(language);
115 self
116 }
117
118 pub fn term_width(&mut self, width: usize) -> &mut Self {
120 self.term_width = Some(width);
121 self
122 }
123
124 pub fn tab_width(&mut self, tab_width: Option<usize>) -> &mut Self {
126 self.config.tab_width = tab_width.unwrap_or(0);
127 self
128 }
129
130 pub fn colored_output(&mut self, yes: bool) -> &mut Self {
132 self.config.colored_output = yes;
133 self
134 }
135
136 pub fn true_color(&mut self, yes: bool) -> &mut Self {
138 self.config.true_color = yes;
139 self
140 }
141
142 pub fn header(&mut self, yes: bool) -> &mut Self {
144 self.active_style_components.header_filename = yes;
145 self
146 }
147
148 pub fn line_numbers(&mut self, yes: bool) -> &mut Self {
150 self.active_style_components.line_numbers = yes;
151 self
152 }
153
154 pub fn grid(&mut self, yes: bool) -> &mut Self {
156 self.active_style_components.grid = yes;
157 self
158 }
159
160 pub fn rule(&mut self, yes: bool) -> &mut Self {
162 self.active_style_components.rule = yes;
163 self
164 }
165
166 #[cfg(feature = "git")]
169 pub fn vcs_modification_markers(&mut self, yes: bool) -> &mut Self {
170 self.active_style_components.vcs_modification_markers = yes;
171 self
172 }
173
174 pub fn show_nonprintable(&mut self, yes: bool) -> &mut Self {
176 self.config.show_nonprintable = yes;
177 self
178 }
179
180 pub fn snip(&mut self, yes: bool) -> &mut Self {
182 self.active_style_components.snip = yes;
183 self
184 }
185
186 pub fn strip_ansi(&mut self, mode: StripAnsiMode) -> &mut Self {
191 self.config.strip_ansi = mode;
192 self
193 }
194
195 pub fn wrapping_mode(&mut self, mode: WrappingMode) -> &mut Self {
197 self.config.wrapping_mode = mode;
198 self
199 }
200
201 pub fn use_italics(&mut self, yes: bool) -> &mut Self {
203 self.config.use_italic_text = yes;
204 self
205 }
206
207 #[cfg(feature = "paging")]
209 pub fn paging_mode(&mut self, mode: PagingMode) -> &mut Self {
210 self.config.paging_mode = mode;
211 self
212 }
213
214 #[cfg(feature = "paging")]
216 pub fn pager(&mut self, cmd: &'a str) -> &mut Self {
217 self.config.pager = Some(cmd);
218 self
219 }
220
221 pub fn line_ranges(&mut self, ranges: LineRanges) -> &mut Self {
223 self.config.visible_lines = VisibleLines::Ranges(ranges);
224 self
225 }
226
227 pub fn highlight(&mut self, line: usize) -> &mut Self {
231 self.highlighted_lines.push(LineRange::new(line, line));
232 self
233 }
234
235 pub fn highlight_range(&mut self, from: usize, to: usize) -> &mut Self {
239 self.highlighted_lines.push(LineRange::new(from, to));
240 self
241 }
242
243 pub fn squeeze_empty_lines(&mut self, maximum: Option<usize>) -> &mut Self {
245 self.config.squeeze_lines = maximum;
246 self
247 }
248
249 pub fn theme(&mut self, theme: impl AsRef<str>) -> &mut Self {
253 self.config.theme = theme.as_ref().to_owned();
254 self
255 }
256
257 pub fn syntax_mapping(&mut self, mapping: SyntaxMapping<'a>) -> &mut Self {
259 self.config.syntax_mapping = mapping;
260 self
261 }
262
263 pub fn themes(&self) -> impl Iterator<Item = &str> {
264 self.assets.themes()
265 }
266
267 pub fn syntaxes(&self) -> impl Iterator<Item = Syntax> + '_ {
268 self.assets
271 .get_syntaxes()
272 .unwrap()
273 .iter()
274 .filter(|s| !s.hidden)
275 .map(|s| Syntax {
276 name: s.name.clone(),
277 file_extensions: s.file_extensions.clone(),
278 })
279 }
280
281 pub fn print(&mut self) -> Result<bool> {
285 self.print_with_writer(None::<&mut dyn std::fmt::Write>)
286 }
287
288 pub fn print_with_writer<W: std::fmt::Write>(&mut self, writer: Option<W>) -> Result<bool> {
290 let highlight_lines = std::mem::take(&mut self.highlighted_lines);
291 self.config.highlighted_lines = HighlightedLineRanges(LineRanges::from(highlight_lines));
292 self.config.term_width = self
293 .term_width
294 .unwrap_or_else(|| Term::stdout().size().1 as usize);
295
296 self.config.style_components.clear();
297 if self.active_style_components.grid {
298 self.config.style_components.insert(StyleComponent::Grid);
299 }
300 if self.active_style_components.rule {
301 self.config.style_components.insert(StyleComponent::Rule);
302 }
303 if self.active_style_components.header_filename {
304 self.config
305 .style_components
306 .insert(StyleComponent::HeaderFilename);
307 }
308 if self.active_style_components.line_numbers {
309 self.config
310 .style_components
311 .insert(StyleComponent::LineNumbers);
312 }
313 if self.active_style_components.snip {
314 self.config.style_components.insert(StyleComponent::Snip);
315 }
316 #[cfg(feature = "git")]
317 if self.active_style_components.vcs_modification_markers {
318 self.config.style_components.insert(StyleComponent::Changes);
319 }
320
321 let inputs = std::mem::take(&mut self.inputs);
323
324 let controller = Controller::new(&self.config, &self.assets);
326
327 if let Some(mut w) = writer {
329 controller.run(
330 inputs.into_iter().map(|i| i.into()).collect(),
331 Some(&mut OutputHandle::FmtWrite(&mut w)),
332 )
333 } else {
334 controller.run(inputs.into_iter().map(|i| i.into()).collect(), None)
335 }
336 }
337}
338
339impl Default for PrettyPrinter<'_> {
340 fn default() -> Self {
341 Self::new()
342 }
343}
344
345pub struct Input<'a> {
347 input: input::Input<'a>,
348}
349
350impl<'a> Input<'a> {
351 pub fn from_reader<R: Read + 'a>(reader: R) -> Self {
353 input::Input::from_reader(Box::new(reader)).into()
354 }
355
356 pub fn from_file(path: impl AsRef<Path>) -> Self {
358 input::Input::ordinary_file(path).into()
359 }
360
361 pub fn from_bytes(bytes: &'a [u8]) -> Self {
363 Input::from_reader(bytes)
364 }
365
366 pub fn from_stdin() -> Self {
368 input::Input::stdin().into()
369 }
370
371 pub fn name(mut self, name: impl AsRef<Path>) -> Self {
374 self.input = self.input.with_name(Some(name));
375 self
376 }
377
378 pub fn kind(mut self, kind: impl Into<String>) -> Self {
380 let kind = kind.into();
381 self.input
382 .description_mut()
383 .set_kind(if kind.is_empty() { None } else { Some(kind) });
384 self
385 }
386
387 pub fn title(mut self, title: impl Into<String>) -> Self {
390 self.input.description_mut().set_title(Some(title.into()));
391 self
392 }
393}
394
395impl<'a> From<input::Input<'a>> for Input<'a> {
396 fn from(input: input::Input<'a>) -> Self {
397 Self { input }
398 }
399}
400
401impl<'a> From<Input<'a>> for input::Input<'a> {
402 fn from(Input { input }: Input<'a>) -> Self {
403 input
404 }
405}