rusty-pdfgrep 0.2.0

Grep through PDF files — a Rust port of Hans-Peter Deifel's `pdfgrep(1)` with lopdf-backed text extraction, regex + fancy-regex pluggable engines, --password retry for encrypted PDFs, GNU-grep-compatible color output, recursive walking with fnmatch include/exclude, and a typed library API.
Documentation

rusty-pdfgrep

Grep through PDF text content. Rust port of Hans-Peter Deifel's pdfgrep(1) 2.2.0.

crates.io docs.rs CI MSRV license: MIT OR Apache-2.0

lopdf-backed text extraction, pluggable regex engines (regex default, fancy-regex for -P), --password retry for encrypted PDFs, GNU-grep-compatible color output, & a typed library API. No C toolchain required: PCRE features come from pure-Rust fancy-regex. Default mode adds --help, --version, & a completions subcommand; Strict mode reverts to byte-equal pdfgrep 2.2.0 stderr.

Part of the Rusty portfolio.

Install

cargo install rusty-pdfgrep
# or, with prebuilt binaries:
cargo binstall rusty-pdfgrep
# or, download directly from GitHub Releases:
# https://github.com/jsh562/rusty-pdfgrep/releases

Usage

# Search a single PDF for a phrase
rusty-pdfgrep "experimental results" report.pdf

# Show which page each match lives on
rusty-pdfgrep -n "force majeure" contract.pdf

# Recursive search across a directory tree, case-insensitive
rusty-pdfgrep -r -i "compliance" ./contracts/

# Try multiple passwords against an encrypted PDF
rusty-pdfgrep --password "pwd1" --password "pwd2" "term" enc.pdf

# Fixed-string search (special regex chars are literal)
rusty-pdfgrep -F "$1.50" prices.pdf

# PCRE features (lookahead, backref) via the fancy-regex engine
rusty-pdfgrep -P "foo(?=bar)" file.pdf

# Count matches per file (no per-line output)
rusty-pdfgrep -c "TODO" *.pdf

# List only filenames containing matches (good for dialog --gauge style scripts)
rusty-pdfgrep -r -l "secret" ./reports/

# Strict pdfgrep-compat mode (drop-in pdfgrep 2.2.0 replacement)
rusty-pdfgrep --strict "term" file.pdf
RUSTY_PDFGREP_STRICT=1 rusty-pdfgrep "term" file.pdf
pdfgrep "term" file.pdf                    # via pdfgrep-alias argv[0] symlink

# Shell completions
rusty-pdfgrep completions bash              # > ~/.bash_completion.d/rusty-pdfgrep
rusty-pdfgrep completions zsh               # > ~/.zfunc/_rusty-pdfgrep
rusty-pdfgrep completions fish              # > ~/.config/fish/completions/rusty-pdfgrep.fish
rusty-pdfgrep completions powershell

Library API

The library exposes the PdfGrep / PdfGrepBuilder / Match / PageIterator / PdfGrepError types without any CLI deps. Use it when you want pdfgrep semantics inside another Rust tool.

use rusty_pdfgrep::PdfGrepBuilder;
use std::path::Path;

let pdfgrep = PdfGrepBuilder::new()
    .pattern("force majeure")
    .case_insensitive(true)
    .build()
    .unwrap();

for result in pdfgrep.search_file(Path::new("contract.pdf")) {
    let m = result.unwrap();
    println!("{}:{}: {}", m.path.display(), m.page, m.text);
}

For library-only consumers without CLI deps see the Cargo Features section.

Cargo Features

default enables full, which (for this tightly-coupled-capability port) resolves to the cli umbrella. pdfgrep-classic reproduces v0.1.x bare-port behavior matching upstream pdfgrep 2.2.0 1:1. To strip the CLI surface use default-features = false or --no-default-features & add the features you want.

rusty-pdfgrep is a tightly-coupled-capability port: its one documented job is "grep through PDF text content". The CLI sub-capabilities (recursive walking, GREP_COLORS color output, encrypted-PDF retry, --include / --exclude glob filters, Strict-mode dispatcher, completions subcommand) are tightly coupled inside the cli umbrella. No optional feature leaves are carved beyond the required umbrellas; see docs/feature-layout.md for why.

