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