mini-docs 0.3.5

A minimal, secure build-time Markdown to HTML generator for the mini-* family.
Documentation
//! Proves mini-docs and mini-static compose through the filesystem alone: mini-docs
//! builds `.md` + Tera templates into `.html` (plus a `data.json` page index), and
//! mini-static serves the result — with live-reload, in debug builds, driven by
//! mini-static's own independent mtime poller. Neither crate imports a type from the
//! other; this example is the only place in the workspace that depends on both.
//!
//! Run with `cargo run -p mini-docs --example mini-docs-static`, then open
//! http://localhost:8080/. Edit a file under `examples/fixtures/docs/` while it's
//! running and watch the page rebuild and live-reload.

use std::env;
use std::path::Path;
use std::thread;
use std::time::Duration;

use mini_docs::Builder;
use mini_static::Server;

#[tokio::main(flavor = "current_thread")]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let port = env::var("PORT")
        .unwrap_or_else(|_| "8080".to_string())
        .parse::<u16>()?;

    let manifest_dir = Path::new(env!("CARGO_MANIFEST_DIR"));
    let docs = manifest_dir.join("examples/fixtures/docs");
    let templates = manifest_dir.join("examples/fixtures/templates");
    let output = manifest_dir.join("examples/fixtures/.output");

    let builder = Builder::new(docs.clone())
        .templates(templates)
        .output(output.clone())
        .default_template("page.html")
        .link_base("/")
        .data_json("data.json");

    // Initial build so mini-static has something to serve the moment it starts.
    builder.build()?;

    // Background polling rebuild loop. mini-docs's `Watcher` is deliberately
    // synchronous and dependency-free (see `Watcher::tick`'s doc comment) — a plain
    // `std::thread` drives it here, not a tokio task. This is the "other half" of the
    // composition this example demonstrates: mini-docs rewrites `.html` on a tick,
    // and mini-static's own independent mtime poller (enabled below via
    // `with_live_reload`) notices the change and pushes its own SSE reload. The two
    // pollers never call into each other — only the filesystem connects them.
    //
    // `builder.watch()` performs its own initial `build()` internally, redoing the
    // walk we already did above — a deliberate, cheap redundancy (everything is
    // already cache-hit up to date) traded for keeping the Watcher's lifetime
    // entirely inside this thread, rather than trying to share a borrow across it.
    thread::spawn(move || {
        let mut watcher = builder
            .watch()
            .expect("watch() should succeed right after build()");
        loop {
            thread::sleep(Duration::from_millis(300));
            if let Err(e) = watcher.tick() {
                eprintln!("mini-docs rebuild error: {e}");
            }
        }
    });

    let server = Server::new(&output)?;
    #[cfg(debug_assertions)]
    let server = server.with_live_reload();

    let (_port, handle) = server.run_all(port, Duration::from_secs(30)).await?;

    println!("mini-docs-static example listening on 0.0.0.0:{port}");
    println!("serving mini-docs output from: {}", output.display());
    println!(
        "edit files under {} and watch them rebuild + reload",
        docs.display()
    );
    #[cfg(debug_assertions)]
    println!("live-reload enabled at {}", mini_static::LIVE_RELOAD_PATH);

    tokio::signal::ctrl_c().await?;
    println!("shutting down...");
    handle.shutdown().await;

    Ok(())
}