1use std::path::PathBuf;
4
5use crate::tree::FileKind;
6
7#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
9pub enum SymlinkPolicy {
10 #[default]
12 Skip,
13 Record,
15 Follow,
17}
18
19#[derive(Debug, Clone, Copy, PartialEq, Eq)]
21pub enum SkipDirPreset {
22 Common,
24 Rust,
26 Node,
28 Python,
30 DotNet,
32}
33
34impl SkipDirPreset {
35 #[must_use]
37 pub const fn names(self) -> &'static [&'static str] {
38 match self {
39 Self::Common => &[".git"],
40 Self::Rust => &["target"],
41 Self::Node => &["node_modules", "dist"],
42 Self::Python => &[
43 "__pycache__",
44 ".venv",
45 "venv",
46 ".pytest_cache",
47 ".mypy_cache",
48 ".tox",
49 ],
50 Self::DotNet => &["bin", "obj"],
51 }
52 }
53
54 #[must_use]
56 pub fn merge(presets: &[Self]) -> Vec<String> {
57 let mut names: Vec<String> = presets
58 .iter()
59 .flat_map(|p| p.names().iter().map(|n| (*n).to_owned()))
60 .collect();
61 names.sort_unstable();
62 names.dedup();
63 names
64 }
65}
66
67#[derive(Debug, Clone, Default)]
70pub struct RecoveryRules {
71 pub exact_file_names: Vec<String>,
73 pub file_name_prefixes: Vec<String>,
75 pub directory_names: Vec<String>,
77 pub rel_path_suffixes: Vec<String>,
79}
80
81impl RecoveryRules {
82 pub(crate) fn matches(&self, rel_path: &str, name: &str, kind: FileKind) -> bool {
84 match kind {
85 FileKind::Directory => self.directory_names.iter().any(|d| d == name),
86 FileKind::File | FileKind::Symlink => {
87 self.exact_file_names.iter().any(|f| f == name)
88 || self.file_name_prefixes.iter().any(|p| name.starts_with(p))
89 || self
90 .rel_path_suffixes
91 .iter()
92 .any(|sfx| rel_path.ends_with(sfx))
93 }
94 }
95 }
96}
97
98#[derive(Debug, Clone)]
100pub struct WalkOptions {
101 pub respect_gitignore: bool,
103 pub include_hidden: bool,
105 pub symlink_policy: SymlinkPolicy,
107 pub skip_dir_names: Vec<String>,
111 pub skip_path_prefixes: Vec<String>,
113 pub max_depth: Option<u32>,
115 pub recovery: Option<RecoveryRules>,
117}
118
119impl Default for WalkOptions {
120 fn default() -> Self {
121 Self {
122 respect_gitignore: true,
123 include_hidden: true,
124 symlink_policy: SymlinkPolicy::Skip,
125 skip_dir_names: SkipDirPreset::merge(&[
126 SkipDirPreset::Common,
127 SkipDirPreset::Rust,
128 SkipDirPreset::Node,
129 ]),
130 skip_path_prefixes: Vec::new(),
131 max_depth: None,
132 recovery: None,
133 }
134 }
135}
136
137#[derive(Debug)]
139pub enum WalkError {
140 RootNotFound,
142 RootNotADirectory,
144 Io {
146 path: PathBuf,
148 source: std::io::Error,
150 },
151}
152
153impl core::fmt::Display for WalkError {
154 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
155 match self {
156 Self::RootNotFound => write!(f, "walk root not found"),
157 Self::RootNotADirectory => write!(f, "walk root is not a directory"),
158 Self::Io { path, source } => write!(f, "io error on {}: {source}", path.display()),
159 }
160 }
161}
162
163impl core::error::Error for WalkError {}