use std::fs;
use std::path::{Path, PathBuf};
use anyhow::{Context, Result, bail};
pub fn canonicalize_directory(path: &Path) -> Result<PathBuf> {
let canonical = fs::canonicalize(path)
.with_context(|| format!("目录不存在或不可访问: {}", path.display()))?;
if !canonical.is_dir() {
bail!("目标必须是目录: {}", canonical.display());
}
Ok(trim_trailing_separator(&canonical))
}
pub fn normalize_absolute_directory(path: &Path) -> Result<PathBuf> {
if !path.is_absolute() {
bail!("目录路径必须为绝对路径: {}", path.display());
}
Ok(trim_trailing_separator(path))
}
pub fn default_display_name(path: &Path) -> String {
path.file_name()
.and_then(|name| name.to_str())
.map(ToOwned::to_owned)
.filter(|value| !value.is_empty())
.unwrap_or_else(|| "directory".to_string())
}
pub fn directory_contains(base: &Path, candidate: &Path) -> bool {
candidate.starts_with(base)
}
pub fn parent_directory(path: &Path) -> Option<PathBuf> {
path.parent()
.filter(|parent| parent != &path)
.map(trim_trailing_separator)
}
fn trim_trailing_separator(path: &Path) -> PathBuf {
if path == Path::new("/") {
return PathBuf::from("/");
}
let display = path.to_string_lossy();
let trimmed = display.trim_end_matches('/');
if trimmed.is_empty() {
PathBuf::from("/")
} else {
PathBuf::from(trimmed)
}
}
#[cfg(test)]
mod tests {
use std::fs;
use super::{canonicalize_directory, directory_contains, parent_directory};
#[test]
fn canonicalize_directory_accepts_existing_dir() {
let temp = tempfile_dir("directory-ok");
fs::create_dir_all(&temp).unwrap();
let result = canonicalize_directory(&temp).unwrap();
assert!(result.is_dir());
}
#[test]
fn directory_contains_respects_path_boundaries() {
assert!(directory_contains(
std::path::Path::new("/srv/work"),
std::path::Path::new("/srv/work/project"),
));
assert!(!directory_contains(
std::path::Path::new("/srv/work"),
std::path::Path::new("/srv/workspace"),
));
}
#[test]
fn parent_directory_returns_none_for_root() {
assert!(parent_directory(std::path::Path::new("/")).is_none());
}
fn tempfile_dir(name: &str) -> std::path::PathBuf {
std::env::temp_dir().join(format!("{}-{}", name, uuid::Uuid::new_v4().simple()))
}
}