use anyhow::Result;
use std::path::Path;
pub struct FileUtils;
impl FileUtils {
pub fn get_file_mtime(file_path: &Path) -> Result<u64> {
let metadata = std::fs::metadata(file_path)?;
let mtime = metadata
.modified()?
.duration_since(std::time::UNIX_EPOCH)?
.as_secs();
Ok(mtime)
}
pub fn is_text_file(contents: &str) -> bool {
if contents.is_empty() {
return false;
}
if contents.contains('\0') {
return false;
}
let total_chars = contents.len();
let printable_chars = contents
.chars()
.filter(|c| c.is_ascii_graphic() || c.is_ascii_whitespace())
.count();
let printable_ratio = printable_chars as f64 / total_chars as f64;
printable_ratio > 0.8
}
pub fn is_allowed_text_extension(path: &Path) -> bool {
const ALLOWED_TEXT_EXTENSIONS: &[&str] = &[
"txt",
"md",
"markdown",
"rst",
"org",
"adoc",
"asciidoc",
"readme",
"changelog",
"license",
"contributors",
];
if let Some(extension) = path.extension() {
if let Some(ext_str) = extension.to_str() {
return ALLOWED_TEXT_EXTENSIONS.contains(&ext_str.to_lowercase().as_str());
}
}
if let Some(filename) = path.file_name() {
if let Some(name_str) = filename.to_str() {
let name_lower = name_str.to_lowercase();
return ALLOWED_TEXT_EXTENSIONS
.iter()
.any(|&ext| name_lower == ext || name_lower.starts_with(&format!("{}.", ext)));
}
}
false
}
pub fn detect_language(path: &Path) -> Option<&'static str> {
match path.extension()?.to_str()? {
"rs" => Some("rust"),
"py" => Some("python"),
"js" | "mjs" | "jsx" => Some("javascript"),
"ts" | "tsx" => Some("typescript"),
"go" => Some("go"),
"java" => Some("java"),
"cpp" | "cc" | "cxx" | "c" | "h" | "hpp" => Some("cpp"),
"php" => Some("php"),
"sh" | "bash" => Some("bash"),
"rb" => Some("ruby"),
"lua" => Some("lua"),
"json" => Some("json"),
"svelte" => Some("svelte"),
"css" | "scss" | "sass" => Some("css"),
"md" | "markdown" => Some("markdown"),
_ => None,
}
}
}