strut_config/scanner/
file.rs1use config::{File, FileFormat, FileSourceFile};
2use std::cmp::Ordering;
3use std::path::PathBuf;
4use strut_core::AppProfile;
5
6#[derive(Debug, Eq, PartialEq)]
8pub enum ConfigFile {
9 GenericToml(PathBuf),
11
12 GenericYaml(PathBuf),
14
15 SpecificToml {
17 path: PathBuf,
19
20 profile: String,
22 },
23
24 SpecificYaml {
26 path: PathBuf,
28
29 profile: String,
31 },
32}
33
34impl ConfigFile {
35 pub fn try_at(path: PathBuf) -> Option<Self> {
38 Self::try_make_with_profile(path, None)
39 }
40
41 pub fn try_make_with_profile(path: PathBuf, known_profile: Option<&str>) -> Option<Self> {
45 let name = match path.file_name().and_then(std::ffi::OsStr::to_str) {
47 Some(name) => name,
48 None => return None,
49 };
50
51 let chunks = name.split('.').collect::<Vec<_>>();
53
54 match *chunks.as_slice() {
56 [_name, extension] => {
57 if is_toml_extension(extension) {
58 Self::toml_from(path, known_profile)
59 } else if is_yaml_extension(extension) {
60 Self::yaml_from(path, known_profile)
61 } else {
62 None
63 }
64 }
65 [_name, profile, extension] => {
66 if let Some(known_profile) = known_profile {
68 if !known_profile.eq_ignore_ascii_case(profile) {
70 return None;
71 }
72 }
73
74 if is_toml_extension(extension) {
76 let profile = profile.to_string();
77 Self::toml_from(path, Some(profile))
78 } else if is_yaml_extension(extension) {
79 let profile = profile.to_string();
80 Self::yaml_from(path, Some(profile))
81 } else {
82 None
83 }
84 }
85 _ => None,
86 }
87 }
88
89 fn toml_from(path: PathBuf, profile: Option<impl Into<String>>) -> Option<Self> {
90 match profile {
91 None => Some(ConfigFile::GenericToml(path)),
92 Some(profile) => {
93 let profile = profile.into();
94
95 Some(ConfigFile::SpecificToml { path, profile })
96 }
97 }
98 }
99
100 fn yaml_from(path: PathBuf, profile: Option<impl Into<String>>) -> Option<Self> {
101 match profile {
102 None => Some(ConfigFile::GenericYaml(path)),
103 Some(profile) => {
104 let profile = profile.into();
105
106 Some(ConfigFile::SpecificYaml { path, profile })
107 }
108 }
109 }
110}
111
112impl ConfigFile {
113 pub fn is_generic(&self) -> bool {
116 match *self {
117 Self::GenericToml(_) | Self::GenericYaml(_) => true,
118 Self::SpecificToml { .. } | Self::SpecificYaml { .. } => false,
119 }
120 }
121
122 pub fn is_specific(&self) -> bool {
125 !self.is_generic()
126 }
127
128 pub fn path(&self) -> &PathBuf {
130 match *self {
131 Self::GenericToml(ref path) => path,
132 Self::GenericYaml(ref path) => path,
133 Self::SpecificToml { ref path, .. } => path,
134 Self::SpecificYaml { ref path, .. } => path,
135 }
136 }
137
138 pub fn profile(&self) -> Option<&str> {
141 match *self {
142 Self::GenericToml(_) => None,
143 Self::GenericYaml(_) => None,
144 Self::SpecificToml { ref profile, .. } => Some(profile),
145 Self::SpecificYaml { ref profile, .. } => Some(profile),
146 }
147 }
148
149 pub fn applies_to_active_profile(&self) -> bool {
152 self.applies_to(AppProfile::active())
153 }
154
155 pub fn applies_to(&self, profile: impl AsRef<AppProfile>) -> bool {
162 let given_profile = profile.as_ref();
163
164 match *self {
165 Self::GenericToml(_) | Self::GenericYaml(_) => true,
166 Self::SpecificToml { ref profile, .. } | Self::SpecificYaml { ref profile, .. } => {
167 given_profile.is(profile)
168 }
169 }
170 }
171}
172
173impl PartialOrd for ConfigFile {
174 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
176 Some(self.cmp(other))
177 }
178}
179
180impl Ord for ConfigFile {
181 fn cmp(&self, other: &Self) -> Ordering {
188 let self_generic = self.is_generic();
190 let other_generic = other.is_generic();
191
192 match (self_generic, other_generic) {
194 (true, false) => return Ordering::Less,
195 (false, true) => return Ordering::Greater,
196 _ => { }
197 }
198
199 let self_path = self.path();
201 let other_path = other.path();
202
203 if self_generic {
205 return self_path.cmp(other_path);
206 }
207
208 let self_profile = self.profile();
210 let other_profile = other.profile();
211
212 match (self_profile, other_profile) {
214 (Some(self_profile_name), Some(other_profile_name)) => {
216 match self_profile_name.cmp(other_profile_name) {
218 Ordering::Equal => self_path.cmp(other_path),
219 non_eq => non_eq,
220 }
221 }
222 _ => self_path.cmp(other_path),
224 }
225 }
226}
227
228fn is_yaml_extension(ext: &str) -> bool {
230 ext.eq_ignore_ascii_case("yml") || ext.eq_ignore_ascii_case("yaml")
231}
232
233fn is_toml_extension(ext: &str) -> bool {
235 ext.eq_ignore_ascii_case("toml")
236}
237
238impl ConfigFile {
239 fn format(&self) -> FileFormat {
241 match *self {
242 Self::GenericToml(_) | Self::SpecificToml { .. } => FileFormat::Toml,
243 Self::GenericYaml(_) | Self::SpecificYaml { .. } => FileFormat::Yaml,
244 }
245 }
246}
247
248impl From<ConfigFile> for PathBuf {
249 fn from(file: ConfigFile) -> Self {
250 match file {
251 ConfigFile::GenericToml(path) => path,
252 ConfigFile::GenericYaml(path) => path,
253 ConfigFile::SpecificToml { path, .. } => path,
254 ConfigFile::SpecificYaml { path, .. } => path,
255 }
256 }
257}
258
259impl From<ConfigFile> for File<FileSourceFile, FileFormat> {
260 fn from(file: ConfigFile) -> Self {
261 let format = file.format();
262 let path = PathBuf::from(file);
263
264 File::from(path).format(format)
265 }
266}