config_load/location/
file.rs1use 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}