use crate::commands::add::errors::IntegrationError;
use std::fs;
use std::io;
use std::path::{Path, PathBuf};
pub fn add_module_to_mod_rs(
project_root: &Path,
target_dir: &Path,
window_name: &str,
) -> Result<PathBuf, IntegrationError> {
let mod_path = target_dir.join("mod.rs");
if !target_dir.exists() {
fs::create_dir_all(target_dir).map_err(|source| IntegrationError::DirectoryCreation {
path: target_dir.to_path_buf(),
source,
})?;
}
let content = if mod_path.exists() {
fs::read_to_string(&mod_path).map_err(|source| IntegrationError::ModFileRead {
path: mod_path.clone(),
source,
})?
} else {
String::from("// UI module exports\n\n")
};
if is_module_declared(&content, window_name) {
return Ok(mod_path); }
let new_content = append_module_declaration(&content, window_name);
fs::write(&mod_path, new_content).map_err(|source| IntegrationError::ModFileWrite {
path: mod_path.clone(),
source,
})?;
ensure_parent_modules_registered(project_root, target_dir)?;
Ok(mod_path)
}
fn ensure_parent_modules_registered(
project_root: &Path,
target_dir: &Path,
) -> Result<(), IntegrationError> {
let mut current_dir = target_dir.to_path_buf();
let ui_root = project_root.join("src/ui");
if current_dir == ui_root {
return Ok(());
}
while let Some(parent_dir) = current_dir.parent() {
if parent_dir < ui_root.as_path() {
break;
}
let parent_mod_path = parent_dir.join("mod.rs");
let subdir_name = current_dir
.file_name()
.and_then(|n| n.to_str())
.ok_or_else(|| IntegrationError::DirectoryCreation {
path: current_dir.clone(),
source: io::Error::new(io::ErrorKind::InvalidInput, "Invalid directory name"),
})?;
let content = if parent_mod_path.exists() {
fs::read_to_string(&parent_mod_path).map_err(|source| {
IntegrationError::ModFileRead {
path: parent_mod_path.clone(),
source,
}
})?
} else {
String::from("// UI module exports\n\n")
};
if !is_module_declared(&content, subdir_name) {
let new_content = append_module_declaration(&content, subdir_name);
fs::write(&parent_mod_path, new_content).map_err(|source| {
IntegrationError::ModFileWrite {
path: parent_mod_path.clone(),
source,
}
})?;
}
if parent_dir == ui_root {
break;
}
current_dir = parent_dir.to_path_buf();
}
Ok(())
}
fn is_module_declared(content: &str, module_name: &str) -> bool {
let pattern = format!(r"(?m)^\s*pub\s+mod\s+{}\s*;", regex::escape(module_name));
match regex::Regex::new(&pattern) {
Ok(re) => re.is_match(content),
Err(_) => false,
}
}
fn append_module_declaration(content: &str, module_name: &str) -> String {
let mut new_content = content.to_string();
if !new_content.is_empty() && !new_content.ends_with('\n') {
new_content.push('\n');
}
new_content.push_str(&format!("pub mod {};\n", module_name));
new_content
}
#[cfg(test)]
mod tests {
use super::*;
use std::fs;
use tempfile::TempDir;
#[test]
fn test_is_module_declared_simple() {
let content = "pub mod window;\n";
assert!(is_module_declared(content, "window"));
assert!(!is_module_declared(content, "settings"));
}
#[test]
fn test_is_module_declared_multiple_modules() {
let content = "pub mod window;\npub mod settings;\npub mod profile;\n";
assert!(is_module_declared(content, "window"));
assert!(is_module_declared(content, "settings"));
assert!(is_module_declared(content, "profile"));
assert!(!is_module_declared(content, "admin"));
}
#[test]
fn test_is_module_declared_with_whitespace() {
let content = "pub mod window ;\n";
assert!(is_module_declared(content, "window"));
}
#[test]
fn test_is_module_declared_with_indentation() {
let content = " pub mod window;\n";
assert!(is_module_declared(content, "window"));
}
#[test]
fn test_is_module_declared_ignores_comments() {
let content = "// pub mod commented;\npub mod window;\n";
assert!(is_module_declared(content, "window"));
assert!(!is_module_declared(content, "commented"));
}
#[test]
fn test_is_module_declared_with_similar_names() {
let content = "pub mod settings;\npub mod settings_page;\n";
assert!(is_module_declared(content, "settings"));
assert!(is_module_declared(content, "settings_page"));
assert!(!is_module_declared(content, "setting"));
}
#[test]
fn test_append_module_declaration_to_empty() {
let content = "";
let result = append_module_declaration(content, "settings");
assert_eq!(result, "pub mod settings;\n");
}
#[test]
fn test_append_module_declaration_to_existing() {
let content = "pub mod window;\n";
let result = append_module_declaration(content, "settings");
assert_eq!(result, "pub mod window;\npub mod settings;\n");
}
#[test]
fn test_append_module_declaration_without_trailing_newline() {
let content = "pub mod window;";
let result = append_module_declaration(content, "settings");
assert_eq!(result, "pub mod window;\npub mod settings;\n");
}
#[test]
fn test_add_module_creates_new_mod_file() {
let temp = TempDir::new().unwrap();
let project_root = temp.path();
let ui_dir = project_root.join("src/ui");
fs::create_dir_all(&ui_dir).unwrap();
let result = add_module_to_mod_rs(project_root, &ui_dir, "settings");
assert!(result.is_ok());
let mod_path = ui_dir.join("mod.rs");
assert!(mod_path.exists());
let content = fs::read_to_string(mod_path).unwrap();
assert!(content.contains("pub mod settings;"));
}
#[test]
fn test_add_module_appends_to_existing() {
let temp = TempDir::new().unwrap();
let project_root = temp.path();
let ui_dir = project_root.join("src/ui");
fs::create_dir_all(&ui_dir).unwrap();
let mod_path = ui_dir.join("mod.rs");
fs::write(&mod_path, "pub mod window;\n").unwrap();
let result = add_module_to_mod_rs(project_root, &ui_dir, "settings");
assert!(result.is_ok());
let content = fs::read_to_string(mod_path).unwrap();
assert!(content.contains("pub mod window;"));
assert!(content.contains("pub mod settings;"));
}
#[test]
fn test_add_module_prevents_duplicates() {
let temp = TempDir::new().unwrap();
let project_root = temp.path();
let ui_dir = project_root.join("src/ui");
fs::create_dir_all(&ui_dir).unwrap();
let mod_path = ui_dir.join("mod.rs");
fs::write(&mod_path, "pub mod settings;\n").unwrap();
let result = add_module_to_mod_rs(project_root, &ui_dir, "settings");
assert!(result.is_ok());
let content = fs::read_to_string(mod_path).unwrap();
let count = content.matches("pub mod settings;").count();
assert_eq!(count, 1);
}
#[test]
fn test_add_module_creates_directory_if_missing() {
let temp = TempDir::new().unwrap();
let project_root = temp.path();
let ui_dir = project_root.join("src/ui/admin");
let result = add_module_to_mod_rs(project_root, &ui_dir, "dashboard");
assert!(result.is_ok());
assert!(ui_dir.exists());
assert!(ui_dir.join("mod.rs").exists());
let content = fs::read_to_string(ui_dir.join("mod.rs")).unwrap();
assert!(content.contains("pub mod dashboard;"));
}
#[test]
fn test_ensure_parent_modules_single_level() {
let temp = TempDir::new().unwrap();
let project_root = temp.path();
let ui_dir = project_root.join("src/ui");
fs::create_dir_all(&ui_dir).unwrap();
let target_dir = ui_dir.join("admin");
fs::create_dir_all(&target_dir).unwrap();
fs::write(target_dir.join("mod.rs"), "pub mod dashboard;\n").unwrap();
let result = ensure_parent_modules_registered(project_root, &target_dir);
assert!(result.is_ok());
let ui_mod_path = ui_dir.join("mod.rs");
assert!(ui_mod_path.exists());
let content = fs::read_to_string(ui_mod_path).unwrap();
assert!(content.contains("pub mod admin;"));
}
#[test]
fn test_ensure_parent_modules_nested_levels() {
let temp = TempDir::new().unwrap();
let project_root = temp.path();
let ui_dir = project_root.join("src/ui");
fs::create_dir_all(&ui_dir).unwrap();
let target_dir = ui_dir.join("admin/reports");
fs::create_dir_all(&target_dir).unwrap();
fs::write(target_dir.join("mod.rs"), "pub mod sales;\n").unwrap();
let result = ensure_parent_modules_registered(project_root, &target_dir);
assert!(result.is_ok());
let admin_mod_path = ui_dir.join("admin/mod.rs");
assert!(admin_mod_path.exists());
let content = fs::read_to_string(&admin_mod_path).unwrap();
assert!(content.contains("pub mod reports;"));
let ui_mod_path = ui_dir.join("mod.rs");
assert!(ui_mod_path.exists());
let content = fs::read_to_string(ui_mod_path).unwrap();
assert!(content.contains("pub mod admin;"));
}
#[test]
fn test_ensure_parent_modules_preserves_existing() {
let temp = TempDir::new().unwrap();
let project_root = temp.path();
let ui_dir = project_root.join("src/ui");
fs::create_dir_all(&ui_dir).unwrap();
fs::write(ui_dir.join("mod.rs"), "pub mod window;\n").unwrap();
let target_dir = ui_dir.join("admin");
fs::create_dir_all(&target_dir).unwrap();
let result = ensure_parent_modules_registered(project_root, &target_dir);
assert!(result.is_ok());
let ui_mod_path = ui_dir.join("mod.rs");
let content = fs::read_to_string(ui_mod_path).unwrap();
assert!(content.contains("pub mod window;"));
assert!(content.contains("pub mod admin;"));
}
}