Skip to content

ripgrep crates/printer/src/lib.rs: Code Companion

Reference code for the Printer Architecture lecture. Sections correspond to the lecture document.


Section 1: The Public API Surface

/*!
This crate provides featureful and fast printers that interoperate with the
[`grep-searcher`](https://docs.rs/grep-searcher)
crate.

# Brief overview

The [`Standard`] printer shows results in a human readable format...
The [`JSON`] printer shows results in a machine readable format...
The [`Summary`] printer shows *aggregate* results for a single search...

# Example

This example shows how to create a "standard" printer and execute a search.
use { grep_regex::RegexMatcher, grep_printer::Standard, grep_searcher::Searcher, };

// Sample text to search - note the byte string literal (&[u8]) const SHERLOCK: &'static [u8] = b"\ For the Doctor Watsons of this world, as opposed to the Sherlock Holmeses, success in the province of detective work must always ... ";

let matcher = RegexMatcher::new(r"Sherlock")?; let mut printer = Standard::new_no_color(vec![]);

// Key integration point: printer.sink() creates a Sink adapter // that the Searcher can write matches to Searcher::new().search_slice(&matcher, SHERLOCK, printer.sink(&matcher))?;

// Unwrap nested writers: Standard>> -> NoColor> -> Vec let output = String::from_utf8(printer.into_inner().into_inner())?;

*/

The doc comment serves as both documentation and a compile-time test. The example demonstrates the full integration pattern between searcher and printer crates.


Section 2: The Re-export Strategy

pub use crate::{
    // Color-related types for terminal output styling
    color::{ColorError, ColorSpecs, UserColorSpec, default_color_specs},

    // Hyperlink support for modern terminals (clickable file paths)
    hyperlink::{
        HyperlinkAlias, HyperlinkConfig, HyperlinkEnvironment,
        HyperlinkFormat, HyperlinkFormatError, hyperlink_aliases,
    },

    // Simple path-only output (just prints matching file paths)
    path::{PathPrinter, PathPrinterBuilder},

    // Human-readable grep-style output
    standard::{Standard, StandardBuilder, StandardSink},

    // Match counting and statistics
    stats::Stats,

    // Aggregate results (counts, file lists)
    summary::{Summary, SummaryBuilder, SummaryKind, SummarySink},
};

Notice the pattern: each major type (Standard, Summary, PathPrinter) has a corresponding Builder and often a Sink type. Users import from the crate root, not the internal modules.


Section 3: Conditional Compilation for Optional Features

// Module-level attribute ensures docs.rs shows feature requirements
#![cfg_attr(docsrs, feature(doc_cfg))]

// JSON types only compiled when serde feature is enabled
#[cfg(feature = "serde")]
pub use crate::json::{JSON, JSONBuilder, JSONSink};

// ... later in the file, the implementing modules are also gated:

#[cfg(feature = "serde")]
mod json;
#[cfg(feature = "serde")]
mod jsont;  // JSON type definitions (schemas)

Both the pub use re-exports and the mod declarations must be gated. This ensures the types, their implementations, and all dependencies are consistently included or excluded.


Section 4: The Look-Ahead Kludge

// The maximum number of bytes to execute a search to account for look-ahead.
//
// This is an unfortunate kludge since PCRE2 doesn't provide a way to search
// a substring of some input while accounting for look-ahead. In theory, we
// could refactor the various 'grep' interfaces to account for it, but it would
// be a large change. So for now, we just let PCRE2 go looking a bit for a
// match without searching the entire rest of the contents.
//
// Note that this kludge is only active in multi-line mode.
const MAX_LOOK_AHEAD: usize = 128;

This constant demonstrates pragmatic software engineering: documenting the ideal solution (interface refactoring), explaining why the workaround exists (PCRE2 look-ahead), and noting its scope (multi-line mode only).


Section 5: Module Organization and Privacy

// Makes macros from this module available throughout the crate
// without explicit imports (older Rust pattern)
#[macro_use]
mod macros;

// Public API implementations (exposed via pub use above)
mod color;
mod hyperlink;
mod path;
mod standard;
mod stats;
mod summary;

// Internal utilities (not in pub use - pure implementation details)
mod counter;
mod util;

// Feature-gated modules for JSON output
#[cfg(feature = "serde")]
mod json;      // Printer implementation
#[cfg(feature = "serde")]
mod jsont;     // Type definitions (schemas)

The module list shows what's exposed publicly (via pub use) versus what remains internal (counter, util). Module privacy is the first layer of API control.


Section 6: The Sink Pattern and Trait Integration

// From the documentation example - the key integration point:

let matcher = RegexMatcher::new(r"Sherlock")?;
let mut printer = Standard::new_no_color(vec![]);

// printer.sink(&matcher) creates a StandardSink that implements
// the Sink trait expected by Searcher::search_slice
Searcher::new().search_slice(
    &matcher,           // Used for matching
    SHERLOCK,           // Input bytes to search
    printer.sink(&matcher)  // Sink adapter for output
)?;

The sink(&matcher) method creates a bridge between the printer and searcher crates. The Searcher doesn't know about formatting—it just writes matches to anything implementing Sink.

┌─────────────────────────────────────────────────────┐
│                  Data Flow                          │
├─────────────────────────────────────────────────────┤
│                                                     │
│  Input → Searcher → Sink (StandardSink) → Printer  │
│             ↓           ↓                           │
│          Matcher    Formatting                      │
│                                                     │
└─────────────────────────────────────────────────────┘

Section 7: The Builder Pattern Ecosystem Continued

// Types re-exported from lib.rs follow consistent naming:

// Standard printer ecosystem
Standard         // The printer itself
StandardBuilder  // Configures and builds Standard
StandardSink     // Implements Sink trait for Searcher integration

// Summary printer ecosystem  
Summary          // The printer
SummaryBuilder   // Configuration
SummaryKind      // Enum for output variants
SummarySink      // Sink implementation

// JSON printer ecosystem (feature-gated)
JSON             // The printer
JSONBuilder      // Configuration
JSONSink         // Sink implementation

// Path printer ecosystem
PathPrinter      // Simple path-only output
PathPrinterBuilder  // Configuration

Every major printer type follows the same pattern: a main type, a builder for configuration, and (for search integration) a sink adapter.


Quick Reference

Printer Types Summary

Printer Purpose Output Format Sink Type
Standard Human-readable results grep-style lines StandardSink
Summary Aggregate statistics counts/file lists SummarySink
JSON Machine-readable JSON Lines JSONSink
PathPrinter File paths only one path per line N/A

Feature Flags

Feature Enables Dependencies
serde JSON, JSONBuilder, JSONSink serde, serde_json

Module Visibility

Module Public Types Notes
color ColorError, ColorSpecs, UserColorSpec Terminal coloring
hyperlink HyperlinkConfig, HyperlinkFormat, etc. Clickable links
standard Standard, StandardBuilder, StandardSink Main grep output
summary Summary, SummaryBuilder, SummaryKind, SummarySink Aggregate results
stats Stats Match counting
path PathPrinter, PathPrinterBuilder Path-only output
counter (none) Internal only
util (none) Internal only

Integration Pattern

// 1. Create matcher
let matcher = RegexMatcher::new(pattern)?;

// 2. Create and configure printer
let mut printer = StandardBuilder::new()
    .color_specs(specs)
    .build(writer);

// 3. Create sink from printer
let sink = printer.sink(&matcher);

// 4. Execute search with sink
Searcher::new().search_slice(&matcher, input, sink)?;