//! Build script — génère les pages HTML à partir du Markdown //! //! Exécuté à chaque `cargo build`. Garantit que : //! 1. changelog.html est généré depuis CHANGELOG.md //! 2. help.html est généré depuis ROADMAP.md //! 3. Le badge version dans index.html suit la version calculée //! 4. La version est auto-générée : base Cargo.toml + compteur commits git //! 5. CHANGELOG.md reçoit une entrée vide pour la nouvelle version si absente use std::fs; use std::path::Path; use std::process::Command; fn main() { let manifest_dir = Path::new(env!("CARGO_MANIFEST_DIR")); let base_version = env!("CARGO_PKG_VERSION"); // Version dynamique = version de base + numéro de build (compteur commits git) let version = compute_version(base_version, manifest_dir); let frontend_dir = manifest_dir.join("src/frontend"); // === changelog.html depuis CHANGELOG.md === let changelog_tmpl = frontend_dir.join("changelog.tmpl.html"); let changelog_md = manifest_dir.join("CHANGELOG.md"); let changelog_out = frontend_dir.join("changelog.html"); if changelog_tmpl.exists() && changelog_md.exists() { let md = fs::read_to_string(&changelog_md).unwrap_or_default(); let html = simple_md_to_html(&md); let tmpl = fs::read_to_string(&changelog_tmpl).unwrap_or_default(); let page = tmpl .replace("{{VERSION}}", &version) .replace("{{CHANGELOG_CONTENT}}", &html); fs::write(&changelog_out, &page).ok(); println!("cargo:rerun-if-changed=CHANGELOG.md"); println!("cargo:rerun-if-changed=src/frontend/changelog.tmpl.html"); } // === help.html depuis ROADMAP.md === let help_tmpl = frontend_dir.join("help.tmpl.html"); let roadmap_md = manifest_dir.join("ROADMAP.md"); let help_out = frontend_dir.join("help.html"); if help_tmpl.exists() && roadmap_md.exists() { let md = fs::read_to_string(&roadmap_md).unwrap_or_default(); let html = simple_md_to_html(&md); let tmpl = fs::read_to_string(&help_tmpl).unwrap_or_default(); let page = tmpl .replace("{{VERSION}}", &version) .replace("{{HELP_CONTENT}}", &html); fs::write(&help_out, &page).ok(); println!("cargo:rerun-if-changed=ROADMAP.md"); println!("cargo:rerun-if-changed=src/frontend/help.tmpl.html"); } // === Injection version dans index.html === let index_path = frontend_dir.join("index.html"); if index_path.exists() { let mut content = fs::read_to_string(&index_path).unwrap_or_default(); // Remplacer tous les badges de version vX.Y.Z par la version courante content = replace_all_versions(&content, &version); fs::write(&index_path, &content).ok(); println!("cargo:rerun-if-changed=src/frontend/index.html"); } println!("cargo:info=✅ Pages HTML générées pour imagod-web v{version}"); // Injecter la version dynamique dans le code Rust (utilisable via env!("IMAGOD_BUILD_VERSION")) println!("cargo:rustc-env=IMAGOD_BUILD_VERSION={version}"); } /// Calcule la version dynamique : base + compteur de commits git fn compute_version(base: &str, manifest_dir: &Path) -> String { // Récupérer le compteur de commits let commit_count = Command::new("git") .args(["rev-list", "--count", "HEAD"]) .current_dir(manifest_dir) .output() .ok() .and_then(|o| String::from_utf8(o.stdout).ok()) .map(|s| s.trim().to_string()) .unwrap_or_else(|| "0".to_string()); // Récupérer le hash court du commit let short_hash = Command::new("git") .args(["rev-parse", "--short=7", "HEAD"]) .current_dir(manifest_dir) .output() .ok() .and_then(|o| String::from_utf8(o.stdout).ok()) .map(|s| s.trim().to_string()) .unwrap_or_else(|| "unknown".to_string()); // Vérifier si le working tree est dirty let dirty = Command::new("git") .args(["diff", "--quiet"]) .current_dir(manifest_dir) .status() .map(|s| !s.success()) .unwrap_or(false); let suffix = if dirty { "-dirty" } else { "" }; format!("{}+build.{}-{}{}", base, commit_count, short_hash, suffix) } /// Module minimal pour la date du jour (zéro dépendance externe) mod chrono_lite { use std::process::Command; pub fn today() -> String { Command::new("date") .args(["+%d %B %Y"]) .output() .ok() .and_then(|o| String::from_utf8(o.stdout).ok()) .map(|s| s.trim().to_string()) .unwrap_or_else(|| "Date inconnue".to_string()) } } /// Mini convertisseur Markdown → HTML (zéro dépendance) fn simple_md_to_html(md: &str) -> String { let mut html = String::new(); let mut in_code_block = false; let mut code_content = String::new(); for line in md.lines() { if line.starts_with("```") { if in_code_block { html.push_str("
");
html.push_str(&html_escape(&code_content));
html.push_str("\n");
code_content.clear();
in_code_block = false;
} else {
in_code_block = true;
}
continue;
}
if in_code_block {
if !code_content.is_empty() {
code_content.push('\n');
}
code_content.push_str(line);
continue;
}
let trimmed = line.trim();
if trimmed.is_empty() {
continue;
}
if trimmed.starts_with("### ") {
html.push_str(&format!("{}
\n", process_inline(trimmed))); } } if in_code_block && !code_content.is_empty() { html.push_str("");
html.push_str(&html_escape(&code_content));
html.push_str("\n");
}
html
}
fn render_table(html: &mut String, line: &str) {
let cells: Vec<&str> = line.split('|').collect();
let is_sep = cells.iter().any(|c| {
let t = c.trim();
t.contains("---") || t.contains(":--") || t.is_empty() && !t.contains(char::is_alphanumeric)
});
if is_sep {
return;
}
let content_cells: Vec<&str> = cells.iter().filter(|c| !c.trim().is_empty()).copied().collect();
if content_cells.is_empty() {
return;
}
html.push_str("