use bitvec::prelude::*;
use std::fs;
use std::{collections::HashMap, fmt};
use yaml_rust::{Yaml, YamlLoader};
pub struct EnumToggles<T> {
toggles_value: BitVec,
_marker: std::marker::PhantomData<T>,
}
impl<T> Default for EnumToggles<T>
where
T: strum::IntoEnumIterator + AsRef<str> + 'static,
{
fn default() -> Self {
EnumToggles {
toggles_value: bitvec![0; T::iter().count()],
_marker: std::marker::PhantomData,
}
}
}
impl<T> EnumToggles<T>
where
T: strum::IntoEnumIterator + AsRef<str> + PartialEq + 'static,
{
pub fn new() -> Self {
let mut toggles: EnumToggles<T> = EnumToggles {
toggles_value: bitvec![0; T::iter().count()],
_marker: std::marker::PhantomData,
};
toggles.toggles_value.fill(false);
toggles
}
pub fn load_from_file(&mut self, filepath: &str) -> Result<(), Box<dyn std::error::Error>> {
let content = fs::read_to_string(filepath)?;
let docs = YamlLoader::load_from_str(&content)?;
let doc = &docs[0];
if let Yaml::Hash(ref h) = doc {
for (key, value) in h {
self.set_by_name(
key.as_str().ok_or("Invalid key: not a string")?,
value.as_i64().ok_or("Invalid value: not an integer")? == 1,
);
}
}
Ok(())
}
pub fn set_all(&mut self, init: HashMap<String, bool>) {
self.toggles_value.fill(false);
for toggle in T::iter() {
if init.contains_key(toggle.as_ref()) {
if let Some(toggle_id) = T::iter().position(|x| x == toggle) {
self.set(toggle_id, init[toggle.as_ref()]);
}
}
}
}
pub fn set_by_name(&mut self, toggle_name: &str, value: bool) {
if let Some(toggle) = T::iter().find(|t| toggle_name == t.as_ref()) {
if let Some(toggle_id) = T::iter().position(|x| x == toggle) {
self.set(toggle_id, value);
}
}
}
pub fn set(&mut self, toggle_id: usize, value: bool) {
if toggle_id >= self.toggles_value.len() {
panic!(
"Out-of-bounds access. The provided toggle_id is {}, but the array size is {}. Please use the default enum value.",
toggle_id,
self.toggles_value.len()
);
}
self.toggles_value.set(toggle_id, value);
}
pub fn get(&self, toggle_id: usize) -> bool {
self.toggles_value[toggle_id]
}
}
impl<T> fmt::Debug for EnumToggles<T>
where
T: strum::IntoEnumIterator + AsRef<str> + PartialEq + 'static,
{
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
for toggle in T::iter() {
if let Some(toggle_id) = T::iter().position(|x| x == toggle) {
let name = toggle.as_ref();
writeln!(f, "{} {} ", self.get(toggle_id) as u8, name)?;
}
}
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::io::Write;
use strum::IntoEnumIterator;
use strum_macros::{AsRefStr, EnumIter};
#[derive(AsRefStr, EnumIter, PartialEq)]
pub enum TestToggles {
Toggle1,
Toggle2,
}
#[test]
fn test_default() {
let toggles: EnumToggles<TestToggles> = EnumToggles::default();
assert_eq!(toggles.toggles_value.len(), TestToggles::iter().count());
}
#[test]
fn test_set_all() {
let mut toggles: EnumToggles<TestToggles> = EnumToggles::new();
toggles.set_all(HashMap::from([("Toggle1".to_string(), true)]));
assert_eq!(toggles.get(TestToggles::Toggle1 as usize), true);
assert_eq!(toggles.get(TestToggles::Toggle2 as usize), false);
}
#[test]
fn test_set_by_name() {
let mut toggles: EnumToggles<TestToggles> = EnumToggles::new();
assert_eq!(toggles.get(TestToggles::Toggle1 as usize), false);
toggles.set_by_name("Toggle1", true);
assert_eq!(toggles.get(TestToggles::Toggle1 as usize), true);
toggles.set_by_name("Undefined_Toggle", true);
}
#[test]
fn test_display() {
let toggles: EnumToggles<TestToggles> = EnumToggles::new();
assert_eq!(format!("{:?}", toggles).is_empty(), false);
}
#[test]
fn test_load_from_file() {
let mut temp_file =
tempfile::NamedTempFile::new().expect("Unable to create temporary file");
writeln!(temp_file, "Toggle1: 1").expect("Unable to write to temporary file");
writeln!(temp_file, "Toggle2: 0").expect("Unable to write to temporary file");
writeln!(temp_file, "VAR1: 0").expect("Unable to write to temporary file");
writeln!(temp_file, "").expect("Unable to write to temporary file");
let filepath = temp_file.path().to_str().unwrap();
let mut toggles: EnumToggles<TestToggles> = EnumToggles::new();
let _ = toggles.load_from_file(filepath);
assert_eq!(toggles.get(TestToggles::Toggle1 as usize), true);
assert_eq!(toggles.get(TestToggles::Toggle2 as usize), false);
}
#[derive(AsRefStr, EnumIter, PartialEq)]
pub enum DeviantToggles {
Toggle1 = 5,
Toggle2 = 10,
}
#[test]
#[should_panic(
expected = "Out-of-bounds access. The provided toggle_id is 5, but the array size is 2. Please use the default enum value."
)]
fn test_deviant_toggles() {
let mut toggles: EnumToggles<DeviantToggles> = EnumToggles::new();
toggles.set(DeviantToggles::Toggle1 as usize, true);
}
}