zim-studio 1.5.0

A Terminal-Based Audio Project Scaffold and Metadata System
Documentation
//! PDF export — walk a tree of markdown files and produce a single PDF
//! with a TOC, sectioned by directory and subsectioned by file.

pub mod assemble;
pub mod compile;
pub mod latex;
pub mod walk;

use crate::zimignore::ZimIgnore;
use std::error::Error;
use std::fs;
use std::path::{Path, PathBuf};

/// Inputs for one PDF generation run.
pub struct GenerateOptions {
    pub root: PathBuf,
    pub output: PathBuf,
    pub title: String,
    pub author: Option<String>,
    pub keep_tex: bool,
}

/// Result summary returned to the CLI handler.
pub struct GenerateStats {
    pub num_files: usize,
    pub num_dirs: usize,
    pub output: PathBuf,
    pub kept_tex: Option<PathBuf>,
}

/// Walk, render, and compile in one call.
pub fn generate(opts: &GenerateOptions) -> Result<GenerateStats, Box<dyn Error>> {
    let zimignore = ZimIgnore::load_for_directory(&opts.root);
    let tree = walk::walk(&opts.root, &zimignore)?;
    if tree.is_empty() {
        return Err(format!("no .md files found under {}", opts.root.display()).into());
    }

    let num_dirs = tree.len();
    let num_files: usize = tree.iter().map(|d| d.file_count()).sum();

    let latex_source = assemble::build_latex(
        &tree,
        &assemble::PdfOptions {
            title: opts.title.clone(),
            author: opts.author.clone(),
        },
    )?;

    let work_dir = std::env::temp_dir().join(format!("zim-pdf-{}", std::process::id()));
    let pdf_path = compile::compile(&latex_source, &work_dir)?;

    if let Some(parent) = opts.output.parent()
        && !parent.as_os_str().is_empty()
    {
        fs::create_dir_all(parent)?;
    }
    fs::copy(&pdf_path, &opts.output)?;

    let kept_tex = if opts.keep_tex {
        let target = sibling(&opts.output, "tex");
        fs::copy(compile::tex_path(&work_dir), &target)?;
        Some(target)
    } else {
        None
    };

    let _ = fs::remove_dir_all(&work_dir);

    Ok(GenerateStats {
        num_files,
        num_dirs,
        output: opts.output.clone(),
        kept_tex,
    })
}

fn sibling(p: &Path, ext: &str) -> PathBuf {
    let mut s = p.to_path_buf();
    s.set_extension(ext);
    s
}