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}