Feature matrix

Feature Description Umbrella(s)
cli All CLI-only dependencies (clap, clap_complete, anyhow, termcolor, anstyle, walkdir, globset) and the binary entry point, recursive walker, color writer, include/exclude glob filters, encrypted-PDF retry CLI dispatcher, Strict-mode argv pre-scanner, and completions subcommand. Library consumers strip via default-features = false. full, pdfgrep-classic, pdfgrep-minimal

Preset bundles

Bundle Composition Use case
pdfgrep-classic cli Drop-in upstream pdfgrep 2.2.0 replacement. Strict mode is invoked via --strict, RUSTY_PDFGREP_STRICT, or pdfgrep/pdfgrep-alias argv[0] auto-detect.
pdfgrep-minimal cli Explicit minimal-CLI alias for users who prefer the <port>-minimal naming convention seen across other portfolio ports. Identical composition to pdfgrep-classic.

Keep-list workaround (Cargo features are union-only)

Cargo features cannot subtract from default. To get "everything except a specific feature," disable defaults & enumerate the features you want:

cargo install rusty-pdfgrep --no-default-features --features "cli"
# → bare CLI. Equivalent to pdfgrep-classic / pdfgrep-minimal.

For the common cases the named preset bundles are usually sufficient.

Library-only consumers

[dependencies]
rusty-pdfgrep = { version = "0.2", default-features = false }

This strips clap, clap_complete, anyhow, termcolor, anstyle, walkdir, & globset. The resulting build pulls only thiserror (required by the always-on PdfGrepError enum) plus the foundational PDF + regex stack (lopdf + regex + fancy-regex). The CI test-no-default job runs cargo tree --no-default-features on every PR & fails the build if any CLI-only dep leaks back in.

Convention authority

This layout follows the portfolio-wide Cargo Features Convention. The "why" lives in ADR-0006; the "what" lives in project-instructions.md §Cargo Feature Surface. Every Rusty port from v0.2 onward exposes the same umbrella set (default / full / cli / <port>-classic), per-port leaves named in kebab-case, & 2 to 4 preset bundles.

Compatibility

rusty-pdfgrep has two modes:

  • Default mode. clap-styled flag parser. Conflicting flag pairs MUST be rejected at parse time. --help, --version, & the completions subcommand are all available.
  • Strict mode (activated by --strict, RUSTY_PDFGREP_STRICT=1, or invoking the binary as pdfgrep/pdfgrep-alias). Byte-equal stderr against upstream v2.2.0 for documented diagnostics. Last-wins flag resolution. --help, --version, & completions MUST be rejected.

-P / --perl-regexp engine

The -P engine is fancy-regex instead of upstream's libpcre2. Pure-Rust, no C toolchain. Edge-case PCRE features (recursive patterns, callouts, conditional patterns) diverge from upstream pdfgrep & MUST be tested against your specific patterns before relying on byte-equal results.

BREAKING-CHANGE vs upstream

stdin is buffered into memory with a configurable cap (default 512 MiB). Upstream pdfgrep buffers unbounded; rusty-pdfgrep refuses to OOM on huge inputs.

See docs/COMPATIBILITY.md for the full per-flag matrix.

What's not shipped

  • -w / --word-regexp. Not in upstream pdfgrep; omitted.
  • --password-list FILE. Upstream uses repeated --password flags; rusty-pdfgrep matches that surface.
  • -A / -B / -C page-context. Deferred.
  • --cache. Page-extraction cache; deferred.
  • --unac. Unicode accent normalization; deferred.
  • -R symlink-follow. Recursive walk uses walkdir's default (no follow). Deferred.
  • pdfium-render backend. lopdf covers the 99% case without a C toolchain dependency.
  • Unbounded stdin buffering. Capped at 512 MiB by default; configurable via the library builder.

MSRV

Rust 1.85 (edition 2024). Re-verified against the portfolio's stable-minus-two policy at each release.

License

Dual-licensed under MIT or Apache-2.0 at your option.