file_source/
paths_provider.rs

1//! [`Glob`] based paths provider implementation.
2
3use std::path::PathBuf;
4
5use file_source_common::internal_events::FileSourceInternalEvents;
6pub use glob::MatchOptions;
7use glob::Pattern;
8
9/// Represents the ability to enumerate paths.
10///
11/// For use in [`crate::file_server::FileServer`].
12///
13/// # Notes
14///
15/// Ideally we'd use an iterator with bound lifetime here:
16///
17/// ```ignore
18/// type Iter<'a>: Iterator<Item = PathBuf> + 'a;
19/// fn paths(&self) -> Self::Iter<'_>;
20/// ```
21///
22/// However, that's currently unavailable at Rust.
23/// See: <https://github.com/rust-lang/rust/issues/44265>
24///
25/// We use an `IntoIter` here as a workaround.
26pub trait PathsProvider {
27    /// Provides the iterator that returns paths.
28    type IntoIter: IntoIterator<Item = PathBuf>;
29
30    /// Provides a set of paths.
31    fn paths(&self) -> Self::IntoIter;
32}
33
34/// A glob-based path provider.
35///
36/// Provides the paths to the files on the file system that match include
37/// patterns and don't match the exclude patterns.
38pub struct Glob<E: FileSourceInternalEvents> {
39    include_patterns: Vec<String>,
40    exclude_patterns: Vec<Pattern>,
41    glob_match_options: MatchOptions,
42    emitter: E,
43}
44
45impl<E: FileSourceInternalEvents> Glob<E> {
46    /// Create a new [`Glob`].
47    ///
48    /// Returns `None` if patterns aren't valid.
49    pub fn new(
50        include_patterns: &[PathBuf],
51        exclude_patterns: &[PathBuf],
52        glob_match_options: MatchOptions,
53        emitter: E,
54    ) -> Option<Self> {
55        let include_patterns = include_patterns
56            .iter()
57            .map(|path| path.to_str().map(ToOwned::to_owned))
58            .collect::<Option<_>>()?;
59
60        let exclude_patterns = exclude_patterns
61            .iter()
62            .filter_map(|path| path.to_str().map(|path| Pattern::new(path).ok()))
63            .collect::<Option<Vec<_>>>()?;
64
65        Some(Self {
66            include_patterns,
67            exclude_patterns,
68            glob_match_options,
69            emitter,
70        })
71    }
72}
73
74impl<E: FileSourceInternalEvents> PathsProvider for Glob<E> {
75    type IntoIter = Vec<PathBuf>;
76
77    fn paths(&self) -> Self::IntoIter {
78        self.include_patterns
79            .iter()
80            .flat_map(|include_pattern| {
81                glob::glob_with(include_pattern.as_str(), self.glob_match_options)
82                    .expect("failed to read glob pattern")
83                    .filter_map(|val| {
84                        val.map_err(|error| {
85                            self.emitter
86                                .emit_path_globbing_failed(error.path(), error.error())
87                        })
88                        .ok()
89                    })
90            })
91            .filter(|candidate_path: &PathBuf| -> bool {
92                !self.exclude_patterns.iter().any(|exclude_pattern| {
93                    let candidate_path_str = candidate_path.to_str().unwrap();
94                    exclude_pattern.matches(candidate_path_str)
95                })
96            })
97            .collect()
98    }
99}