dotenv_finder/
lib.rs

1use std::path::PathBuf;
2
3use dotenv_core::LineEntry;
4
5use crate::file::Files;
6
7mod file;
8mod fs;
9mod quote;
10
11pub use crate::file::FileEntry;
12
13pub struct Finder<'a> {
14    dir: &'a PathBuf,
15    paths: Vec<PathBuf>,
16    excluded: Vec<PathBuf>,
17    recursive: bool,
18}
19
20pub struct FinderBuilder<'a> {
21    dir: &'a PathBuf,
22    paths: Vec<PathBuf>,
23    excluded: Vec<PathBuf>,
24    recursive: bool,
25}
26
27impl<'a> FinderBuilder<'a> {
28    pub fn new(dir: &'a PathBuf) -> Self {
29        Self {
30            dir,
31            paths: vec![],
32            excluded: vec![],
33            recursive: false,
34        }
35    }
36
37    pub fn with_paths(mut self, paths: &'a [&'a PathBuf]) -> Self {
38        self.paths = paths
39            .iter()
40            .filter_map(|f| fs::canonicalize(f).ok())
41            .collect();
42
43        if self.paths.is_empty() {
44            self.paths.push(self.dir.clone());
45        }
46
47        self
48    }
49
50    pub fn exclude(mut self, exclude: &'a [&'a PathBuf]) -> Self {
51        self.excluded = exclude
52            .iter()
53            .filter_map(|f| fs::canonicalize(f).ok())
54            .collect();
55        self
56    }
57
58    pub fn recursive(mut self, recursive: bool) -> Self {
59        self.recursive = recursive;
60        self
61    }
62
63    pub fn build(self) -> Finder<'a> {
64        Finder {
65            dir: self.dir,
66            paths: self.paths,
67            excluded: self.excluded,
68            recursive: self.recursive,
69        }
70    }
71}
72
73impl<'a> Finder<'a> {
74    pub fn find(&self) -> Files {
75        let files = find_dotenv_paths(self.paths.clone(), self.excluded.as_slice(), self.recursive)
76            .iter()
77            .filter_map(|path: &PathBuf| -> Option<(FileEntry, Vec<LineEntry>)> {
78                fs::get_relative_path(path, self.dir).and_then(FileEntry::from)
79            })
80            .collect();
81
82        Files::new(files)
83    }
84}
85
86fn find_dotenv_paths(
87    dir_entries: Vec<PathBuf>,
88    excludes: &[PathBuf],
89    is_recursive: bool,
90) -> Vec<PathBuf> {
91    let nested_paths: Vec<PathBuf> = dir_entries
92        .iter()
93        .filter(|entry| entry.is_dir())
94        .filter(|entry| !excludes.contains(entry))
95        .filter_map(|dir| dir.read_dir().ok())
96        .map(|read_dir| {
97            read_dir
98                .filter_map(|e| e.ok())
99                .map(|e| e.path())
100                .filter(|path| {
101                    file::is_dotenv_file(path)
102                        || (is_recursive && path.is_dir() && path.read_link().is_err())
103                })
104                .collect()
105        })
106        .flat_map(|dir_entries| find_dotenv_paths(dir_entries, excludes, is_recursive))
107        .collect();
108
109    let mut file_paths: Vec<PathBuf> = dir_entries
110        .into_iter()
111        .filter(|entry| entry.is_file())
112        .filter(|entry| !excludes.contains(entry))
113        .collect();
114
115    file_paths.extend(nested_paths);
116    file_paths.sort();
117    file_paths.dedup();
118    file_paths
119}