use std::path::{Path, PathBuf};
pub use termesh_core::{DirEntryInfo, EntryKind, FsError, FsResult};
pub trait FileSystemService: Send + Sync {
fn read_dir(&self, path: &Path) -> FsResult<Vec<DirEntryInfo>>;
fn read_file(&self, path: &Path) -> FsResult<Vec<u8>>;
fn create_file(&self, path: &Path) -> FsResult<()>;
fn write_file(&self, path: &Path, contents: &[u8]) -> FsResult<()>;
fn create_dir(&self, path: &Path) -> FsResult<()>;
fn rename(&self, from: &Path, to: &Path) -> FsResult<()>;
fn remove_file(&self, path: &Path) -> FsResult<()>;
fn remove_dir_all(&self, path: &Path) -> FsResult<()>;
fn canonicalize(&self, path: &Path) -> FsResult<PathBuf>;
}
pub fn sort_entries(entries: &mut [DirEntryInfo]) {
entries.sort_by(|a, b| {
a.kind
.sort_rank()
.cmp(&b.kind.sort_rank())
.then_with(|| {
a.name
.to_string_lossy()
.to_lowercase()
.cmp(&b.name.to_string_lossy().to_lowercase())
})
.then_with(|| a.name.cmp(&b.name))
});
}
#[cfg(test)]
mod tests {
use super::*;
fn entry(name: &str, kind: EntryKind) -> DirEntryInfo {
DirEntryInfo { name: name.into(), path: PathBuf::from(name), kind }
}
#[test]
fn dirs_sort_before_files_then_case_insensitively() {
let mut v = vec![
entry("README.md", EntryKind::File),
entry("src", EntryKind::Dir),
entry("Cargo.toml", EntryKind::File),
entry("assets", EntryKind::Dir),
];
sort_entries(&mut v);
let names: Vec<_> = v.iter().map(|e| e.name.to_string_lossy().into_owned()).collect();
assert_eq!(names, ["assets", "src", "Cargo.toml", "README.md"]);
}
#[test]
fn sort_is_deterministic_for_names_differing_only_by_case() {
let mut v = vec![entry("b", EntryKind::File), entry("B", EntryKind::File)];
sort_entries(&mut v);
let first = v[0].name.clone();
sort_entries(&mut v);
assert_eq!(v[0].name, first);
}
}