config_load/location/
file.rs

1use std::env;
2use std::ffi::OsStr;
3use std::path::{Path, PathBuf};
4
5use either::Either;
6
7use crate::location::Location;
8
9pub enum FileLocation {
10    FirstSome(Option<PathBuf>),
11}
12
13impl FileLocation {
14    pub fn first_some_path() -> Self {
15        Self::FirstSome(None)
16    }
17
18    pub fn from_file(mut self, file_path: Option<PathBuf>) -> Self {
19        if let Some(file_path) = file_path {
20            if file_path.is_relative() {
21                return self.from_cwd(file_path);
22            } else {
23                match &mut self {
24                    FileLocation::FirstSome(path) => {
25                        if path.is_none() {
26                            path.replace(file_path);
27                        }
28                    },
29                }
30            }
31        }
32        self
33    }
34
35    pub fn from_file_exists(mut self, file_path: Option<PathBuf>) -> Self {
36        if let Some(file_path) = file_path {
37            if file_path.is_relative() {
38                return self.from_cwd_exists(file_path);
39            } else {
40                match &mut self {
41                    FileLocation::FirstSome(path) => {
42                        if path.is_none() {
43                            *path = existing_file(Some(file_path), Option::<&Path>::None);
44                        }
45                    },
46                }
47            }
48        }
49        self
50    }
51
52    pub fn from_env(mut self, env_var: impl AsRef<OsStr>) -> Self {
53        match &mut self {
54            FileLocation::FirstSome(path) => {
55                if path.is_none() {
56                    *path = env::var(env_var).ok().map(Into::into);
57                }
58            },
59        }
60        self
61    }
62
63    pub fn from_env_exists(mut self, env_var: impl AsRef<OsStr>) -> Self {
64        match &mut self {
65            FileLocation::FirstSome(path) => {
66                if path.is_none() {
67                    *path = existing_file(env::var(env_var).ok().map(Into::into), Option::<&Path>::None);
68                }
69            },
70        }
71        self
72    }
73
74    pub fn from_home(mut self, relative_path: impl AsRef<Path>) -> Self {
75        match &mut self {
76            FileLocation::FirstSome(path) => {
77                if path.is_none() {
78                    *path = env::home_dir().map(|home| home.join(relative_path));
79                }
80            },
81        }
82        self
83    }
84
85    pub fn from_home_exists(mut self, relative_path: impl AsRef<Path>) -> Self {
86        match &mut self {
87            FileLocation::FirstSome(path) => {
88                if path.is_none() {
89                    *path = existing_file(env::home_dir(), Some(relative_path));
90                }
91            },
92        }
93        self
94    }
95
96    pub fn from_cwd(mut self, relative_path: impl AsRef<Path>) -> Self {
97        match &mut self {
98            FileLocation::FirstSome(path) => {
99                if path.is_none() {
100                    *path = env::current_dir().ok().map(|cwd| cwd.join(relative_path));
101                }
102            },
103        }
104        self
105    }
106
107    pub fn from_cwd_exists(mut self, relative_path: impl AsRef<Path>) -> Self {
108        match &mut self {
109            FileLocation::FirstSome(path) => {
110                if path.is_none() {
111                    *path = existing_file(env::current_dir().ok(), Some(relative_path));
112                }
113            },
114        }
115        self
116    }
117
118    pub fn from_cwd_and_parents_exists(mut self, relative_path: impl AsRef<Path>) -> Self {
119        match &mut self {
120            FileLocation::FirstSome(path) => {
121                if path.is_none() {
122                    *path = env::current_dir()
123                        .ok()
124                        .and_then(|cwd| find_existing_file_in_dir_and_parents(cwd, relative_path));
125                }
126            },
127        }
128        self
129    }
130}
131
132impl Location for FileLocation {
133    fn try_into_path(self) -> Either<PathBuf, Self>
134    where
135        Self: Sized,
136    {
137        match self {
138            Self::FirstSome(path) => path
139                .map(Either::Left)
140                .unwrap_or_else(|| Either::Right(Self::first_some_path())),
141        }
142    }
143}
144
145fn existing_file(root_path: Option<PathBuf>, relative_path: Option<impl AsRef<Path>>) -> Option<PathBuf> {
146    root_path.and_then(|root| {
147        let path = if let Some(relative_path) = relative_path {
148            root.join(relative_path)
149        } else {
150            root
151        };
152        path.is_file().then_some(path)
153    })
154}
155
156fn find_existing_file_in_dir_and_parents(dir: impl AsRef<Path>, relative_path: impl AsRef<Path>) -> Option<PathBuf> {
157    let mut current_dir = dir.as_ref();
158    loop {
159        let path = current_dir.join(relative_path.as_ref());
160        if path.is_file() {
161            break Some(path);
162        } else {
163            current_dir = current_dir.parent()?;
164        }
165    }
166}