markplus_render 0.1.0

HTML and PDF renderer for the MarkPlus ecosystem (AST → HTML / Typst → PDF)
Documentation
//    Copyright [2026] [Purnendu Kumar]
//    Apache-2.0 License

//! End-to-end example: parse a Markdown document, render HTML, Typst source,
//! and (on native) compile a PDF.
//!
//! Run with:
//!   cargo run --example render_doc -- path/to/document.md
//!
//! Outputs are written alongside the input:
//!   document.html, document.typ, document.pdf

use std::path::PathBuf;
use markplus_core::parse_document;
use markplus_render::RenderEngine;

fn main() -> anyhow::Result<()> {
    let md_path: PathBuf = std::env::args()
        .nth(1)
        .map(PathBuf::from)
        .unwrap_or_else(|| {
            // Default: use a small inline example when no arg is given
            eprintln!("No path given — using built-in example document.");
            PathBuf::from("example.md")
        });

    // --- Read source ---------------------------------------------------
    let md_src = if md_path.exists() {
        std::fs::read_to_string(&md_path)?
    } else {
        // Inline demo document so the example runs without any file
        r#"---
title: MarkPlus Render Demo
author: Demo User
date: 2026-06-07
tags:
  - demo
  - markplus
---
# MarkPlus Render Demo

This is a **demonstration** document rendered by `markplus_render`.

## Features

- Headings with levels 1–6
- **Bold**, _italic_, and `inline code`
- Fenced code blocks with syntax names
- Math: inline $E = mc^2$ and display:

$$
\int_0^\infty e^{-x}\,dx = 1
$$

## Code Example

```rust
fn greet(name: &str) -> String {
    format!("Hello, {name}!")
}
```

## Table

| Feature | Native | Wasm |
| ------- | ------ | ---- |
| HTML    | ✓      | ✓    |
| Typst   | ✓      | ✓    |
| PDF     | ✓      | opt  |

---

*Generated by markplus_render.*
"#.to_owned()
    };

    // --- Parse -----------------------------------------------------------
    let asset = parse_document(&md_src)?;
    println!("Parsed {} AST nodes", asset.ast.len());

    // --- Build engine from templates dir ---------------------------------
    let templates_dir = PathBuf::from(
        std::env::var("MARKPLUS_TEMPLATES").unwrap_or_else(|_| "templates".into())
    );

    let engine = if templates_dir.exists() {
        RenderEngine::builder()
            .with_templates(&templates_dir)
            .build()?
    } else {
        // Fall back to in-memory defaults when run from any working dir
        let html_tpl = include_str!("../templates/default/article.html.tera");
        let typ_tpl  = include_str!("../templates/default/article.typ.tera");
        RenderEngine::builder()
            .build_with_templates(std::collections::HashMap::from([
                ("default/article.html.tera".into(), html_tpl.into()),
                ("default/article.typ.tera".into(),  typ_tpl.into()),
            ]))?
    };

    // --- Render HTML -----------------------------------------------------
    let html = engine.render_html(&asset, "default/article.html.tera")?;
    let html_path = md_path.with_extension("html");
    std::fs::write(&html_path, &html)?;
    println!("HTML written → {}", html_path.display());

    // --- Render Typst source ---------------------------------------------
    let typ_src = engine.render_typst_string(&asset, "default/article.typ.tera")?;
    let typ_path = md_path.with_extension("typ");
    std::fs::write(&typ_path, &typ_src)?;
    println!("Typst written → {}", typ_path.display());

    // --- Compile PDF (native only) ---------------------------------------
    println!("Compiling PDF via typst-as-lib...");
    match engine.compile_pdf(&typ_src) {
        Ok(pdf_bytes) => {
            let pdf_path = md_path.with_extension("pdf");
            std::fs::write(&pdf_path, &pdf_bytes)?;
            println!("PDF written  → {} ({} bytes)", pdf_path.display(), pdf_bytes.len());
        }
        Err(e) => eprintln!("PDF compile skipped: {e}"),
    }

    Ok(())
}