1#[cfg(unix)]
32use crate::Result;
33
34#[cfg(not(feature="async-std"))]
35use std::path::Path;
36
37#[cfg(feature="async-std")]
38use {
39 core::future::Future,
40 async_std::path::Path,
41};
42
43#[derive(Debug, Clone, Eq, PartialEq)]
49pub enum Filter<'a> {
50
51 AllPaths,
53
54 All(&'a [Filter<'a>]),
61
62 Any(&'a [Filter<'a>]),
69
70 IgnoredDirNames {
76
77 names: &'a [&'a str],
79
80 case: Case,
82
83 },
84
85 IgnoredFileExts {
91
92 exts: &'a [&'a str],
94
95 case: Case,
97
98 files_without_extension: bool,
100
101 },
102
103 IgnoredFileNames {
109
110 names: &'a [&'a str],
112
113 case: Case,
115
116 },
117
118 NonSymlinkFiles,
120
121 #[cfg(unix)]
125 #[doc(cfg(unix))]
126 SameDevice {
127
128 device_id: u64,
130
131 },
132
133 SomeDirNames {
139
140 names: &'a [&'a str],
142
143 case: Case,
145
146 },
147
148 SomeFileExts {
154
155 exts: &'a [&'a str],
157
158 case: Case,
160
161 files_without_extension: bool,
163
164 },
165
166 SomeFileNames {
172
173 names: &'a [&'a str],
175
176 case: Case,
178
179 },
180
181 SymlinkFiles,
183
184 UserDefined {
198
199 #[cfg(not(feature="async-std"))]
201 #[doc(cfg(not(feature="async-std")))]
202 accept: fn(&Path) -> bool,
203
204 #[cfg(feature="async-std")]
206 #[doc(cfg(feature="async-std"))]
207 accept: fn(&Path) -> Box<dyn Future<Output=bool> + Unpin>,
208
209 },
210
211}
212
213#[derive(Debug, Clone, Copy, Eq, PartialEq, Hash)]
215pub enum Case {
216
217 Sensitive,
219
220 Insensitive,
222
223}
224
225#[cfg(unix)]
226macro_rules! make_same_device { ($path: ident) => {{
227 use std::os::unix::fs::MetadataExt;
228 Ok(Self::SameDevice {
229 device_id: async_call!($path.as_ref().metadata()).map(|m| m.dev())?,
230 })
231}}}
232
233macro_rules! accept { ($self: ident, $path: ident) => {{
248 match $self {
249 Self::AllPaths => true,
250 Self::All(filters) => accept_all!(filters, $path),
251 Self::Any(filters) => accept_any!(filters, $path),
252 Self::IgnoredDirNames { names, case } => ignored_dir_names!(names, case, $path),
253 Self::IgnoredFileExts { exts, case, files_without_extension: fwe } => ignored_file_exts!(exts, case, fwe, $path),
254 Self::IgnoredFileNames { names, case } => ignored_file_names!(names, case, $path),
255 Self::NonSymlinkFiles => non_symlink_files!($path),
256 #[cfg(unix)]
257 Self::SameDevice { device_id } => same_device!(device_id, $path),
258 Self::SomeDirNames { names, case } => some_dir_names!(names, case, $path),
259 Self::SomeFileExts { exts, case, files_without_extension } => some_file_exts!(exts, case, files_without_extension, $path),
260 Self::SomeFileNames { names, case } => some_file_names!(names, case, $path),
261 Self::SymlinkFiles => symlink_files!($path),
262 Self::UserDefined { accept } => {
263 let f = accept($path);
264 #[cfg(feature="async-std")]
265 let f = Box::pin(f);
266 async_call!(f)
267 },
268 }
269}}}
270
271macro_rules! accept_all { ($filters: ident, $path: ident) => {{
272 if $filters.is_empty() {
273 false
274 } else {
275 for f in *$filters {
276 let f = f.accept($path);
277 #[cfg(feature="async-std")]
278 let f = Box::pin(f);
279 if async_call!(f) == false {
280 return false
281 }
282 }
283 true
284 }
285}}}
286
287macro_rules! accept_any { ($filters: ident, $path: ident) => {{
288 for f in *$filters {
289 let f = f.accept($path);
290 #[cfg(feature="async-std")]
291 let f = Box::pin(f);
292 if async_call!(f) {
293 return true
294 }
295 }
296 false
297}}}
298
299macro_rules! ignored_dir_names { ($dir_names: ident, $case: ident, $path: ident) => {{
300 if $dir_names.is_empty() {
301 return true;
302 }
303
304 if async_call!($path.is_dir()) {
305 if let Some(Some(name)) = $path.file_name().map(|n| n.to_str()) {
306 return match $case {
307 Case::Sensitive => $dir_names.contains(&name) == false,
308 Case::Insensitive => {
309 let name = name.to_lowercase();
310 $dir_names.iter().all(|n| n.to_lowercase() != name)
311 },
312 };
313 }
314 }
315
316 true
317}}}
318
319macro_rules! ignored_file_exts { ($file_exts: ident, $case: ident, $files_without_extension: ident, $path: ident) => {{
320 if async_call!($path.is_file()) {
321 return match $path.extension().map(|e| e.to_str()) {
322 Some(Some(ext)) => $file_exts.is_empty() || match $case {
323 Case::Sensitive => $file_exts.contains(&ext) == false,
324 Case::Insensitive => {
325 let ext = ext.to_lowercase();
326 $file_exts.iter().all(|e| e.to_lowercase() != ext)
327 },
328 },
329 Some(None) => true,
330 None => $files_without_extension == &false,
331 };
332 }
333
334 true
335}}}
336
337macro_rules! ignored_file_names { ($file_names: ident, $case: ident, $path: ident) => {{
338 if $file_names.is_empty() {
339 return true;
340 }
341
342 if async_call!($path.is_file()) {
343 if let Some(Some(name)) = $path.file_name().map(|n| n.to_str()) {
344 return match $case {
345 Case::Sensitive => $file_names.contains(&name) == false,
346 Case::Insensitive => {
347 let name = name.to_lowercase();
348 $file_names.iter().all(|n| n.to_lowercase() != name)
349 },
350 };
351 }
352 }
353
354 true
355}}}
356
357macro_rules! non_symlink_files { ($path: ident) => {{
358 if async_call!($path.is_file()) {
359 if let Ok(true) = async_call!($path.symlink_metadata()).map(|m| m.file_type().is_symlink()) {
360 return false;
361 }
362 }
363 true
364}}}
365
366#[cfg(unix)]
367macro_rules! same_device { ($device_id: ident, $path: ident) => {{
368 use std::os::unix::fs::MetadataExt;
369 match async_call!($path.metadata()).map(|m| m.dev()) {
370 Ok(id) => $device_id == &id,
371 Err(_) => false,
372 }
373}}}
374
375macro_rules! some_dir_names { ($names: ident, $case: ident, $path: ident) => {{
376 if async_call!($path.is_dir()) {
377 if $names.is_empty() {
378 return false;
379 }
380
381 return match $path.file_name().map(|n| n.to_str()) {
382 Some(Some(name)) => match $case {
383 Case::Sensitive => $names.contains(&name),
384 Case::Insensitive => {
385 let name = name.to_lowercase();
386 $names.iter().any(|n| n.to_lowercase() == name)
387 },
388 },
389 _ => false,
390 };
391 }
392
393 true
394}}}
395
396macro_rules! some_file_exts { ($file_exts: ident, $case: ident, $files_without_extension: ident, $path: ident) => {{
397 if async_call!($path.is_file()) {
398 return match $path.extension().map(|e| e.to_str()) {
399 Some(Some(ext)) => $file_exts.is_empty() == false && match $case {
400 Case::Sensitive => $file_exts.contains(&ext),
401 Case::Insensitive => {
402 let ext = ext.to_lowercase();
403 $file_exts.iter().any(|e| e.to_lowercase() == ext)
404 },
405 },
406 Some(None) => false,
407 None => *$files_without_extension,
408 };
409 }
410
411 true
412}}}
413
414macro_rules! some_file_names { ($names: ident, $case: ident, $path: ident) => {{
415 if async_call!($path.is_file()) {
416 if $names.is_empty() {
417 return false;
418 }
419
420 return match $path.file_name().map(|n| n.to_str()) {
421 Some(Some(name)) => match $case {
422 Case::Sensitive => $names.contains(&name),
423 Case::Insensitive => {
424 let name = name.to_lowercase();
425 $names.iter().any(|n| n.to_lowercase() == name)
426 },
427 },
428 _ => false,
429 };
430 }
431
432 true
433}}}
434
435macro_rules! symlink_files { ($path: ident) => {{
436 if async_call!($path.is_file()) {
437 return match async_call!($path.symlink_metadata()).map(|m| m.file_type().is_symlink()) {
438 Ok(true) => true,
439 _ => false,
440 };
441 }
442 true
443}}}
444
445impl Filter<'_> {
446
447
448 #[cfg(all(not(feature="async-std"), unix))]
450 #[doc(cfg(all(not(feature="async-std"), unix)))]
451 pub fn make_same_device<P>(path: P) -> Result<Self> where P: AsRef<Path> {
452 make_same_device!(path)
453 }
454
455 #[cfg(all(feature="async-std", unix))]
457 #[doc(cfg(all(feature="async-std", unix)))]
458 pub async fn make_same_device<P>(path: P) -> Result<Self> where P: AsRef<Path> {
459 make_same_device!(path)
460 }
461
462 #[cfg(not(feature="async-std"))]
463 #[doc(cfg(not(feature="async-std")))]
464 pub (crate) fn accept(&self, path: &Path) -> bool {
465 accept!(self, path)
466 }
467
468 #[cfg(feature="async-std")]
469 #[doc(cfg(feature="async-std"))]
470 pub (crate) async fn accept(&self, path: &Path) -> bool {
471 accept!(self, path)
472 }
473
474}