Skip to main content

path_rs/
metadata.rs

1//! Entry metadata types shared by listing and search.
2
3use std::path::PathBuf;
4use std::time::SystemTime;
5
6/// Classification of a filesystem entry.
7#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
8#[non_exhaustive]
9pub enum EntryKind {
10    /// Regular file.
11    File,
12    /// Directory.
13    Directory,
14    /// Symbolic link (when not followed).
15    Symlink,
16    /// Other (socket, fifo, device, unknown).
17    Other,
18}
19
20/// A discovered filesystem entry.
21///
22/// Metadata fields are best-effort: permission errors or exotic filesystems
23/// may leave some fields as `None` without failing the whole traversal when
24/// the error policy allows it.
25#[derive(Debug, Clone, PartialEq, Eq)]
26pub struct FileEntry {
27    /// Absolute or walk-produced path of the entry.
28    pub path: PathBuf,
29    /// Path relative to the walk root, when known.
30    pub relative_path: Option<PathBuf>,
31    /// Entry kind classification.
32    pub kind: EntryKind,
33    /// File size in bytes, when available.
34    pub size: Option<u64>,
35    /// Last modification time, when available.
36    pub modified: Option<SystemTime>,
37    /// Read-only flag, when available.
38    pub readonly: Option<bool>,
39}
40
41impl FileEntry {
42    /// Create a minimal entry with the given path and kind.
43    pub fn new(path: PathBuf, kind: EntryKind) -> Self {
44        Self {
45            path,
46            relative_path: None,
47            kind,
48            size: None,
49            modified: None,
50            readonly: None,
51        }
52    }
53}
54
55/// Deterministic sort mode for listing and search results.
56#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
57#[non_exhaustive]
58pub enum SortMode {
59    /// Do not sort (walk order).
60    None,
61    /// Sort by full path (lexicographic on the platform's OsStr ordering via lossy UTF-8 for stability).
62    #[default]
63    Path,
64    /// Sort by file name only.
65    Name,
66    /// Directories first, then files, then by path.
67    DirsFirst,
68}
69
70/// How to handle I/O errors during traversal.
71#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
72#[non_exhaustive]
73pub enum TraversalErrorPolicy {
74    /// Abort on the first error.
75    #[default]
76    FailFast,
77    /// Skip entries that produce errors and continue.
78    SkipErrors,
79}
80
81/// Apply sorting to a list of entries according to `mode`.
82pub fn sort_entries(entries: &mut [FileEntry], mode: SortMode) {
83    match mode {
84        SortMode::None => {}
85        SortMode::Path => {
86            entries.sort_by(|a, b| a.path.to_string_lossy().cmp(&b.path.to_string_lossy()));
87        }
88        SortMode::Name => {
89            entries.sort_by(|a, b| {
90                let an = a
91                    .path
92                    .file_name()
93                    .map(|n| n.to_string_lossy())
94                    .unwrap_or_default();
95                let bn = b
96                    .path
97                    .file_name()
98                    .map(|n| n.to_string_lossy())
99                    .unwrap_or_default();
100                an.cmp(&bn)
101                    .then_with(|| a.path.to_string_lossy().cmp(&b.path.to_string_lossy()))
102            });
103        }
104        SortMode::DirsFirst => {
105            entries.sort_by(|a, b| {
106                let ak = kind_rank(a.kind);
107                let bk = kind_rank(b.kind);
108                ak.cmp(&bk)
109                    .then_with(|| a.path.to_string_lossy().cmp(&b.path.to_string_lossy()))
110            });
111        }
112    }
113}
114
115fn kind_rank(kind: EntryKind) -> u8 {
116    match kind {
117        EntryKind::Directory => 0,
118        EntryKind::File => 1,
119        EntryKind::Symlink => 2,
120        EntryKind::Other => 3,
121    }
122}