Skip to main content

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}