use anyhow::{Context, Result};
use std::fs;
use std::path::Path;
pub fn ensure_dir(path: &Path) -> Result<()> {
let safe_path = crate::utils::platform::windows_long_path(path);
if !safe_path.exists() {
fs::create_dir_all(&safe_path).with_context(|| {
let platform_help = if crate::utils::platform::is_windows() {
"On Windows: Check that the path length is < 260 chars or that long path support is enabled"
} else {
"Check directory permissions and path validity"
};
format!("Failed to create directory: {}\n\n{}", path.display(), platform_help)
})?;
} else if !safe_path.is_dir() {
return Err(anyhow::anyhow!("Path exists but is not a directory: {}", path.display()));
}
Ok(())
}
pub fn ensure_parent_dir(path: &Path) -> Result<()> {
if let Some(parent) = path.parent() {
ensure_dir(parent)?;
}
Ok(())
}
pub fn ensure_dir_exists(path: &Path) -> Result<()> {
ensure_dir(path)
}
pub fn copy_dir(src: &Path, dst: &Path) -> Result<()> {
ensure_dir(dst)?;
for entry in
fs::read_dir(src).with_context(|| format!("Failed to read directory: {}", src.display()))?
{
let entry = entry?;
let file_type = entry.file_type()?;
let src_path = entry.path();
let dst_path = dst.join(entry.file_name());
if file_type.is_dir() {
copy_dir(&src_path, &dst_path)?;
} else if file_type.is_file() {
fs::copy(&src_path, &dst_path).with_context(|| {
format!("Failed to copy file from {} to {}", src_path.display(), dst_path.display())
})?;
}
}
Ok(())
}
pub fn copy_dir_all(src: &Path, dst: &Path) -> Result<()> {
copy_dir(src, dst)
}
pub fn remove_dir_all(path: &Path) -> Result<()> {
if path.exists() {
fs::remove_dir_all(path)
.with_context(|| format!("Failed to remove directory: {}", path.display()))?;
}
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
use tempfile::tempdir;
#[test]
fn test_ensure_dir() {
let temp = tempdir().unwrap();
let test_dir = temp.path().join("test_dir");
assert!(!test_dir.exists());
ensure_dir(&test_dir).unwrap();
assert!(test_dir.exists());
assert!(test_dir.is_dir());
}
#[test]
fn test_ensure_dir_on_file() {
let temp = tempdir().unwrap();
let file_path = temp.path().join("file.txt");
std::fs::write(&file_path, "content").unwrap();
let result = ensure_dir(&file_path);
assert!(result.is_err());
}
#[test]
fn test_ensure_parent_dir() {
let temp = tempdir().unwrap();
let file_path = temp.path().join("parent").join("child").join("file.txt");
ensure_parent_dir(&file_path).unwrap();
assert!(file_path.parent().unwrap().exists());
}
#[test]
fn test_ensure_parent_dir_edge_cases() {
use std::path::PathBuf;
let temp = tempdir().unwrap();
let root_file = if cfg!(windows) {
PathBuf::from("C:\\file.txt")
} else {
PathBuf::from("/file.txt")
};
ensure_parent_dir(&root_file).unwrap();
let current_file = PathBuf::from("file.txt");
ensure_parent_dir(¤t_file).unwrap();
let existing = temp.path().join("file.txt");
ensure_parent_dir(&existing).unwrap();
ensure_parent_dir(&existing).unwrap(); }
#[test]
fn test_ensure_dir_exists() {
let temp = tempdir().unwrap();
let test_dir = temp.path().join("test_dir_alias");
assert!(!test_dir.exists());
ensure_dir_exists(&test_dir).unwrap();
assert!(test_dir.exists());
}
#[test]
fn test_copy_dir() {
let temp = tempdir().unwrap();
let src = temp.path().join("src");
let dst = temp.path().join("dst");
ensure_dir(&src).unwrap();
ensure_dir(&src.join("subdir")).unwrap();
std::fs::write(src.join("file1.txt"), "content1").unwrap();
std::fs::write(src.join("subdir/file2.txt"), "content2").unwrap();
copy_dir(&src, &dst).unwrap();
assert!(dst.join("file1.txt").exists());
assert!(dst.join("subdir/file2.txt").exists());
let content1 = std::fs::read_to_string(dst.join("file1.txt")).unwrap();
assert_eq!(content1, "content1");
let content2 = std::fs::read_to_string(dst.join("subdir/file2.txt")).unwrap();
assert_eq!(content2, "content2");
}
#[test]
fn test_copy_dir_all() {
let temp = tempdir().unwrap();
let src = temp.path().join("src_alias");
let dst = temp.path().join("dst_alias");
ensure_dir(&src).unwrap();
std::fs::write(src.join("file.txt"), "content").unwrap();
copy_dir_all(&src, &dst).unwrap();
assert!(dst.join("file.txt").exists());
}
#[test]
fn test_copy_dir_with_permissions() {
let temp = tempdir().unwrap();
let src = temp.path().join("src");
let dst = temp.path().join("dst");
ensure_dir(&src).unwrap();
std::fs::write(src.join("file.txt"), "content").unwrap();
#[cfg(unix)]
{
use std::os::unix::fs::PermissionsExt;
let mut perms = std::fs::metadata(src.join("file.txt")).unwrap().permissions();
perms.set_mode(0o644);
std::fs::set_permissions(src.join("file.txt"), perms).unwrap();
}
copy_dir(&src, &dst).unwrap();
assert!(dst.join("file.txt").exists());
#[cfg(unix)]
{
use std::os::unix::fs::PermissionsExt;
let perms = std::fs::metadata(dst.join("file.txt")).unwrap().permissions();
assert_eq!(perms.mode() & 0o777, 0o644);
}
}
#[test]
fn test_remove_dir_all() {
let temp = tempdir().unwrap();
let dir = temp.path().join("to_remove");
ensure_dir(&dir).unwrap();
std::fs::write(dir.join("file.txt"), "content").unwrap();
assert!(dir.exists());
remove_dir_all(&dir).unwrap();
assert!(!dir.exists());
}
#[test]
fn test_remove_dir_all_nonexistent() {
let temp = tempdir().unwrap();
let dir = temp.path().join("nonexistent");
remove_dir_all(&dir).unwrap();
}
#[test]
#[cfg(unix)]
fn test_remove_dir_all_symlink() {
let temp = tempdir().unwrap();
let target = temp.path().join("target");
let link = temp.path().join("link");
ensure_dir(&target).unwrap();
std::fs::write(target.join("important.txt"), "data").unwrap();
std::os::unix::fs::symlink(&target, &link).unwrap();
remove_dir_all(&link).unwrap();
assert!(target.exists());
assert!(target.join("important.txt").exists());
}
}