Files
imagod-web/build.rs
T

309 lines
11 KiB
Rust

//! 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 Cargo.toml
use std::fs;
use std::path::Path;
fn main() {
let manifest_dir = Path::new(env!("CARGO_MANIFEST_DIR"));
let version = env!("CARGO_PKG_VERSION");
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}");
}
/// 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("<pre><code>");
html.push_str(&html_escape(&code_content));
html.push_str("</code></pre>\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!("<h3>{}</h3>\n", html_escape(&trimmed[4..])));
} else if trimmed.starts_with("## ") {
html.push_str(&format!("<h2>{}</h2>\n", html_escape(&trimmed[3..])));
} else if trimmed.starts_with("# ") {
html.push_str(&format!("<h1>{}</h1>\n", html_escape(&trimmed[2..])));
} else if trimmed == "---" {
html.push_str("<hr>\n");
} else if trimmed.starts_with('|') && trimmed.ends_with('|') {
render_table(&mut html, trimmed);
} else if trimmed.starts_with("- [x]") || trimmed.starts_with("- [X]") {
html.push_str(&format!(
"<li><input type=\"checkbox\" checked disabled> {}</li>\n",
process_inline(&trimmed[5..].trim())
));
} else if trimmed.starts_with("- [ ]") {
html.push_str(&format!(
"<li><input type=\"checkbox\" disabled> {}</li>\n",
process_inline(&trimmed[5..].trim())
));
} else if trimmed.starts_with("- ") || trimmed.starts_with("* ") {
html.push_str(&format!("<li>{}</li>\n", process_inline(&trimmed[2..])));
} else {
html.push_str(&format!("<p>{}</p>\n", process_inline(trimmed)));
}
}
if in_code_block && !code_content.is_empty() {
html.push_str("<pre><code>");
html.push_str(&html_escape(&code_content));
html.push_str("</code></pre>\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("<tr>");
for cell in &content_cells {
html.push_str(&format!("<td>{}</td>", process_inline(cell.trim())));
}
html.push_str("</tr>\n");
}
fn process_inline(text: &str) -> String {
// Échapper HTML d'abord
let text = html_escape(text);
// Puis appliquer les formats
let text = replace_pattern(&text, "**", "strong");
let text = replace_pattern(&text, "__", "strong");
let text = replace_pattern(&text, "*", "em");
let text = replace_pattern(&text, "_", "em");
let text = replace_pattern(&text, "~~", "del");
let text = replace_pattern(&text, "`", "code");
// Liens [texte](url)
replace_links(&text)
}
fn replace_pattern(text: &str, marker: &str, tag: &str) -> String {
let mut result = String::new();
let mut chars = text.chars().peekable();
let marker_len = marker.chars().count();
let marker_chars: Vec<char> = marker.chars().collect();
while let Some(&c) = chars.peek() {
// Vérifier si on est au début d'un marqueur
if c == marker_chars[0] {
let mut candidate = String::new();
let mut clone = chars.clone();
for _ in 0..marker_len {
if let Some(&mc) = clone.peek() {
candidate.push(mc);
clone.next();
}
}
if candidate == marker {
// Avancer après le marqueur d'ouverture
for _ in 0..marker_len {
chars.next();
}
// Chercher le marqueur de fermeture
let mut inner = String::new();
let mut found = false;
while let Some(&c2) = chars.peek() {
if c2 == marker_chars[0] {
let mut close_candidate = String::new();
let mut close_clone = chars.clone();
for _ in 0..marker_len {
if let Some(&mc) = close_clone.peek() {
close_candidate.push(mc);
close_clone.next();
}
}
if close_candidate == marker {
for _ in 0..marker_len {
chars.next();
}
found = true;
break;
}
}
inner.push(chars.next().unwrap());
}
if found {
result.push_str(&format!("<{tag}>{inner}</{tag}>"));
} else {
result.push_str(&candidate);
result.push_str(&inner);
}
continue;
}
}
result.push(chars.next().unwrap());
}
result
}
fn replace_links(text: &str) -> String {
let mut result = String::new();
let chars: Vec<char> = text.chars().collect();
let mut i = 0;
while i < chars.len() {
if chars[i] == '[' {
let mut j = i + 1;
let mut link_text = String::new();
while j < chars.len() && chars[j] != ']' {
link_text.push(chars[j]);
j += 1;
}
if j + 2 < chars.len() && chars[j] == ']' && chars[j + 1] == '(' {
let mut k = j + 2;
let mut url = String::new();
while k < chars.len() && chars[k] != ')' {
url.push(chars[k]);
k += 1;
}
if k < chars.len() && chars[k] == ')' {
result.push_str(&format!("<a href=\"{url}\">{link_text}</a>"));
i = k + 1;
continue;
}
}
// Pas un lien valide, remettre le [
result.push('[');
result.push_str(&link_text);
i = j;
} else {
result.push(chars[i]);
i += 1;
}
}
result
}
fn html_escape(text: &str) -> String {
text.replace('&', "&amp;")
.replace('<', "&lt;")
.replace('>', "&gt;")
.replace('"', "&quot;")
}
/// Remplace toutes les occurrences de `vX.Y.Z` dans le HTML par la version courante
fn replace_all_versions(html: &str, version: &str) -> String {
let mut result = String::new();
let chars: Vec<char> = html.chars().collect();
let mut i = 0;
while i < chars.len() {
if chars[i] == 'v'
&& i + 1 < chars.len()
&& chars[i + 1].is_ascii_digit()
{
// Essayer de parser une version vX.Y.Z ou vX.Y
let mut j = i + 1;
let mut dots = 0;
while j < chars.len() {
if chars[j].is_ascii_digit() {
j += 1;
} else if chars[j] == '.' && dots < 2 && j + 1 < chars.len() && chars[j + 1].is_ascii_digit() {
dots += 1;
j += 1;
} else {
break;
}
}
if dots >= 1 {
// C'est une version — remplacer
result.push_str(&format!("v{version}"));
i = j;
continue;
}
}
result.push(chars[i]);
i += 1;
}
result
}