rusty-pdfgrep 0.1.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
//! US4 (Library API) integration tests.

use rusty_pdfgrep::{Match, PdfGrepBuilder, PdfGrepError};
use static_assertions::assert_impl_all;

#[test]
fn send_sync_bounds_sc033() {
    assert_impl_all!(rusty_pdfgrep::PdfGrep: Send);
    assert_impl_all!(PdfGrepBuilder: Send, Sync);
    assert_impl_all!(Match: Send, Sync);
    assert_impl_all!(PdfGrepError: Send, Sync);
}

#[test]
fn builder_build_invalid_regex_returns_err_sc026() {
    let err = PdfGrepBuilder::new()
        .pattern("[invalid")
        .build()
        .unwrap_err();
    assert!(matches!(err, PdfGrepError::RegexCompile { .. }));
}

#[test]
fn builder_build_reverse_page_range_returns_err() {
    let err = PdfGrepBuilder::new()
        .pattern("x")
        .page_range(Some((5, 3)))
        .build()
        .unwrap_err();
    assert!(matches!(err, PdfGrepError::PageRange { .. }));
}

#[test]
fn builder_password_appends_in_order_sc029() {
    // Behavioral test for password append semantics (Clarifications Q4).
    // Builder retains passwords in flag-order.
    let g = PdfGrepBuilder::new()
        .pattern("x")
        .password("first")
        .password("second")
        .password("third")
        .build()
        .unwrap();
    assert_eq!(g.passwords(), &["first", "second", "third"]);
}

#[test]
fn builder_setters_are_independent() {
    let canonical = PdfGrepBuilder::new()
        .pattern("foo")
        .case_insensitive(true)
        .max_count(Some(5))
        .build()
        .unwrap();
    let shuffled = PdfGrepBuilder::new()
        .max_count(Some(5))
        .case_insensitive(true)
        .pattern("foo")
        .build()
        .unwrap();
    assert_eq!(canonical.max_count(), shuffled.max_count());
}

#[test]
fn pdf_grep_error_supports_source_chain_sc030() {
    use std::error::Error;
    let err = PdfGrepError::Io {
        path: std::path::PathBuf::from("/tmp/test"),
        source: std::io::Error::new(std::io::ErrorKind::NotFound, "test"),
    };
    assert!(err.source().is_some(), "Io variant must expose source()");

    let leaf = PdfGrepError::Encrypted {
        path: std::path::PathBuf::from("/tmp/enc"),
    };
    assert!(leaf.source().is_none(), "Encrypted is a leaf variant");
}

#[test]
fn fixed_strings_treats_pattern_as_literal_sc014() {
    let g = PdfGrepBuilder::new()
        .pattern("(group)")
        .fixed_strings(true)
        .build()
        .unwrap();
    // The engine should be Regex-backed (escaped) and find the literal.
    let _ = g; // construction succeeds; engine selection asserted in unit tests
}

#[test]
fn perl_regexp_engine_compiles_lookahead_sc015() {
    let g = PdfGrepBuilder::new()
        .pattern("foo(?=bar)")
        .perl_regexp(true)
        .build();
    assert!(g.is_ok(), "fancy-regex must accept lookahead");
}