use pagetop_build::StaticFilesBundle; use std::collections::HashMap; use std::env; use std::fs::{self, File}; use std::io::{self, BufRead, BufReader, Write}; use std::path::Path; use std::process::Command; use std::time::{SystemTime, UNIX_EPOCH}; const BUILD_STATE_FILE: &str = "./static/build_state.txt"; fn main() -> io::Result<()> { // Reconstruye la documentación. rebuild_doc()?; // Crea la colección de archivos estáticos. StaticFilesBundle::from_dir( "./static/doc", Some(pagetop_mdbook::except_common_resources), ) .with_name("doc") .build() } /// Reconstruye la documentación que ha cambiado para cada versión y sus idiomas. fn rebuild_doc() -> io::Result<()> { // Lee el estado de la última preparación. let mut build_state = read_build_state()?; // Define la ruta al directorio `doc`. let project_dir = env::var("CARGO_MANIFEST_DIR").map_err(|e| { io::Error::new( io::ErrorKind::Other, format!("Environment variable error: {}", e), ) })?; let doc_dir = Path::new(&project_dir).join("doc"); // Itera sobre las versiones de la documentación en `doc`. for entry in fs::read_dir(&doc_dir)? { let entry = entry?; let version_dir = entry.path(); if version_dir.is_dir() && version_dir.file_name().unwrap_or_default() != "theme" { let version_name = version_dir .file_name() .unwrap() .to_string_lossy() .to_string(); // Itera sobre los idiomas dentro de la versión. for lang_entry in fs::read_dir(&version_dir)? { let lang_entry = lang_entry?; let lang_dir = lang_entry.path(); if lang_dir.is_dir() { let lang_name = lang_dir.file_name().unwrap().to_string_lossy().to_string(); // Obtiene la última marca de tiempo de cambios en archivos para el idioma. let latest_change_time = get_latest_change_time(&lang_dir)?; // Obtiene la última marca de tiempo registrada para esta versión e idioma. let state_key = format!("{}/{}", version_name, lang_name); let last_build_time = build_state.get(&state_key).copied().unwrap_or(UNIX_EPOCH); // Si hay cambios, construye el libro para este idioma. if latest_change_time > last_build_time { build_mdbook(&lang_dir)?; build_state.insert(state_key, SystemTime::now()); } } } } } // Guarda el estado actualizado. write_build_state(&build_state)?; Ok(()) } /// Construye el libro mdBook para una versión específica. fn build_mdbook(version_dir: &Path) -> io::Result<()> { if !Command::new("mdbook") .arg("build") .current_dir(version_dir) .status()? .success() { return Err(io::Error::new( io::ErrorKind::Other, format!("Failed to build mdBook in {:?}", version_dir), )); } Ok(()) } /// Lee el estado de construcción desde un archivo de texto plano. fn read_build_state() -> io::Result> { let mut build_state = HashMap::new(); let path = Path::new(BUILD_STATE_FILE); if path.exists() { let file = File::open(path)?; let reader = BufReader::new(file); for line in reader.lines() { let line = line?; let parts: Vec<&str> = line.split_whitespace().collect(); if parts.len() == 2 { if let Ok(timestamp) = parts[1].parse::() { build_state.insert( parts[0].to_string(), UNIX_EPOCH + std::time::Duration::from_secs(timestamp), ); } } } } Ok(build_state) } /// Escribe el estado de construcción en un archivo de texto plano. fn write_build_state(build_state: &HashMap) -> io::Result<()> { let path = Path::new(BUILD_STATE_FILE); // Crea el directorio `static` si no existe. if let Some(parent) = path.parent() { if !parent.exists() { fs::create_dir_all(parent)?; } } let mut file = File::create(path)?; for (version, &time) in build_state { let timestamp = time .duration_since(UNIX_EPOCH) .map_err(|e| { io::Error::new(io::ErrorKind::Other, format!("Invalid system time: {}", e)) })? .as_secs(); writeln!(file, "{} {}", version, timestamp)?; } Ok(()) } /// Obtiene la marca de tiempo más reciente de los archivos de un directorio usando recursión. fn get_latest_change_time(dir: &Path) -> io::Result { let mut latest = UNIX_EPOCH; /// Función recursiva para recorrer directorios. fn visit_dir(dir: &Path, latest: &mut SystemTime) -> io::Result<()> { for entry in fs::read_dir(dir)? { let entry = entry?; let metadata = entry.metadata()?; if metadata.is_dir() { // Recurse en los subdirectorios. visit_dir(&entry.path(), latest)?; } else if let Ok(modified) = metadata.modified() { // Actualiza la última marca de tiempo. if modified > *latest { *latest = modified; } } } Ok(()) } visit_dir(dir, &mut latest)?; Ok(latest) }