sim_cookbook/
embed_codegen.rs1use std::io;
24use std::path::{Path, PathBuf};
25
26pub fn write_embed(recipes_subdir: &str) -> io::Result<()> {
30 let manifest_dir = std::env::var("CARGO_MANIFEST_DIR")
31 .map_err(|_| io::Error::other("CARGO_MANIFEST_DIR not set (call from build.rs)"))?;
32 let out_dir = std::env::var("OUT_DIR")
33 .map_err(|_| io::Error::other("OUT_DIR not set (call from build.rs)"))?;
34 let root = Path::new(&manifest_dir).join(recipes_subdir);
35 println!("cargo:rerun-if-changed={}", root.display());
36 let code = generate_embed_code(&root)?;
37 std::fs::write(Path::new(&out_dir).join("cookbook_recipes.rs"), code)
38}
39
40pub fn generate_embed_code(recipes_root: &Path) -> io::Result<String> {
44 let mut files: Vec<(String, PathBuf)> = Vec::new();
45 if recipes_root.is_dir() {
46 collect(recipes_root, recipes_root, &mut files)?;
47 }
48 files.sort();
49 let mut out = String::from("&[\n");
50 for (rel, abs) in &files {
51 out.push_str(&format!(
53 " ({:?}, include_bytes!({:?}) as &[u8]),\n",
54 rel,
55 abs.to_string_lossy(),
56 ));
57 }
58 out.push_str("]\n");
59 Ok(out)
60}
61
62fn collect(base: &Path, dir: &Path, out: &mut Vec<(String, PathBuf)>) -> io::Result<()> {
63 let mut entries: Vec<_> = std::fs::read_dir(dir)?.collect::<io::Result<Vec<_>>>()?;
64 entries.sort_by_key(|e| e.file_name());
65 for entry in entries {
66 let path = entry.path();
67 if path.is_dir() {
68 collect(base, &path, out)?;
69 } else if path.is_file() {
70 let rel = path
71 .strip_prefix(base)
72 .map_err(io::Error::other)?
73 .components()
74 .map(|c| c.as_os_str().to_string_lossy())
75 .collect::<Vec<_>>()
76 .join("/");
77 out.push((rel, path));
78 }
79 }
80 Ok(())
81}
82
83#[cfg(test)]
84mod tests {
85 use super::*;
86
87 #[test]
88 fn empty_dir_yields_empty_slice() {
89 let dir = std::env::temp_dir().join(format!("sim-cb-embed-empty-{}", std::process::id()));
90 let code = generate_embed_code(&dir).unwrap();
91 assert_eq!(code, "&[\n]\n");
92 }
93
94 #[test]
95 fn generates_sorted_include_bytes() {
96 let root = std::env::temp_dir().join(format!("sim-cb-embed-{}", std::process::id()));
97 let _ = std::fs::remove_dir_all(&root);
98 std::fs::create_dir_all(root.join("01-basics/add")).unwrap();
99 std::fs::write(root.join("book.toml"), b"book = \"x\"\n").unwrap();
100 std::fs::write(root.join("01-basics/add/recipe.toml"), b"id=\"a\"\n").unwrap();
101 let code = generate_embed_code(&root).unwrap();
102 assert!(code.contains("\"01-basics/add/recipe.toml\""), "{code}");
103 assert!(code.contains("\"book.toml\""), "{code}");
104 assert!(code.contains("include_bytes!("), "{code}");
105 let recipe_at = code.find("recipe.toml").unwrap();
108 let book_at = code.find("book.toml").unwrap();
109 assert!(recipe_at < book_at, "expected sorted order, got:\n{code}");
110 let _ = std::fs::remove_dir_all(&root);
111 }
112}