1use bitvec::prelude::*;
65use std::fs;
66use std::{collections::HashMap, fmt};
67use yaml_rust::{Yaml, YamlLoader};
68
69pub struct EnumToggles<T> {
71 toggles_value: BitVec,
72 _marker: std::marker::PhantomData<T>,
73}
74
75impl<T> Default for EnumToggles<T>
76where
77 T: strum::IntoEnumIterator + AsRef<str> + 'static,
78{
79 fn default() -> Self {
80 EnumToggles {
81 toggles_value: bitvec![0; T::iter().count()],
82 _marker: std::marker::PhantomData,
83 }
84 }
85}
86
87impl<T> EnumToggles<T>
89where
90 T: strum::IntoEnumIterator + AsRef<str> + PartialEq + 'static,
91{
92 pub fn new() -> Self {
96 let mut toggles: EnumToggles<T> = EnumToggles {
97 toggles_value: bitvec![0; T::iter().count()],
98 _marker: std::marker::PhantomData,
99 };
100 toggles.toggles_value.fill(false);
101 toggles
102 }
103
104 pub fn load_from_file(&mut self, filepath: &str) -> Result<(), Box<dyn std::error::Error>> {
106 let content = fs::read_to_string(filepath)?;
107 let docs = YamlLoader::load_from_str(&content)?;
108 let doc = &docs[0];
109
110 if let Yaml::Hash(ref h) = doc {
111 for (key, value) in h {
112 self.set_by_name(
113 key.as_str().ok_or("Invalid key: not a string")?,
114 value.as_i64().ok_or("Invalid value: not an integer")? == 1,
115 );
116 }
117 }
118
119 Ok(())
120 }
121 pub fn set_all(&mut self, init: HashMap<String, bool>) {
125 self.toggles_value.fill(false);
126 for toggle in T::iter() {
127 if init.contains_key(toggle.as_ref()) {
128 if let Some(toggle_id) = T::iter().position(|x| x == toggle) {
129 self.set(toggle_id, init[toggle.as_ref()]);
130 }
131 }
132 }
133 }
134
135 pub fn set_by_name(&mut self, toggle_name: &str, value: bool) {
139 if let Some(toggle) = T::iter().find(|t| toggle_name == t.as_ref()) {
140 if let Some(toggle_id) = T::iter().position(|x| x == toggle) {
141 self.set(toggle_id, value);
142 }
143 }
144 }
145
146 pub fn set(&mut self, toggle_id: usize, value: bool) {
150 if toggle_id >= self.toggles_value.len() {
151 panic!(
152 "Out-of-bounds access. The provided toggle_id is {}, but the array size is {}. Please use the default enum value.",
153 toggle_id,
154 self.toggles_value.len()
155 );
156 }
157 self.toggles_value.set(toggle_id, value);
158 }
159
160 pub fn get(&self, toggle_id: usize) -> bool {
164 self.toggles_value[toggle_id]
165 }
166}
167
168impl<T> fmt::Debug for EnumToggles<T>
170where
171 T: strum::IntoEnumIterator + AsRef<str> + PartialEq + 'static,
172{
173 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
174 for toggle in T::iter() {
175 if let Some(toggle_id) = T::iter().position(|x| x == toggle) {
176 let name = toggle.as_ref();
177 writeln!(f, "{} {} ", self.get(toggle_id) as u8, name)?;
178 }
179 }
180 Ok(())
181 }
182}
183
184#[cfg(test)]
185mod tests {
186 use super::*;
187 use std::io::Write;
188 use strum::IntoEnumIterator;
189 use strum_macros::{AsRefStr, EnumIter};
190
191 #[derive(AsRefStr, EnumIter, PartialEq)]
192 pub enum TestToggles {
193 Toggle1,
194 Toggle2,
195 }
196
197 #[test]
198 fn test_default() {
199 let toggles: EnumToggles<TestToggles> = EnumToggles::default();
200 assert_eq!(toggles.toggles_value.len(), TestToggles::iter().count());
201 }
202
203 #[test]
204 fn test_set_all() {
205 let mut toggles: EnumToggles<TestToggles> = EnumToggles::new();
206 toggles.set_all(HashMap::from([("Toggle1".to_string(), true)]));
207 assert_eq!(toggles.get(TestToggles::Toggle1 as usize), true);
208 assert_eq!(toggles.get(TestToggles::Toggle2 as usize), false);
209 }
210
211 #[test]
212 fn test_set_by_name() {
213 let mut toggles: EnumToggles<TestToggles> = EnumToggles::new();
214 assert_eq!(toggles.get(TestToggles::Toggle1 as usize), false);
215 toggles.set_by_name("Toggle1", true);
216 assert_eq!(toggles.get(TestToggles::Toggle1 as usize), true);
217
218 toggles.set_by_name("Undefined_Toggle", true);
219 }
220
221 #[test]
222 fn test_display() {
223 let toggles: EnumToggles<TestToggles> = EnumToggles::new();
224 assert_eq!(format!("{:?}", toggles).is_empty(), false);
225 }
226
227 #[test]
228 fn test_load_from_file() {
229 let mut temp_file =
231 tempfile::NamedTempFile::new().expect("Unable to create temporary file");
232
233 writeln!(temp_file, "Toggle1: 1").expect("Unable to write to temporary file");
235 writeln!(temp_file, "Toggle2: 0").expect("Unable to write to temporary file");
236 writeln!(temp_file, "VAR1: 0").expect("Unable to write to temporary file");
237 writeln!(temp_file, "").expect("Unable to write to temporary file");
238
239 let filepath = temp_file.path().to_str().unwrap();
241
242 let mut toggles: EnumToggles<TestToggles> = EnumToggles::new();
244 let _ = toggles.load_from_file(filepath);
245
246 assert_eq!(toggles.get(TestToggles::Toggle1 as usize), true);
248 assert_eq!(toggles.get(TestToggles::Toggle2 as usize), false);
249 }
250
251 #[derive(AsRefStr, EnumIter, PartialEq)]
252 pub enum DeviantToggles {
253 Toggle1 = 5,
254 Toggle2 = 10,
255 }
256
257 #[test]
258 #[should_panic(
259 expected = "Out-of-bounds access. The provided toggle_id is 5, but the array size is 2. Please use the default enum value."
260 )]
261 fn test_deviant_toggles() {
262 let mut toggles: EnumToggles<DeviantToggles> = EnumToggles::new();
263 toggles.set(DeviantToggles::Toggle1 as usize, true);
264 }
265}