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) {
106 match fs::read_to_string(filepath) {
107 Ok(content) => {
108 let docs = YamlLoader::load_from_str(&content).unwrap();
109 let doc = &docs[0];
110
111 if let Yaml::Hash(ref h) = doc {
112 for (key, value) in h {
113 self.set_by_name(
114 key.as_str().unwrap_or("<non-string>"),
115 value.as_i64().unwrap_or(0) == 1,
116 );
117 }
118 }
119 }
120 Err(e) => println!("Error reading file: {}", e),
121 }
122 }
123
124 pub fn set_all(&mut self, init: HashMap<String, bool>) {
128 self.toggles_value.fill(false);
129 for toggle in T::iter() {
130 if init.contains_key(toggle.as_ref()) {
131 if let Some(toggle_id) = T::iter().position(|x| x == toggle) {
132 self.set(toggle_id, init[toggle.as_ref()]);
133 }
134 }
135 }
136 }
137
138 pub fn set_by_name(&mut self, toggle_name: &str, value: bool) {
142 if let Some(toggle) = T::iter().find(|t| toggle_name == t.as_ref()) {
143 if let Some(toggle_id) = T::iter().position(|x| x == toggle) {
144 self.set(toggle_id, value);
145 }
146 }
147 }
148
149 pub fn set(&mut self, toggle_id: usize, value: bool) {
153 if toggle_id >= self.toggles_value.len() {
154 panic!(
155 "Out-of-bounds access. The provided toggle_id is {}, but the array size is {}. Please use the default enum value.",
156 toggle_id,
157 self.toggles_value.len()
158 );
159 }
160 self.toggles_value.set(toggle_id, value);
161 }
162
163 pub fn get(&self, toggle_id: usize) -> bool {
167 self.toggles_value[toggle_id]
168 }
169}
170
171impl<T> fmt::Debug for EnumToggles<T>
173where
174 T: strum::IntoEnumIterator + AsRef<str> + PartialEq + 'static,
175{
176 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
177 for toggle in T::iter() {
178 if let Some(toggle_id) = T::iter().position(|x| x == toggle) {
179 let name = toggle.as_ref();
180 writeln!(f, "{} {} ", self.get(toggle_id) as u8, name)?;
181 }
182 }
183 Ok(())
184 }
185}
186
187#[cfg(test)]
188mod tests {
189 use super::*;
190 use std::io::Write;
191 use strum::IntoEnumIterator;
192 use strum_macros::{AsRefStr, EnumIter};
193
194 #[derive(AsRefStr, EnumIter, PartialEq)]
195 pub enum TestToggles {
196 Toggle1,
197 Toggle2,
198 }
199
200 #[test]
201 fn test_default() {
202 let toggles: EnumToggles<TestToggles> = EnumToggles::default();
203 assert_eq!(toggles.toggles_value.len(), TestToggles::iter().count());
204 }
205
206 #[test]
207 fn test_set_all() {
208 let mut toggles: EnumToggles<TestToggles> = EnumToggles::new();
209 toggles.set_all(HashMap::from([("Toggle1".to_string(), true)]));
210 assert_eq!(toggles.get(TestToggles::Toggle1 as usize), true);
211 assert_eq!(toggles.get(TestToggles::Toggle2 as usize), false);
212 }
213
214 #[test]
215 fn test_set_by_name() {
216 let mut toggles: EnumToggles<TestToggles> = EnumToggles::new();
217 assert_eq!(toggles.get(TestToggles::Toggle1 as usize), false);
218 toggles.set_by_name("Toggle1", true);
219 assert_eq!(toggles.get(TestToggles::Toggle1 as usize), true);
220
221 toggles.set_by_name("Undefined_Toggle", true);
222 }
223
224 #[test]
225 fn test_display() {
226 let toggles: EnumToggles<TestToggles> = EnumToggles::new();
227 assert_eq!(format!("{:?}", toggles).is_empty(), false);
228 }
229
230 #[test]
231 fn test_load_from_file() {
232 let mut temp_file =
234 tempfile::NamedTempFile::new().expect("Unable to create temporary file");
235
236 writeln!(temp_file, "Toggle1: 1").expect("Unable to write to temporary file");
238 writeln!(temp_file, "Toggle2: 0").expect("Unable to write to temporary file");
239 writeln!(temp_file, "VAR1: 0").expect("Unable to write to temporary file");
240 writeln!(temp_file, "").expect("Unable to write to temporary file");
241
242 let filepath = temp_file.path().to_str().unwrap();
244
245 let mut toggles: EnumToggles<TestToggles> = EnumToggles::new();
247 toggles.load_from_file(filepath);
248
249 assert_eq!(toggles.get(TestToggles::Toggle1 as usize), true);
251 assert_eq!(toggles.get(TestToggles::Toggle2 as usize), false);
252 }
253
254 #[derive(AsRefStr, EnumIter, PartialEq)]
255 pub enum DeviantToggles {
256 Toggle1 = 5,
257 Toggle2 = 10,
258 }
259
260 #[test]
261 #[should_panic(
262 expected = "Out-of-bounds access. The provided toggle_id is 5, but the array size is 2. Please use the default enum value."
263 )]
264 fn test_deviant_toggles() {
265 let mut toggles: EnumToggles<DeviantToggles> = EnumToggles::new();
266 toggles.set(DeviantToggles::Toggle1 as usize, true);
267 }
268}