strut_config/scanner/
entry.rs

1use crate::scanner::dir::ConfigDir;
2use crate::ConfigFile;
3use std::path::PathBuf;
4use strut_core::AppProfile;
5
6/// Represents a filesystem entry that is relevant for config files.
7#[derive(Debug, Ord, PartialOrd, Eq, PartialEq)]
8pub enum ConfigEntry {
9    /// A directory.
10    Directory(ConfigDir),
11
12    /// A config file.
13    File(ConfigFile),
14}
15
16impl ConfigEntry {
17    /// Creates a [directory](ConfigEntry::Directory)-type [`ConfigEntry`] from
18    /// the given path with no questions asked (no validation on the path).
19    ///
20    /// This is intended for creating root entries.
21    pub fn dir(path: PathBuf) -> Self {
22        Self::Directory(ConfigDir::at(path))
23    }
24
25    /// Creates a [`ConfigEntry`] from the given [`PathBuf`], if the path points
26    /// to a workable config-related filesystem entry.
27    pub fn try_from(path: PathBuf) -> Option<Self> {
28        Self::try_from_with_profile(path, None)
29    }
30
31    /// Creates a [`ConfigEntry`] from the given [`PathBuf`], if the path points
32    /// to a workable config-related filesystem entry, optionally applying the
33    /// given known profile name to the config files.
34    pub fn try_from_with_profile(path: PathBuf, known_profile: Option<&str>) -> Option<Self> {
35        // A directory is easy
36        if path.is_dir() {
37            let config_dir = ConfigDir::make_with_profile(path, known_profile);
38            return Some(ConfigEntry::Directory(config_dir));
39        }
40
41        // A file is easy too
42        if let Some(config_file) = ConfigFile::try_make_with_profile(path, known_profile) {
43            return Some(ConfigEntry::File(config_file));
44        }
45
46        None
47    }
48}
49
50impl ConfigEntry {
51    /// Reports whether this [`ConfigEntry`] is a
52    /// [directory](ConfigEntry::Directory).
53    pub fn is_directory(&self) -> bool {
54        matches!(self, ConfigEntry::Directory(_))
55    }
56
57    /// Reports whether this [`ConfigEntry`] is a config
58    /// [file](ConfigEntry::File).
59    pub fn is_file(&self) -> bool {
60        matches!(self, ConfigEntry::File(_))
61    }
62
63    /// Returns a reference to the internally held [`PathBuf`].
64    pub fn path(&self) -> &PathBuf {
65        match *self {
66            ConfigEntry::Directory(ref config_dir) => config_dir.path(),
67            ConfigEntry::File(ref config_file) => config_file.path(),
68        }
69    }
70
71    /// Returns a reference to the internally held file/directory name.
72    pub fn name(&self) -> Option<&str> {
73        self.path().file_name().and_then(std::ffi::OsStr::to_str)
74    }
75
76    /// Reports whether this [`ConfigEntry`] [applies](ConfigEntry::applies_to)
77    /// to the [active](AppProfile::active) [`AppProfile`].
78    pub fn applies_to_active_profile(&self) -> bool {
79        self.applies_to(AppProfile::active())
80    }
81
82    /// Reports whether this [`ConfigEntry`] applies to the given [`AppProfile`].
83    ///
84    /// Delegates to the underlying logic for both the
85    /// [directory](ConfigDir::applies_to) and the
86    /// [file](ConfigFile::applies_to) variants.
87    pub fn applies_to(&self, profile: impl AsRef<AppProfile>) -> bool {
88        match *self {
89            ConfigEntry::Directory(ref config_dir) => config_dir.applies_to(profile),
90            ConfigEntry::File(ref config_file) => config_file.applies_to(profile),
91        }
92    }
93
94    /// Consumes this [`ConfigEntry`] and yields only [`ConfigFile`]s.
95    pub fn to_config_file(self) -> Option<ConfigFile> {
96        match self {
97            ConfigEntry::Directory(_) => None,
98            ConfigEntry::File(config_file) => Some(config_file),
99        }
100    }
101}
102
103impl ConfigEntry {
104    /// Iterates over [`ConfigEntry`]s that are the immediate children of this
105    /// [`ConfigEntry`], if this entry is a [directory](ConfigEntry::Directory).
106    /// If this entry is a file, yields a [`Once`](std::iter::Once) iterator
107    /// over that file.
108    ///
109    /// If this entry is a directory that is [associated](ConfigDir::Specific)
110    /// with a profile — the profile is carried over into all nested entries.
111    ///
112    /// All failure conditions (e.g., non-existing paths, un-readable files) are
113    /// silently ignored.
114    ///
115    /// This is intended for convenient flat-mapping to move deeper into nested
116    /// directories, if any.
117    pub fn cd(self) -> ConfigEntryIter {
118        match self {
119            ConfigEntry::Directory(ref config_dir) => {
120                let config_entries = config_dir.expand(config_dir.profile());
121                ConfigEntryIter::Directory(config_entries.into_iter())
122            }
123            ConfigEntry::File(_) => ConfigEntryIter::File(std::iter::once(self)),
124        }
125    }
126
127    /// Same as [`cd`](ConfigEntry::cd), but if this entry is a directory, then
128    /// instead of carrying over the profile that this directory may be
129    /// associated with, captures the profile from this directory’s name.
130    pub fn cd_capturing_profile(self) -> ConfigEntryIter {
131        match self {
132            ConfigEntry::Directory(ref config_dir) => {
133                let config_entries = config_dir.expand(config_dir.name());
134                ConfigEntryIter::Directory(config_entries.into_iter())
135            }
136            ConfigEntry::File(_) => ConfigEntryIter::File(std::iter::once(self)),
137        }
138    }
139
140    /// Same as [`cd`](ConfigEntry::cd), but if this entry is a directory, then
141    /// instead of carrying over the profile that this directory may be
142    /// associated with, explicitly “forgets” any associated profile: the
143    /// children entries will not be associated with any profile.
144    pub fn cd_forgetting_profile(self) -> ConfigEntryIter {
145        match self {
146            ConfigEntry::Directory(ref config_dir) => {
147                let config_entries = config_dir.expand(None);
148                ConfigEntryIter::Directory(config_entries.into_iter())
149            }
150            ConfigEntry::File(_) => ConfigEntryIter::File(std::iter::once(self)),
151        }
152    }
153}
154
155impl From<ConfigEntry> for PathBuf {
156    fn from(file: ConfigEntry) -> Self {
157        match file {
158            ConfigEntry::Directory(config_dir) => PathBuf::from(config_dir),
159            ConfigEntry::File(config_file) => PathBuf::from(config_file),
160        }
161    }
162}
163
164/// Represents an iterator on the expanded [`ConfigEntry`]:
165pub enum ConfigEntryIter {
166    /// For a [directory](ConfigEntry::Directory), iterates over nested
167    /// [`ConfigEntry`]s.
168    Directory(std::vec::IntoIter<ConfigEntry>),
169
170    /// For a [file](ConfigEntry::File), iterates over that single file.
171    File(std::iter::Once<ConfigEntry>),
172}
173
174impl Iterator for ConfigEntryIter {
175    type Item = ConfigEntry;
176
177    fn next(&mut self) -> Option<Self::Item> {
178        match *self {
179            Self::Directory(ref mut iter) => iter.next(),
180            Self::File(ref mut iter) => iter.next(),
181        }
182    }
183
184    fn size_hint(&self) -> (usize, Option<usize>) {
185        match *self {
186            Self::Directory(ref iter) => iter.size_hint(),
187            Self::File(ref iter) => iter.size_hint(),
188        }
189    }
190}