weavatrix_scan/config/
builder.rs1use super::{
2 CacheValidationPolicy, CancellationToken, ContentDiscoveryMode, ContentValidationPolicy,
3 DEFAULT_SKIP_DIRECTORIES, Duration, EvidenceMode, FileTypeMatch, IgnorePolicy, NamedFileTypes,
4 OsStr, Path, ScanOptions, StandardSkips,
5};
6
7impl ScanOptions {
8 #[must_use]
9 pub fn with_extensions<I, S>(mut self, extensions: I) -> Self
10 where
11 I: IntoIterator<Item = S>,
12 S: AsRef<str>,
13 {
14 self.extensions = extensions
15 .into_iter()
16 .map(|item| item.as_ref().trim_start_matches('.').to_ascii_lowercase())
17 .collect();
18 self
19 }
20
21 #[must_use]
23 pub fn with_file_types(mut self, file_types: NamedFileTypes) -> Self {
24 self.file_types = file_types;
25 self
26 }
27
28 #[must_use]
29 pub fn with_ignore_files<I, S>(mut self, names: I) -> Self
30 where
31 I: IntoIterator<Item = S>,
32 S: AsRef<str>,
33 {
34 self.ignore_files = names
35 .into_iter()
36 .map(|item| item.as_ref().to_owned())
37 .collect();
38 self
39 }
40
41 #[must_use]
46 pub fn with_override_rules<I, S>(mut self, rules: I) -> Self
47 where
48 I: IntoIterator<Item = S>,
49 S: AsRef<str>,
50 {
51 self.override_rules = rules
52 .into_iter()
53 .map(|item| item.as_ref().to_owned())
54 .collect();
55 self
56 }
57
58 #[must_use]
59 pub const fn with_ignore_case_insensitive(mut self, enabled: bool) -> Self {
60 self.ignore_case_insensitive = enabled;
61 self
62 }
63
64 #[must_use]
65 pub fn with_ignore_policy(mut self, policy: IgnorePolicy) -> Self {
66 self.ignore_policy = policy;
67 self
68 }
69
70 #[must_use]
71 pub const fn with_skip_hidden(mut self, enabled: bool) -> Self {
72 self.skip_hidden = enabled;
73 self
74 }
75
76 #[must_use]
81 pub fn metadata_only(mut self) -> Self {
82 self.hash_file_contents = false;
83 self.detect_binary_files = false;
84 self
85 }
86
87 #[must_use]
89 pub const fn selected_files_only(mut self) -> Self {
90 self.evidence = EvidenceMode::SelectedFiles;
91 self
92 }
93
94 #[must_use]
98 pub const fn with_parallelism(mut self, parallelism: usize) -> Self {
99 self.parallelism = parallelism;
100 self
101 }
102
103 #[must_use]
105 pub const fn with_traversal_parallelism(mut self, parallelism: usize) -> Self {
106 self.traversal_parallelism = Some(parallelism);
107 self
108 }
109
110 #[must_use]
112 pub const fn with_content_parallelism(mut self, parallelism: usize) -> Self {
113 self.content_parallelism = Some(parallelism);
114 self
115 }
116
117 #[must_use]
119 pub const fn with_content_discovery(mut self, mode: ContentDiscoveryMode) -> Self {
120 self.content_discovery = mode;
121 self
122 }
123
124 #[must_use]
125 pub const fn with_max_entries(mut self, max_entries: Option<u64>) -> Self {
126 self.limits.max_entries = max_entries;
127 self
128 }
129
130 #[must_use]
131 pub const fn with_max_total_bytes(mut self, max_total_bytes: Option<u64>) -> Self {
132 self.limits.max_total_bytes = max_total_bytes;
133 self
134 }
135
136 #[must_use]
137 pub const fn with_timeout(mut self, timeout: Option<Duration>) -> Self {
138 self.limits.timeout = timeout;
139 self
140 }
141
142 #[must_use]
143 pub fn with_cancellation(mut self, cancellation: CancellationToken) -> Self {
144 self.cancellation = Some(cancellation);
145 self
146 }
147
148 #[must_use]
149 pub const fn with_cache_validation(mut self, policy: CacheValidationPolicy) -> Self {
150 self.cache_validation = policy;
151 self
152 }
153
154 #[must_use]
155 pub const fn with_content_validation(mut self, policy: ContentValidationPolicy) -> Self {
156 self.content_validation = policy;
157 self
158 }
159
160 pub(crate) fn should_skip_directory(&self, name: &OsStr) -> bool {
161 self.standard_skips == StandardSkips::Enabled
162 && DEFAULT_SKIP_DIRECTORIES
163 .iter()
164 .any(|candidate| name == OsStr::new(candidate))
165 }
166
167 pub(crate) fn accepts_extension(&self, path: &Path, relative: &str) -> bool {
168 if self.extensions.is_empty() && !self.file_types.is_active() {
169 return true;
170 }
171 match self.file_types.matched(path, relative) {
172 FileTypeMatch::Include => return true,
173 FileTypeMatch::Exclude => return false,
174 FileTypeMatch::None => {}
175 }
176 if self.extensions.is_empty() && self.file_types.has_includes() {
177 return false;
178 }
179 if self.extensions.is_empty() {
180 return true;
181 }
182 let Some(extension) = path.extension().and_then(|value| value.to_str()) else {
183 return false;
184 };
185 self.contains_extension(extension)
186 || (extension.bytes().any(|byte| byte.is_ascii_uppercase())
187 && self.contains_extension(&extension.to_ascii_lowercase()))
188 }
189
190 fn contains_extension(&self, extension: &str) -> bool {
191 if self.extensions.len() <= 8 {
192 self.extensions
193 .iter()
194 .any(|candidate| candidate == extension)
195 } else {
196 self.extensions.contains(extension)
197 }
198 }
199}