trg/print.rs
1//! Printer construction and output-mode dispatch. `path(true)` is mandatory:
2//! without it `sink_with_path` silently degrades to `sink` and member names
3//! vanish.
4
5use grep_printer::{
6 ColorSpecs, Standard, StandardBuilder, Summary, SummaryBuilder, SummaryKind, JSON, JSONBuilder,
7};
8use termcolor::Buffer;
9
10use crate::escape::Escaped;
11
12/// The mutually exclusive output modes.
13///
14/// Mutually exclusive because each one is a different answer to "what is a
15/// result": a line, a count, a path, or nothing at all. `cli` collapses the
16/// flags into exactly one of these, so nothing downstream has to re-derive
17/// precedence.
18#[derive(Debug, Clone, Copy, PartialEq, Eq)]
19pub enum Mode {
20 /// One line per match — the default.
21 Standard,
22 /// `-c`: one `member:count` line per member that matched.
23 Count,
24 /// `-l`: one `member` line per member that matched.
25 FilesWithMatches,
26 /// `-q`: nothing at all; the exit code is the whole report.
27 Quiet,
28 /// `--json`: one JSON object per event, JSON Lines.
29 Json,
30}
31
32pub struct PrintOpts {
33 pub only_matching: bool,
34 pub column: bool,
35 pub mode: Mode,
36 /// Resolved by `main` from `--color` plus whether stdout is a tty; by the
37 /// time it reaches here it is a decision, not a preference.
38 pub color: bool,
39 /// Resolved the same way as `color`, from `--escape` and stdout's tty-ness.
40 pub escape: bool,
41}
42
43impl Default for PrintOpts {
44 fn default() -> Self {
45 Self {
46 only_matching: false,
47 column: false,
48 mode: Mode::Standard,
49 color: false,
50 escape: false,
51 }
52 }
53}
54
55/// One archive's printer, in whichever mode the run asked for.
56///
57/// An enum rather than a trait object because the three printers' sinks are
58/// unrelated concrete types with different lifetimes, and boxing them would
59/// buy nothing: the match arms live in exactly one place, `search::search_member`.
60pub enum Printer<'a> {
61 Standard(Standard<Escaped<&'a mut Buffer>>),
62 Summary(Summary<Escaped<&'a mut Buffer>>),
63 Json(JSON<Escaped<&'a mut Buffer>>),
64}
65
66pub fn build<'a>(o: &PrintOpts, buf: &'a mut Buffer) -> Printer<'a> {
67 // On a `Buffer` built for a `ColorChoice::Never` writer these specs are
68 // inert, so passing them unconditionally would still be correct; gating
69 // keeps the "did the operator ask for colour" decision visible in one
70 // place instead of implied by the buffer's construction.
71 let specs =
72 if o.color { ColorSpecs::default_with_color() } else { ColorSpecs::default() };
73 // Every printer, not just the standard one: `-l` and `-c` print member
74 // names, and member names come out of the archive.
75 let buf = Escaped::new(buf, o.escape);
76
77 match o.mode {
78 Mode::Standard => Printer::Standard(
79 StandardBuilder::new()
80 .path(true)
81 // Never grouped under a path heading: every line is prefixed
82 // `archive:member:line`, and a heading would hide the archive
83 // the line came from — the one thing the operator is reading
84 // for. `-n`/`-N` reach the printer through `SearchOpts`, which
85 // is what `Searcher` consults, so there is nothing to mirror.
86 .heading(false)
87 .only_matching(o.only_matching)
88 .column(o.column)
89 .stats(false)
90 .color_specs(specs)
91 .build(buf),
92 ),
93 // The JSON schema carries the path, line number and offsets itself, so
94 // none of the standard printer's layout options apply.
95 //
96 // Its output is already JSON, so it gets JSON's escapes, not ours.
97 Mode::Json => Printer::Json(JSONBuilder::new().build(buf.into_json())),
98 // `-c`, `-l` and `-q` are all "one aggregate result per member", which
99 // is exactly what `Summary` is for. Writing the count line by hand
100 // instead would mean re-deriving `exclude_zero`, the field separator
101 // and the path colouring that this printer already gets right.
102 //
103 // `stats` stays off: none of these modes needs the extra statistics,
104 // and enabling it would force `QuietWithMatch` to keep searching a
105 // member after the first match purely to compute numbers nobody reads.
106 Mode::Count | Mode::FilesWithMatches | Mode::Quiet => {
107 let kind = match o.mode {
108 Mode::Count => SummaryKind::Count,
109 Mode::FilesWithMatches => SummaryKind::PathWithMatch,
110 _ => SummaryKind::QuietWithMatch,
111 };
112 Printer::Summary(
113 SummaryBuilder::new()
114 .kind(kind)
115 .path(true)
116 .stats(false)
117 .color_specs(specs)
118 .build(buf),
119 )
120 }
121 }
122}