use std::env;
use std::fs;
use std::io;
use std::path::{Path, PathBuf};
fn main() -> io::Result<()> {
let out_dir = PathBuf::from(env::var("OUT_DIR").expect("OUT_DIR is set by Cargo"));
let out_file = out_dir.join("embedded_web_assets.rs");
let manifest_dir =
PathBuf::from(env::var("CARGO_MANIFEST_DIR").expect("CARGO_MANIFEST_DIR is set by Cargo"));
let web_dir = manifest_dir.join("web");
println!("cargo:rerun-if-changed={}", web_dir.display());
if !web_dir.exists() {
write_empty_assets(&out_file)?;
return Ok(());
}
let mut files = Vec::new();
collect_files(&web_dir, &web_dir, &mut files)?;
files.sort_by(|left, right| left.0.cmp(&right.0));
for (_, absolute) in &files {
println!("cargo:rerun-if-changed={}", absolute.display());
}
write_assets(&out_file, &files)
}
fn write_empty_assets(out_file: &Path) -> io::Result<()> {
fs::write(
out_file,
r#"pub struct WebAsset {
pub path: &'static str,
pub contents: &'static [u8],
pub mime: &'static str,
}
pub static ASSETS: &[WebAsset] = &[];
"#,
)
}
fn write_assets(out_file: &Path, files: &[(String, PathBuf)]) -> io::Result<()> {
let mut generated = String::from(
r#"pub struct WebAsset {
pub path: &'static str,
pub contents: &'static [u8],
pub mime: &'static str,
}
pub static ASSETS: &[WebAsset] = &[
"#,
);
for (relative, absolute) in files {
let path_literal = format!("{relative:?}");
let absolute_literal = format!("{:?}", absolute.to_string_lossy());
let mime_literal = format!("{:?}", mime_for_path(relative));
generated.push_str(" WebAsset {\n");
generated.push_str(&format!(" path: {path_literal},\n"));
generated.push_str(&format!(
" contents: include_bytes!({absolute_literal}),\n"
));
generated.push_str(&format!(" mime: {mime_literal},\n"));
generated.push_str(" },\n");
}
generated.push_str("];\n");
fs::write(out_file, generated)
}
fn collect_files(root: &Path, dir: &Path, files: &mut Vec<(String, PathBuf)>) -> io::Result<()> {
for entry in fs::read_dir(dir)? {
let entry = entry?;
let path = entry.path();
if path.is_dir() {
collect_files(root, &path, files)?;
continue;
}
if !path.is_file() {
continue;
}
let relative = path
.strip_prefix(root)
.expect("asset path is under root")
.to_string_lossy()
.replace('\\', "/");
files.push((relative, path));
}
Ok(())
}
fn mime_for_path(path: &str) -> &'static str {
match Path::new(path).extension().and_then(|ext| ext.to_str()) {
Some("css") => "text/css; charset=utf-8",
Some("html") => "text/html; charset=utf-8",
Some("ico") => "image/x-icon",
Some("js") => "text/javascript; charset=utf-8",
Some("json") => "application/json; charset=utf-8",
Some("png") => "image/png",
Some("svg") => "image/svg+xml",
Some("wasm") => "application/wasm",
_ => "application/octet-stream",
}
}