reptr 0.8.0

Local-first pentest report generator: Markdown findings -> HTML/JSON/DOCX/PDF.
Documentation
use std::path::Path;

use anyhow::{bail, Result};
use console::style;

use crate::model::{validate_engagement, Engagement};
use crate::parse::{load_engagement_config, load_findings};
use crate::render::render_all;

pub fn run(root: &Path) -> Result<()> {
    let started = std::time::Instant::now();

    let (cfg, client) = load_engagement_config(root)?;
    let findings = load_findings(&root.join("findings"))?;
    println!("{} {} findings", style("✓ Parsed").green(), findings.len());

    let mut engagement = Engagement {
        meta: cfg.engagement,
        client,
        findings,
        appendices: vec![],
        output: cfg.output,
        template: cfg.template,
        severity_thresholds: cfg.severity_thresholds,
        library: cfg.library,
    };
    engagement.sort_findings();

    let errors = validate_engagement(&engagement);
    if !errors.is_empty() {
        eprintln!("{}", style("✗ Validation failed:").red().bold());
        for err in &errors {
            eprintln!("{err}");
        }
        bail!("{} validation error(s)", errors.len());
    }

    let produced = render_all(root, &engagement)?;
    for path in &produced {
        let ext = path
            .extension()
            .and_then(|x| x.to_str())
            .unwrap_or("")
            .to_uppercase();
        println!(
            "{} {:<5}{}",
            style("✓ Rendered").green(),
            ext,
            path.display()
        );
    }

    let elapsed = started.elapsed();
    println!("Done in {:.1?}.", elapsed);
    Ok(())
}