Skip to main content

aqc_filetree/
tree.rs

1//! The walk result: `FileTree`, its entries, and the queries.
2
3use std::path::PathBuf;
4
5use globset::{GlobBuilder, GlobSetBuilder};
6
7/// What a tree entry is.
8#[derive(Debug, Clone, Copy, PartialEq, Eq)]
9pub enum FileKind {
10    /// Regular file.
11    File,
12    /// Directory.
13    Directory,
14    /// Symlink (recorded only under [`SymlinkPolicy::Record`]).
15    Symlink,
16}
17
18/// Which phase produced an entry.
19#[derive(Debug, Clone, Copy, PartialEq, Eq)]
20pub enum EntryOrigin {
21    /// Phase 1 walk.
22    Primary,
23    /// Phase 2 recovery walk.
24    Recovered,
25}
26
27/// One entry of a [`FileTree`].
28#[derive(Debug, Clone)]
29pub struct FileEntry {
30    /// Relative to the walk root: `/` separators, no leading `/`, no `..`.
31    /// Non-UTF-8 file names are recorded lossily (declared policy).
32    pub rel_path: String,
33    /// Absolute path.
34    pub abs_path: PathBuf,
35    /// What the entry is.
36    pub kind: FileKind,
37    /// Which phase produced it.
38    pub origin: EntryOrigin,
39}
40
41/// The result of a walk: the root plus entries sorted by `rel_path`.
42#[derive(Debug, Clone)]
43pub struct FileTree {
44    /// Walk root (canonicalized when possible).
45    pub root: PathBuf,
46    /// Entries sorted by `rel_path`.
47    pub entries: Vec<FileEntry>,
48}
49
50impl FileTree {
51    /// The entry at exactly this `rel_path`, if present.
52    #[must_use]
53    pub fn entry(&self, rel_path: &str) -> Option<&FileEntry> {
54        self.entries
55            .binary_search_by(|e| e.rel_path.as_str().cmp(rel_path))
56            .ok()
57            .and_then(|i| self.entries.get(i))
58    }
59
60    /// All entries with the given origin.
61    #[must_use]
62    pub fn entries_with_origin(&self, origin: EntryOrigin) -> Vec<&FileEntry> {
63        self.entries.iter().filter(|e| e.origin == origin).collect()
64    }
65
66    /// Entries whose `rel_path` matches the glob pattern. `*` may match
67    /// across `/` (declared policy).
68    ///
69    /// # Errors
70    ///
71    /// Returns the `globset` error for an invalid pattern.
72    pub fn glob(
73        &self,
74        pattern: &str,
75        case_sensitive: bool,
76    ) -> Result<Vec<&FileEntry>, globset::Error> {
77        let glob = GlobBuilder::new(pattern)
78            .case_insensitive(!case_sensitive)
79            .literal_separator(false)
80            .build()?;
81        let mut builder = GlobSetBuilder::new();
82        let _ = builder.add(glob);
83        let set = builder.build()?;
84        Ok(self
85            .entries
86            .iter()
87            .filter(|e| set.is_match(&e.rel_path))
88            .collect())
89    }
90}