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