printwell-cli 0.1.11

Command-line tool for HTML to PDF conversion
Documentation
//! Batch conversion command implementation.

use anyhow::{Context, Result};
use std::path::Path;
use std::time::Instant;

use crate::cli::args::ConvertBatchArgs;
use crate::cli::utils::output;

pub async fn convert_batch(args: ConvertBatchArgs) -> Result<()> {
    use printwell::{ConversionJob, ConverterPool, Orientation, PageSize, PdfOptions};

    // Parse page size
    let page_size = match args.page_size.to_uppercase().as_str() {
        "A3" => PageSize::A3,
        "A4" => PageSize::A4,
        "A5" => PageSize::A5,
        "LETTER" => PageSize::Letter,
        "LEGAL" => PageSize::Legal,
        "TABLOID" => PageSize::Tabloid,
        _ => anyhow::bail!(
            "Unknown page size: {}. Use A3, A4, A5, Letter, Legal, or Tabloid",
            args.page_size
        ),
    };

    // Create output directory if needed
    std::fs::create_dir_all(&args.output_dir)
        .with_context(|| format!("Failed to create output directory: {}", args.output_dir))?;

    output::note(format!(
        "Starting batch conversion of {} files with {} workers...",
        args.inputs.len(),
        args.workers
    ));

    let pool = ConverterPool::new(args.workers).context("Failed to create converter pool")?;
    let start = Instant::now();

    // Build jobs
    let mut jobs = Vec::new();
    for input in &args.inputs {
        let html = if input.starts_with("http://") || input.starts_with("https://") {
            // Fetch URL content
            let resource_options = printwell::ResourceOptions::default();
            let resp = printwell::fetch_url(input, &resource_options)
                .map_err(|e| anyhow::anyhow!("Failed to fetch URL {input}: {e}"))?;
            resp.into_bytes()
        } else if input == "-" {
            // Read from stdin
            use std::io::Read;
            let mut content = String::new();
            std::io::stdin()
                .read_to_string(&mut content)
                .context("Failed to read from stdin")?;
            content.into_bytes()
        } else {
            // Read file
            std::fs::read(input).with_context(|| format!("Failed to read input file: {input}"))?
        };

        let html_str = String::from_utf8_lossy(&html).to_string();

        let pdf_options = PdfOptions::builder()
            .page_size(page_size)
            .orientation(if args.landscape {
                Orientation::Landscape
            } else {
                Orientation::Portrait
            })
            .print_background(args.background)
            .build();

        jobs.push((
            input.clone(),
            ConversionJob::html(html_str).with_pdf_options(pdf_options),
        ));
    }

    // Convert all files
    let mut success = 0;
    let mut failed = 0;

    for (input, job) in jobs {
        let output_name = if input.starts_with("http") {
            // Extract filename from URL
            input
                .split('/')
                .next_back()
                .unwrap_or("output")
                .replace(".html", ".pdf")
        } else if input == "-" {
            "stdin.pdf".to_string()
        } else {
            Path::new(&input).file_stem().map_or_else(
                || "output.pdf".to_string(),
                |s| format!("{}.pdf", s.to_string_lossy()),
            )
        };

        let output_path = Path::new(&args.output_dir).join(&output_name);

        match pool.convert(job).await {
            Ok(pdf) => {
                if let Err(e) = pdf.write_to_file(&output_path) {
                    output::batch_fail(&input, format!("Failed to write: {e}"));
                    failed += 1;
                } else {
                    output::batch_ok(&input, &output_path, pdf.page_count());
                    success += 1;
                }
            }
            Err(e) => {
                output::batch_fail(&input, e);
                failed += 1;
            }
        }
    }

    let elapsed = start.elapsed();
    output::batch_summary(success, failed, elapsed.as_secs_f64());

    if failed > 0 {
        anyhow::bail!("{failed} file(s) failed to convert");
    }

    Ok(())
}