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.
// 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
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)?;