use syn::{Item, parse_file};
pub fn ensure_mod_declarations(content: &str, submodules: &[String]) -> String {
let existing_mods = extract_existing_mods(content);
let mut to_add = Vec::new();
for submod in submodules {
if !existing_mods.contains(submod) {
to_add.push(submod.clone());
}
}
if to_add.is_empty() {
return content.to_string();
}
let mod_decls = to_add
.iter()
.map(|name| format!("pub mod {};", name))
.collect::<Vec<_>>()
.join("\n");
if content.contains("// BEGIN DYNAMI GENERATED") {
let parts: Vec<&str> = content.split("// BEGIN DYNAMI GENERATED").collect();
if parts.len() >= 2 {
format!(
"{}\n{}\n\n// BEGIN DYNAMI GENERATED{}",
parts[0].trim_end(),
mod_decls,
parts[1]
)
} else {
content.to_string()
}
} else if content.trim().is_empty() {
mod_decls
} else {
format!("{}\n\n{}", mod_decls, content)
}
}
fn extract_existing_mods(content: &str) -> Vec<String> {
if let Ok(file) = parse_file(content) {
file.items
.iter()
.filter_map(|item| {
if let Item::Mod(item_mod) = item {
Some(item_mod.ident.to_string())
} else {
None
}
})
.collect()
} else {
Vec::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_extract_existing_mods() {
let content = r#"
pub mod api;
pub mod users;
struct Something {}
"#;
let mods = extract_existing_mods(content);
assert_eq!(mods.len(), 2);
assert!(mods.contains(&"api".to_string()));
assert!(mods.contains(&"users".to_string()));
}
#[test]
fn test_ensure_mod_declarations_no_duplicates() {
let content = "pub mod api;\n";
let submodules = vec!["api".to_string(), "users".to_string()];
let result = ensure_mod_declarations(content, &submodules);
assert!(result.contains("pub mod users;"));
assert_eq!(result.matches("pub mod api;").count(), 1);
}
#[test]
fn test_ensure_mod_declarations_with_markers() {
let content = r#"pub mod existing;
// BEGIN DYNAMI GENERATED
pub fn router() -> Router {
Router::new()
}
// END DYNAMI GENERATED"#;
let submodules = vec!["api".to_string()];
let result = ensure_mod_declarations(content, &submodules);
assert!(result.contains("pub mod api;"));
assert!(result.contains("pub mod existing;"));
assert!(result.contains("// BEGIN DYNAMI GENERATED"));
let api_pos = result.find("pub mod api;").unwrap();
let begin_pos = result.find("// BEGIN DYNAMI GENERATED").unwrap();
assert!(api_pos < begin_pos);
}
#[test]
fn test_ensure_mod_declarations_empty_content() {
let content = "";
let submodules = vec!["api".to_string(), "users".to_string()];
let result = ensure_mod_declarations(content, &submodules);
assert!(result.contains("pub mod api;"));
assert!(result.contains("pub mod users;"));
}
}