laterite-web 0.9.14

Laterite CMF public web layer: themes, static-site generation, page metadata, canonical URLs, and sitemaps
Documentation
//! A theme rendered to files: every page whose URL has no parameter, at its
//! URL, through the same render path the site serves, plus the theme's text
//! assets. The output is a plain directory for any static host.

use super::engine::Engine;
use super::router::Params;
use super::{site, store, Kind, ThemeError};
use crate::StaticSite;
use laterite_core::Db;
use std::path::Path;

/// What a build wrote.
#[derive(Debug, Default, Clone, PartialEq, Eq)]
pub struct BuildReport {
    /// The pages written, by URL.
    pub pages: Vec<String>,
    /// The assets written, by path under the output directory.
    pub assets: Vec<String>,
    /// The pages left out: a URL with parameters, or a hidden page.
    pub skipped: Vec<String>,
}

/// Renders the theme `code` into `out_dir` as a site at `base_url`. A page
/// at `/404` is also written as `404.html`, the file most static hosts serve
/// for an unmatched URL.
pub async fn build(
    db: &Db,
    code: &str,
    out_dir: &Path,
    base_url: &str,
) -> Result<BuildReport, ThemeError> {
    let theme = store::find_theme(db, code)
        .await?
        .ok_or_else(|| ThemeError::NotFound(code.to_string()))?;
    let origin = base_url.trim_end_matches('/').to_string();
    let engine = Engine::build_with(db, code, {
        let origin = origin.clone();
        move |env| site::add_url(env, origin)
    })
    .await?;
    let mut out = StaticSite::new(out_dir, &origin)?;
    let mut report = BuildReport::default();
    let mut names: Vec<&str> = engine.routes().names();
    names.sort_unstable();
    for name in names {
        let settings = engine.page(name).cloned().unwrap_or_default();
        let pattern = match settings.pattern() {
            Ok(p) => p,
            Err(_) => continue,
        };
        if !pattern.is_static() || settings.hidden {
            report.skipped.push(name.to_string());
            continue;
        }
        let url = pattern.source().to_string();
        let html = site::render_page(&engine, &theme, name, &Params::new(), &url, false, &origin)
            .map_err(ThemeError::Template)?;
        out.page(&url, &html)?;
        if url == "/404" {
            out.file("404.html", &html)?;
        }
        report.pages.push(url);
    }
    report.pages.sort();
    report.skipped.sort();
    for template in engine.names() {
        let Some(path) = template.strip_prefix("assets/") else {
            continue;
        };
        let body = engine
            .render(Kind::Asset, path, ())
            .map_err(ThemeError::Template)?;
        let target = format!("themes/{code}/assets/{path}");
        out.file(&target, &body)?;
        report.assets.push(target);
    }
    Ok(report)
}