1use std::path::{Path, PathBuf};
15
16#[derive(Debug, Clone, PartialEq, Eq)]
25pub struct DirEntry {
26 pub path: PathBuf,
27 pub name: String,
28 pub is_dir: bool,
29}
30
31pub fn read_dir_sorted(dir: &Path) -> std::io::Result<Vec<DirEntry>> {
44 let mut out: Vec<DirEntry> = Vec::new();
45 for entry in std::fs::read_dir(dir)? {
46 let entry = entry?;
47 let is_dir = entry.file_type()?.is_dir();
48 out.push(DirEntry {
49 path: entry.path(),
50 name: entry.file_name().to_string_lossy().into_owned(),
51 is_dir,
52 });
53 }
54 out.sort_by(|a, b| match (a.is_dir, b.is_dir) {
55 (true, false) => std::cmp::Ordering::Less,
56 (false, true) => std::cmp::Ordering::Greater,
57 _ => a.name.cmp(&b.name),
58 });
59 Ok(out)
60}
61
62#[cfg(test)]
63mod tests {
64 #![allow(clippy::unwrap_used)]
65 use super::*;
66
67 fn tempdir(tag: &str) -> PathBuf {
68 use std::sync::atomic::{AtomicU64, Ordering};
69 static COUNTER: AtomicU64 = AtomicU64::new(0);
70 let nanos = std::time::SystemTime::now()
71 .duration_since(std::time::UNIX_EPOCH)
72 .map(|d| d.as_nanos())
73 .unwrap_or(0);
74 let n = COUNTER.fetch_add(1, Ordering::Relaxed);
75 let d = std::env::temp_dir().join(format!("lattice-dir-{tag}-{nanos}-{n}"));
76 std::fs::create_dir_all(&d).unwrap();
77 d
78 }
79
80 #[test]
81 fn directories_sort_before_files_then_alphabetically() {
82 let dir = tempdir("sort");
83 std::fs::write(dir.join("b.txt"), "").unwrap();
84 std::fs::write(dir.join("a.txt"), "").unwrap();
85 std::fs::create_dir(dir.join("zeta")).unwrap();
86 std::fs::create_dir(dir.join("alpha")).unwrap();
87
88 let names: Vec<String> = read_dir_sorted(&dir)
89 .unwrap()
90 .into_iter()
91 .map(|e| e.name)
92 .collect();
93 assert_eq!(names, vec!["alpha", "zeta", "a.txt", "b.txt"]);
94
95 std::fs::remove_dir_all(dir).ok();
96 }
97
98 #[test]
99 fn each_entry_carries_both_halves_the_majors_need() {
100 let dir = tempdir("halves");
101 std::fs::create_dir(dir.join("sub")).unwrap();
102 std::fs::write(dir.join("f.rs"), "").unwrap();
103
104 let entries = read_dir_sorted(&dir).unwrap();
105 let sub = &entries[0];
106 assert_eq!(sub.name, "sub", "oil's rope reads the bare name");
107 assert_eq!(
108 sub.path,
109 dir.join("sub"),
110 "the tree keys rows and icons off the path"
111 );
112 assert!(sub.is_dir);
113 assert!(!entries[1].is_dir);
114
115 std::fs::remove_dir_all(dir).ok();
116 }
117
118 #[test]
122 fn a_missing_directory_is_an_error_not_an_empty_listing() {
123 let missing = std::env::temp_dir().join("lattice-dir-does-not-exist-please");
124 let _ = std::fs::remove_dir_all(&missing);
125 assert!(read_dir_sorted(&missing).is_err());
126 }
127}