md-pdf 0.1.2

Convert markdown files to PDF using typst with templating
//! Build script for md-pdf
//!
//! This script dynamically discovers all `.typ` template files in the `templates`
//! directory and generates a Rust module that embeds them into the binary using
//! `include_str!`. This allows new templates to be automatically included when
//! added to the templates folder.

use std::env;
use std::fs::{self, File};
use std::io::Write;
use std::path::Path;

fn main() {
  let out_dir = env::var("OUT_DIR").expect("OUT_DIR not set");
  let dest_path = Path::new(&out_dir).join("embedded_templates.rs");
  let mut file = File::create(&dest_path).expect("Could not create embedded_templates.rs");

  // Get the manifest directory (where Cargo.toml is)
  let manifest_dir = env::var("CARGO_MANIFEST_DIR").expect("CARGO_MANIFEST_DIR not set");
  let templates_dir = Path::new(&manifest_dir).join("templates");

  // Collect all .typ files in the templates directory
  let mut templates: Vec<(String, String)> = Vec::new();

  if templates_dir.exists() && templates_dir.is_dir() {
    for entry in fs::read_dir(&templates_dir).expect("Could not read templates directory") {
      let entry = entry.expect("Could not read directory entry");
      let path = entry.path();

      if path.is_file()
        && let Some(ext) = path.extension()
        && ext == "typ"
        && let Some(stem) = path.file_stem()
        && let Some(name) = stem.to_str()
      {
        let full_path = path.to_string_lossy().to_string();
        templates.push((name.to_string(), full_path));
      }
    }
  }

  // Sort templates by name for consistent ordering
  templates.sort_by(|a, b| a.0.cmp(&b.0));

  // Generate the Rust code
  writeln!(file, "// Auto-generated module containing embedded templates.").unwrap();
  writeln!(file, "// This file is generated by build.rs - do not edit manually.").unwrap();
  writeln!(file).unwrap();
  writeln!(file, "use std::collections::HashMap;").unwrap();
  writeln!(file).unwrap();

  // Generate a constant for each template
  for (name, path) in &templates {
    let const_name = name.to_uppercase().replace('-', "_");
    writeln!(file, "pub const TEMPLATE_{}: &str = include_str!(r#\"{}\"#);", const_name, path).unwrap();
  }

  writeln!(file).unwrap();

  // Generate list of embedded template names
  writeln!(file, "/// List of all embedded template names").unwrap();
  writeln!(file, "pub const EMBEDDED_TEMPLATE_NAMES: &[&str] = &[").unwrap();
  for (name, _) in &templates {
    writeln!(file, "    \"{}\",", name).unwrap();
  }
  writeln!(file, "];").unwrap();
  writeln!(file).unwrap();

  // Generate function to get template content by name
  writeln!(file, "/// Get embedded template content by name").unwrap();
  writeln!(file, "pub fn get_embedded_template(name: &str) -> Option<&'static str> {{").unwrap();
  writeln!(file, "    match name {{").unwrap();
  for (name, _) in &templates {
    let const_name = name.to_uppercase().replace('-', "_");
    writeln!(file, "        \"{}\" => Some(TEMPLATE_{}),", name, const_name).unwrap();
  }
  writeln!(file, "        _ => None,").unwrap();
  writeln!(file, "    }}").unwrap();
  writeln!(file, "}}").unwrap();
  writeln!(file).unwrap();

  // Generate function to get all embedded templates as a HashMap
  writeln!(file, "/// Get all embedded templates as a HashMap").unwrap();
  writeln!(file, "#[allow(dead_code)]").unwrap();
  writeln!(file, "pub fn get_all_embedded_templates() -> HashMap<&'static str, &'static str> {{").unwrap();
  writeln!(file, "    let mut map = HashMap::new();").unwrap();
  for (name, _) in &templates {
    let const_name = name.to_uppercase().replace('-', "_");
    writeln!(file, "    map.insert(\"{}\", TEMPLATE_{});", name, const_name).unwrap();
  }
  writeln!(file, "    map").unwrap();
  writeln!(file, "}}").unwrap();

  // Tell Cargo to rerun this script if templates change
  println!("cargo:rerun-if-changed=templates");
  for (_, path) in &templates {
    println!("cargo:rerun-if-changed={}", path);
  }
}