use crate::config::{RouteConfig, RuitlConfig};
use crate::error::{Result, RuitlError};
use std::fs;
use std::path::{Path, PathBuf};
fn route_to_file(out_dir: &Path, url_path: &str) -> PathBuf {
let trimmed = url_path.trim_matches('/');
if trimmed.is_empty() {
return out_dir.join("index.html");
}
out_dir.join(trimmed).join("index.html")
}
pub fn render_site<F>(cfg: &RuitlConfig, out_dir: &Path, mut renderer: F) -> Result<Vec<PathBuf>>
where
F: FnMut(&str, &str) -> Result<String>,
{
fs::create_dir_all(out_dir)
.map_err(|e| RuitlError::config(format!("create out dir {}: {}", out_dir.display(), e)))?;
let mut written = Vec::with_capacity(cfg.routes.len());
for route in &cfg.routes {
let output = render_route(route, out_dir, &mut renderer)?;
written.push(output);
}
Ok(written)
}
fn render_route<F>(route: &RouteConfig, out_dir: &Path, renderer: &mut F) -> Result<PathBuf>
where
F: FnMut(&str, &str) -> Result<String>,
{
let props_json = fs::read_to_string(&route.props_file).map_err(|e| {
RuitlError::config(format!(
"read props file {}: {}",
route.props_file.display(),
e
))
})?;
let html = renderer(&route.component, &props_json)?;
let target = route_to_file(out_dir, &route.path);
if let Some(parent) = target.parent() {
fs::create_dir_all(parent).map_err(|e| {
RuitlError::config(format!("create {}: {}", parent.display(), e))
})?;
}
fs::write(&target, html)
.map_err(|e| RuitlError::config(format!("write {}: {}", target.display(), e)))?;
Ok(target)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn route_to_file_maps_root_to_index() {
let got = route_to_file(Path::new("/dist"), "/");
assert_eq!(got, PathBuf::from("/dist/index.html"));
}
#[test]
fn route_to_file_preserves_nested_path() {
let got = route_to_file(Path::new("/dist"), "/blog/post");
assert_eq!(got, PathBuf::from("/dist/blog/post/index.html"));
}
}