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
//! Shared fixture helpers for integration tests (T036, HINT-006).

use assert_cmd::Command;

/// Command for the `rusty-pdfgrep` binary.
pub fn rusty_pdfgrep_cmd() -> Command {
    Command::cargo_bin("rusty-pdfgrep").expect("binary built")
}

/// Build a minimal valid PDF byte stream containing a single page with the
/// given UTF-8 text using a hand-crafted PDF structure.
///
/// The generated PDF is the smallest possible valid PDF that lopdf can parse
/// and extract text from. Used to avoid pulling in printpdf as a dev-dep.
#[allow(dead_code)]
pub fn minimal_pdf(text: &str) -> Vec<u8> {
    let escaped: String = text
        .chars()
        .flat_map(|c| match c {
            '(' => "\\(".chars().collect::<Vec<_>>(),
            ')' => "\\)".chars().collect::<Vec<_>>(),
            '\\' => "\\\\".chars().collect::<Vec<_>>(),
            other => vec![other],
        })
        .collect();
    let content = format!("BT /F1 12 Tf 50 700 Td ({escaped}) Tj ET");
    let content_len = content.len();
    let mut pdf = format!(
        "%PDF-1.4\n\
         1 0 obj\n<< /Type /Catalog /Pages 2 0 R >>\nendobj\n\
         2 0 obj\n<< /Type /Pages /Kids [3 0 R] /Count 1 >>\nendobj\n\
         3 0 obj\n<< /Type /Page /Parent 2 0 R /MediaBox [0 0 612 792] /Resources << /Font << /F1 4 0 R >> >> /Contents 5 0 R >>\nendobj\n\
         4 0 obj\n<< /Type /Font /Subtype /Type1 /BaseFont /Helvetica >>\nendobj\n\
         5 0 obj\n<< /Length {content_len} >>\nstream\n{content}\nendstream\nendobj\n"
    );
    let xref_offset = pdf.len();
    pdf.push_str(&format!(
        "xref\n0 6\n\
         0000000000 65535 f \n\
         0000000009 00000 n \n\
         0000000058 00000 n \n\
         0000000110 00000 n \n\
         0000000219 00000 n \n\
         0000000282 00000 n \n\
         trailer\n<< /Size 6 /Root 1 0 R >>\nstartxref\n{xref_offset}\n%%EOF\n"
    ));
    pdf.into_bytes()
}

/// Helper: write a minimal PDF into a tempfile and return its path.
#[allow(dead_code)]
pub fn make_test_pdf(text: &str) -> (tempfile::TempDir, std::path::PathBuf) {
    let dir = tempfile::tempdir().expect("tempdir");
    let path = dir.path().join("test.pdf");
    std::fs::write(&path, minimal_pdf(text)).expect("write");
    (dir, path)
}