use color_eyre::eyre::{eyre, Result};
use std::path::{Path, PathBuf};
use tokio::fs;
pub struct FileOperations;
impl FileOperations {
pub async fn exists(path: &Path) -> bool {
fs::metadata(path).await.is_ok()
}
pub async fn validate_file(path: &Path) -> Result<()> {
tracing::debug!("Validating file path: {:?}", path);
tracing::debug!("Path as string: {}", path.display());
tracing::debug!("Path extension: {:?}", path.extension());
if !Self::exists(path).await {
return Err(eyre!("File not found: {}", path.display()));
}
if !path.is_file() {
return Err(eyre!("Path is not a file: {}", path.display()));
}
fs::metadata(path)
.await
.map_err(|e| eyre!("Cannot read file: {}", e))?;
Ok(())
}
pub async fn read_to_string(path: &Path) -> Result<String> {
Self::validate_file(path).await?;
fs::read_to_string(path)
.await
.map_err(|e| eyre!("Failed to read file {}: {}", path.display(), e))
}
#[allow(dead_code)]
pub async fn write_string(path: &Path, content: &str) -> Result<()> {
if let Some(parent) = path.parent() {
fs::create_dir_all(parent)
.await
.map_err(|e| eyre!("Failed to create directory {}: {}", parent.display(), e))?;
}
fs::write(path, content)
.await
.map_err(|e| eyre!("Failed to write file {}: {}", path.display(), e))
}
pub fn expand_tilde(path: &Path) -> PathBuf {
if path.starts_with("~") {
if let Some(home) = dirs::home_dir() {
return home.join(path.strip_prefix("~").expect("prefix checked"));
}
}
path.to_path_buf()
}
#[allow(dead_code)]
pub fn canonicalize(path: &Path) -> Result<PathBuf> {
let expanded = Self::expand_tilde(path);
if expanded.is_absolute() {
Ok(expanded)
} else {
std::env::current_dir()
.map(|cwd| cwd.join(expanded))
.map_err(|e| eyre!("Failed to get current directory: {}", e))
}
}
#[allow(dead_code)]
pub fn get_extension(path: &Path) -> Option<String> {
path.extension()
.and_then(|ext| ext.to_str())
.map(|s| s.to_lowercase())
}
#[allow(dead_code)]
pub fn is_supported_document(path: &Path) -> bool {
if let Some(ext) = Self::get_extension(path) {
matches!(ext.as_str(), "txt" | "md" | "rst" | "log")
} else {
false
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_expand_tilde() {
let path = Path::new("~/test.txt");
let expanded = FileOperations::expand_tilde(path);
if let Some(home) = dirs::home_dir() {
assert_eq!(expanded, home.join("test.txt"));
}
}
#[test]
fn test_get_extension() {
assert_eq!(
FileOperations::get_extension(Path::new("test.txt")),
Some("txt".to_string())
);
assert_eq!(
FileOperations::get_extension(Path::new("test.TXT")),
Some("txt".to_string())
);
assert_eq!(FileOperations::get_extension(Path::new("test")), None);
}
#[test]
fn test_is_supported_document() {
assert!(FileOperations::is_supported_document(Path::new("test.txt")));
assert!(FileOperations::is_supported_document(Path::new("test.md")));
assert!(!FileOperations::is_supported_document(Path::new(
"test.pdf"
)));
}
}