Skip to main content

dioxus_docs_kit_build/
lib.rs

1use serde::Deserialize;
2use std::env;
3use std::fs;
4use std::path::Path;
5
6#[derive(Deserialize)]
7struct NavConfig {
8    groups: Vec<NavGroup>,
9}
10
11#[derive(Deserialize)]
12struct NavGroup {
13    pages: Vec<String>,
14}
15
16/// Generates `doc_content_generated.rs` in `OUT_DIR` from a `_nav.json` file.
17///
18/// Call this from your `build.rs`:
19///
20/// ```rust,ignore
21/// fn main() {
22///     dioxus_docs_kit_build::generate_content_map("docs/_nav.json");
23/// }
24/// ```
25///
26/// The generated file is an expression that returns a `HashMap<&'static str, &'static str>`
27/// and is intended to be used with `include!()`.
28///
29/// The docs directory is inferred from the parent of `nav_json_path`
30/// (e.g. `"docs/_nav.json"` → `"docs"`).
31pub fn generate_content_map(nav_json_path: &str) {
32    let manifest_dir = env::var("CARGO_MANIFEST_DIR").unwrap();
33
34    println!("cargo:rerun-if-changed={nav_json_path}");
35
36    let json = fs::read_to_string(nav_json_path)
37        .unwrap_or_else(|e| panic!("Failed to read {nav_json_path}: {e}"));
38    let nav: NavConfig = serde_json::from_str(&json)
39        .unwrap_or_else(|e| panic!("Failed to parse {nav_json_path}: {e}"));
40
41    // Infer docs directory from nav path parent (e.g. "docs/_nav.json" → "docs")
42    let docs_dir = Path::new(nav_json_path)
43        .parent()
44        .and_then(|p| p.to_str())
45        .unwrap_or("docs");
46
47    let mut code = String::from("// Auto-generated by dioxus-docs-kit-build — do not edit\n{\n");
48    code.push_str("    let mut map = std::collections::HashMap::new();\n");
49
50    for group in &nav.groups {
51        for page in &group.pages {
52            let mdx_path = format!("{docs_dir}/{page}.mdx");
53            let full_path = format!("{manifest_dir}/{mdx_path}");
54
55            if !Path::new(&full_path).exists() {
56                println!(
57                    "cargo:warning=Skipping \"{page}\": {mdx_path} not found (OpenAPI endpoints don't need .mdx files)"
58                );
59                continue;
60            }
61
62            println!("cargo:rerun-if-changed={mdx_path}");
63
64            // Use absolute path so include_str! works from OUT_DIR
65            code.push_str(&format!(
66                "    map.insert(\"{page}\", include_str!(\"{full_path}\"));\n"
67            ));
68        }
69    }
70
71    code.push_str("    map\n}\n");
72
73    let out_dir = env::var("OUT_DIR").unwrap();
74    let dest = Path::new(&out_dir).join("doc_content_generated.rs");
75    fs::write(&dest, code).expect("Failed to write generated file");
76}
77
78// ============================================================================
79// Blog content map generation
80// ============================================================================
81
82#[derive(Deserialize)]
83struct BlogManifest {
84    posts: Vec<String>,
85}
86
87/// Generates `blog_content_generated.rs` in `OUT_DIR` from a `_blog.json` file.
88///
89/// Call this from your `build.rs`:
90///
91/// ```rust,ignore
92/// fn main() {
93///     dioxus_docs_kit_build::generate_blog_content_map("blog/_blog.json");
94/// }
95/// ```
96///
97/// The generated file is an expression that returns a `HashMap<&'static str, &'static str>`
98/// and is intended to be used with `include!()`.
99///
100/// The blog directory is inferred from the parent of `manifest_path`
101/// (e.g. `"blog/_blog.json"` → `"blog"`).
102///
103/// The manifest JSON itself is embedded under the key `"__manifest__"` so the
104/// runtime library can parse author definitions and other metadata.
105pub fn generate_blog_content_map(manifest_path: &str) {
106    let manifest_dir = env::var("CARGO_MANIFEST_DIR").unwrap();
107
108    println!("cargo:rerun-if-changed={manifest_path}");
109
110    let json = fs::read_to_string(manifest_path)
111        .unwrap_or_else(|e| panic!("Failed to read {manifest_path}: {e}"));
112    let manifest: BlogManifest = serde_json::from_str(&json)
113        .unwrap_or_else(|e| panic!("Failed to parse {manifest_path}: {e}"));
114
115    // Infer blog directory from manifest path parent
116    let blog_dir = Path::new(manifest_path)
117        .parent()
118        .and_then(|p| p.to_str())
119        .unwrap_or("blog");
120
121    let mut code = String::from("// Auto-generated by dioxus-docs-kit-build — do not edit\n{\n");
122    code.push_str("    let mut map = std::collections::HashMap::new();\n");
123
124    // Embed the manifest JSON itself
125    let manifest_full_path = format!("{manifest_dir}/{manifest_path}");
126    code.push_str(&format!(
127        "    map.insert(\"__manifest__\", include_str!(\"{manifest_full_path}\"));\n"
128    ));
129
130    for slug in &manifest.posts {
131        let mdx_path = format!("{blog_dir}/{slug}.mdx");
132        let full_path = format!("{manifest_dir}/{mdx_path}");
133
134        if !Path::new(&full_path).exists() {
135            println!("cargo:warning=Skipping \"{slug}\": {mdx_path} not found");
136            continue;
137        }
138
139        println!("cargo:rerun-if-changed={mdx_path}");
140
141        code.push_str(&format!(
142            "    map.insert(\"{slug}\", include_str!(\"{full_path}\"));\n"
143        ));
144    }
145
146    code.push_str("    map\n}\n");
147
148    let out_dir = env::var("OUT_DIR").unwrap();
149    let dest = Path::new(&out_dir).join("blog_content_generated.rs");
150    fs::write(&dest, code).expect("Failed to write generated file");
151}