strut_config/scanner/dir.rs
1use crate::ConfigEntry;
2use std::path::PathBuf;
3use strut_core::AppProfile;
4
5/// Represents a single config-related directory.
6///
7/// Unlike with [`ConfigFile`]s, the path validity is not checked on
8/// instantiation. But given that the filesystem is completely external to the
9/// application, the existence or validity of any given path cannot be assumed
10/// anyway.
11#[derive(Debug, Ord, PartialOrd, Eq, PartialEq)]
12pub enum ConfigDir {
13 /// A directory that is not associated with an [`AppProfile`].
14 Generic(PathBuf),
15
16 /// A directory that is associated with an [`AppProfile`] of the given name.
17 Specific {
18 /// The path to the directory.
19 path: PathBuf,
20
21 /// The associated profile name.
22 profile: String,
23 },
24}
25
26impl ConfigDir {
27 /// Creates a [`ConfigDir`] from the given [`PathBuf`], if the path points
28 /// to a workable directory.
29 pub fn at(path: PathBuf) -> Self {
30 Self::make_with_profile(path, None)
31 }
32
33 /// Creates a [`ConfigDir`] from the given [`PathBuf`], if the path points
34 /// to a workable directory, optionally applying the given known profile
35 /// name.
36 pub fn make_with_profile(path: PathBuf, known_profile: Option<&str>) -> Self {
37 // If a profile is known, assign it
38 if let Some(known_profile) = known_profile {
39 return Self::Specific {
40 path,
41 profile: known_profile.to_string(),
42 };
43 }
44
45 // Otherwise, just make a generic directory
46 Self::Generic(path)
47 }
48
49 /// Creates a [`ConfigDir`] from the given [`PathBuf`], attempting to
50 /// capture its name as a profile name. If the name cannot be captured
51 /// (e.g., if the path doesn’t exist), the [generic](ConfigDir::Generic)
52 /// variant is returned.
53 pub fn make_capturing_profile(path: PathBuf) -> Self {
54 // Read file name
55 match path.file_name().and_then(std::ffi::OsStr::to_str) {
56 Some(name) => {
57 let profile = name.to_string();
58 Self::Specific { path, profile }
59 }
60 None => Self::Generic(path),
61 }
62 }
63}
64
65impl ConfigDir {
66 /// Reports the name of this directory, if it is readable.
67 pub fn name(&self) -> Option<&str> {
68 self.path().file_name().and_then(std::ffi::OsStr::to_str)
69 }
70
71 /// Reports whether this [`ConfigDir`] is applicable regardless of the
72 /// [active](AppProfile::active) [`AppProfile`].
73 pub fn is_generic(&self) -> bool {
74 match *self {
75 Self::Generic(_) => true,
76 Self::Specific { .. } => false,
77 }
78 }
79
80 /// Reports whether this [`ConfigDir`] is applicable only to a particular
81 /// [`AppProfile`].
82 pub fn is_specific(&self) -> bool {
83 !self.is_generic()
84 }
85
86 /// Returns a reference to the internally held [`PathBuf`].
87 pub fn path(&self) -> &PathBuf {
88 match *self {
89 Self::Generic(ref path) => path,
90 Self::Specific { ref path, .. } => path,
91 }
92 }
93
94 /// Returns a reference to the internally held profile name (if this
95 /// variant is [specific](ConfigDir::is_specific)).
96 pub fn profile(&self) -> Option<&str> {
97 match *self {
98 Self::Generic(_) => None,
99 Self::Specific { ref profile, .. } => Some(profile),
100 }
101 }
102
103 /// Reports whether this [`ConfigDir`] [applies](ConfigDir::applies_to) to
104 /// the [active](AppProfile::active) [`AppProfile`].
105 pub fn applies_to_active_profile(&self) -> bool {
106 self.applies_to(AppProfile::active())
107 }
108
109 /// Reports whether this [`ConfigDir`] applies to the given [`AppProfile`].
110 ///
111 /// A generic config file (without a profile name in its file name) applies
112 /// to any profile by default. A specific config file (with a profile name
113 /// in its file name) applies to the given profile if the profile name
114 /// matches.
115 pub fn applies_to(&self, profile: impl AsRef<AppProfile>) -> bool {
116 let given_profile = profile.as_ref();
117
118 match *self {
119 Self::Generic(_) => true,
120 Self::Specific { ref profile, .. } => given_profile.is(profile),
121 }
122 }
123
124 /// Expands this directory into a vector of nested [`ConfigEntry`]s.
125 pub fn expand(&self, profile: Option<&str>) -> Vec<ConfigEntry> {
126 std::fs::read_dir(self.path())
127 .into_iter()
128 .flat_map(|read_dir| {
129 read_dir
130 .filter_map(Result::ok)
131 .map(|entry| entry.path())
132 .filter_map(|path| ConfigEntry::try_from_with_profile(path, profile))
133 })
134 .collect::<Vec<_>>()
135 }
136}
137
138impl From<ConfigDir> for PathBuf {
139 fn from(file: ConfigDir) -> Self {
140 match file {
141 ConfigDir::Generic(path) => path,
142 ConfigDir::Specific { path, .. } => path,
143 }
144 }
145}