use std::collections::BTreeMap;
use std::env;
use std::fs;
use std::io;
use std::path::{Path, PathBuf};
fn main() {
let manifest_dir = PathBuf::from(env::var("CARGO_MANIFEST_DIR").expect("CARGO_MANIFEST_DIR"));
let docs_root = manifest_dir.join("docs/crucible-rs");
let schemas_root = manifest_dir.join("schemas/crucible-rs");
let config_root = manifest_dir.join("config/crucible-rs");
let out_dir = PathBuf::from(env::var("OUT_DIR").expect("OUT_DIR"));
let out_file = out_dir.join("crucible_asset_index.rs");
let mut docs = Vec::new();
let mut schemas = Vec::new();
let mut config = Vec::new();
collect_files(&docs_root, &mut docs).expect("collect docs files");
collect_files(&schemas_root, &mut schemas).expect("collect schemas files");
collect_files(&config_root, &mut config).expect("collect config files");
println!("cargo:rerun-if-changed=docs/crucible-rs");
println!("cargo:rerun-if-changed=schemas/crucible-rs");
println!("cargo:rerun-if-changed=config/crucible-rs");
println!("cargo:rerun-if-changed=.crucible/metadata/metadata.yaml");
let generated = generate_index(
&manifest_dir,
&docs_root,
&schemas_root,
&config_root,
&docs,
&schemas,
&config,
);
fs::write(&out_file, generated).expect("write crucible_asset_index.rs");
}
fn collect_files(root: &Path, out: &mut Vec<PathBuf>) -> io::Result<()> {
if !root.exists() {
return Ok(());
}
let mut stack = vec![root.to_path_buf()];
while let Some(dir) = stack.pop() {
for entry in fs::read_dir(&dir)? {
let entry = entry?;
let path = entry.path();
if path.file_name().is_some_and(|n| n == ".DS_Store") {
continue;
}
let metadata = entry.metadata()?;
if metadata.is_dir() {
stack.push(path);
} else if metadata.is_file() {
out.push(path);
}
}
}
out.sort();
Ok(())
}
fn generate_index(
manifest_dir: &Path,
docs_root: &Path,
schemas_root: &Path,
config_root: &Path,
docs: &[PathBuf],
schemas: &[PathBuf],
config: &[PathBuf],
) -> String {
let docs_entries = files_to_entries(manifest_dir, docs_root, docs);
let schemas_entries = files_to_entries(manifest_dir, schemas_root, schemas);
let config_entries = files_to_entries(manifest_dir, config_root, config);
let mut out = String::new();
out.push_str("// @generated by build.rs; do not edit.\n");
out.push_str("// This file contains the embedded Crucible asset index.\n\n");
out.push_str("pub static DOCS: &[CrucibleAsset] = &[\n");
for (rel, len) in docs_entries.iter() {
out.push_str(&format!(
" CrucibleAsset {{ category: AssetCategory::Docs, path: \"{}\", bytes_len: {} }},\n",
escape(rel),
len
));
}
out.push_str("];\n\n");
out.push_str("pub static SCHEMAS: &[CrucibleAsset] = &[\n");
for (rel, len) in schemas_entries.iter() {
out.push_str(&format!(
" CrucibleAsset {{ category: AssetCategory::Schemas, path: \"{}\", bytes_len: {} }},\n",
escape(rel),
len
));
}
out.push_str("];\n\n");
out.push_str("pub static CONFIG: &[CrucibleAsset] = &[\n");
for (rel, len) in config_entries.iter() {
out.push_str(&format!(
" CrucibleAsset {{ category: AssetCategory::Config, path: \"{}\", bytes_len: {} }},\n",
escape(rel),
len
));
}
out.push_str("];\n\n");
out.push_str("pub fn open_doc(path: &str) -> Option<&'static [u8]> {\n");
out.push_str(" match path {\n");
for (rel, _) in docs_entries.iter() {
out.push_str(&format!(
" \"{}\" => Some(include_bytes!(concat!(env!(\"CARGO_MANIFEST_DIR\"), \"/docs/crucible-rs/{}\"))),\n",
escape(rel),
escape(rel)
));
}
out.push_str(" _ => None,\n");
out.push_str(" }\n");
out.push_str("}\n\n");
out.push_str("pub fn open_schema(path: &str) -> Option<&'static [u8]> {\n");
out.push_str(" match path {\n");
for (rel, _) in schemas_entries.iter() {
out.push_str(&format!(
" \"{}\" => Some(include_bytes!(concat!(env!(\"CARGO_MANIFEST_DIR\"), \"/schemas/crucible-rs/{}\"))),\n",
escape(rel),
escape(rel)
));
}
out.push_str(" _ => None,\n");
out.push_str(" }\n");
out.push_str("}\n\n");
out.push_str("pub fn open_config(path: &str) -> Option<&'static [u8]> {\n");
out.push_str(" match path {\n");
for (rel, _) in config_entries.iter() {
out.push_str(&format!(
" \"{}\" => Some(include_bytes!(concat!(env!(\"CARGO_MANIFEST_DIR\"), \"/config/crucible-rs/{}\"))),\n",
escape(rel),
escape(rel)
));
}
out.push_str(" _ => None,\n");
out.push_str(" }\n");
out.push_str("}\n");
out
}
fn files_to_entries(
manifest_dir: &Path,
root: &Path,
files: &[PathBuf],
) -> BTreeMap<String, usize> {
let mut out = BTreeMap::new();
for path in files {
let rel = path
.strip_prefix(root)
.unwrap_or(path)
.to_string_lossy()
.replace('\\', "/");
let len = fs::metadata(path).map(|m| m.len() as usize).unwrap_or(0);
out.insert(rel, len);
if let Ok(rel_to_manifest) = path.strip_prefix(manifest_dir) {
println!("cargo:rerun-if-changed={}", rel_to_manifest.display());
}
}
out
}
fn escape(s: &str) -> String {
s.replace('\\', "\\\\").replace('"', "\\\"")
}