diff --git a/src/user_preferences/mod.rs b/src/user_preferences/mod.rs index a3f16e2..3b16c4b 100644 --- a/src/user_preferences/mod.rs +++ b/src/user_preferences/mod.rs @@ -65,6 +65,13 @@ pub trait Preference { /// [1]: https://fem.mint.lgbt/Emi/PronounsToday/raw/branch/main/doc/User-Preference-String-Spec.txt fn as_prefstring_bytes(&self) -> Vec; + /// Create a Preference instance from a list of pronoun preferences + /// + /// This should produce an instance of Preference using the provided preferences. + /// `prefs` is a list of pronoun preferences. Each entry is the weight of the pronoun + /// at that index in the pronoun list in the settings. + fn from_preferences(prefs: &[u8]) -> Self where Self: Sized; + /// Parse a base64 prefstring /// /// This is the primary method of creating a `Preference` object from a prefstring. The @@ -128,4 +135,8 @@ impl Preference for UserPreferences { UserPreferences::V0(pref) => pref, }.as_prefstring_bytes() } + + fn from_preferences(prefs: &[u8]) -> Self where Self: Sized { + UserPreferences::V0(UserPreferencesV0::from_preferences(prefs)) + } } diff --git a/src/user_preferences/v0.rs b/src/user_preferences/v0.rs index 805bad8..368918a 100644 --- a/src/user_preferences/v0.rs +++ b/src/user_preferences/v0.rs @@ -17,6 +17,7 @@ use std::{ /// See the [prefstring specification][1] for more information about how this is interpretted. /// /// [1]: https://fem.mint.lgbt/Emi/PronounsToday/raw/branch/main/doc/User-Preference-String-Spec.txt +#[derive(Debug, PartialEq, Eq)] pub struct UserPreferencesV0 { pub default_weight: u8, pub default_enabled: bool, @@ -129,6 +130,91 @@ impl Preference for UserPreferencesV0 { .chain(self.commands.iter().map(|cmd| cmd.into())) .collect() } + + fn from_preferences(prefs: &[u8]) -> Self where Self: Sized { + if prefs.is_empty() { + return Self::default(); + } + let mut weight_counts = vec![0u8; *prefs.iter().max().unwrap() as usize + 1]; + let mut num_zeros = 0; + for w in prefs { + if *w == 0 { + num_zeros += 1; + } else { + weight_counts[*w as usize] += 1; + } + } + let default_weight = weight_counts + .iter() + .enumerate() + .max_by(|(_, v1), (_, v2)| v1.cmp(v2)) + .map(|(w, _)| w as u8) + .unwrap(); + let default_enabled = num_zeros < prefs.len() / 2; + let mut commands = Vec::new(); + if default_enabled { + let mut last_default = -1; + for (i, w) in prefs.iter().enumerate() { + let i = i as isize; + if *w == default_weight { + continue; + } else { + if i - last_default > 1 { + let toggle_enabled = *w == 0; + let distance = if toggle_enabled { + (i - last_default) as u8 - 1 + } else { + (i - last_default) as u8 - 2 + }; + eprintln!("{} {} {} {}", toggle_enabled, distance, i, last_default); + commands.push(Command::Move { + toggle_enabled, + distance + }); + if !toggle_enabled { + commands.push(Command::SetWeight(*w)); + } + } else { + commands.push(Command::SetWeight(*w)); + } + last_default = i; + } + } + } else { + let mut last_enabled = -1; + for (i, w) in prefs.iter().enumerate() { + let i = i as isize; + if *w == 0 { + continue; + } else { + if i - last_enabled > 1 { + let toggle_enabled = *w == default_weight; + let distance = if toggle_enabled { + (i - last_enabled) as u8 - 1 + } else { + (i - last_enabled) as u8 - 2 + }; + eprintln!("{} {} {} {}", toggle_enabled, distance, i, last_enabled); + commands.push(Command::Move { + toggle_enabled, + distance + }); + if !toggle_enabled { + commands.push(Command::SetWeight(*w)); + } + } else { + commands.push(Command::SetWeight(*w)); + } + last_enabled = i; + } + } + } + Self { + default_weight, + default_enabled, + commands, + } + } } /// Default to all pronouns on with equal weight @@ -392,4 +478,132 @@ mod tests { check_table(table, expected_table); } + + #[test] + fn test_from_prefs_most_disabled1() { + let pref_vals = vec![0,1,2,0,0]; + let expected_prefs = UserPreferencesV0 { + default_enabled: false, + default_weight: 2, + commands: vec![ + Command::Move { + toggle_enabled: false, + distance: 0, + }, + Command::SetWeight(1), + Command::SetWeight(2), + ], + }; + let prefs = UserPreferencesV0::from_preferences(&pref_vals); + assert_eq!(prefs, expected_prefs); + } + + #[test] + fn test_from_prefs_most_disabled2() { + let pref_vals = vec![0,1,1,0,0,0,2]; + let expected_prefs = UserPreferencesV0 { + default_enabled: false, + default_weight: 1, + commands: vec![ + Command::Move { + toggle_enabled: true, + distance: 1, + }, + Command::SetWeight(1), + Command::Move { + toggle_enabled: false, + distance: 2, + }, + Command::SetWeight(2), + ], + }; + let prefs = UserPreferencesV0::from_preferences(&pref_vals); + assert_eq!(prefs, expected_prefs); + } + + #[test] + fn test_from_prefs_most_disabled3() { + let pref_vals = vec![0,0,0,0,1,0,0,0,2]; + let expected_prefs = UserPreferencesV0 { + default_enabled: false, + default_weight: 2, + commands: vec![ + Command::Move { + toggle_enabled: false, + distance: 3, + }, + Command::SetWeight(1), + Command::Move { + toggle_enabled: true, + distance: 3, + }, + ], + }; + let prefs = UserPreferencesV0::from_preferences(&pref_vals); + assert_eq!(prefs, expected_prefs); + } + + #[test] + fn test_from_prefs_most_enabled1() { + let pref_vals = vec![0,1,2,1,1]; + let expected_prefs = UserPreferencesV0 { + default_enabled: true, + default_weight: 1, + commands: vec![ + Command::SetWeight(0), + Command::Move { + toggle_enabled: false, + distance: 0, + }, + Command::SetWeight(2), + ], + }; + let prefs = UserPreferencesV0::from_preferences(&pref_vals); + assert_eq!(prefs, expected_prefs); + } + + #[test] + fn test_from_prefs_most_enabled2() { + let pref_vals = vec![1,0,0,1,1,1,2]; + let expected_prefs = UserPreferencesV0 { + default_enabled: true, + default_weight: 1, + commands: vec![ + Command::Move { + toggle_enabled: true, + distance: 1, + }, + Command::SetWeight(0), + Command::Move { + toggle_enabled: false, + distance: 2, + }, + Command::SetWeight(2), + ], + }; + let prefs = UserPreferencesV0::from_preferences(&pref_vals); + assert_eq!(prefs, expected_prefs); + } + + #[test] + fn test_from_prefs_most_enabled3() { + let pref_vals = vec![1,1,1,1,0,1,1,1,2]; + let expected_prefs = UserPreferencesV0 { + default_enabled: true, + default_weight: 1, + commands: vec![ + Command::Move { + toggle_enabled: true, + distance: 4, + }, + Command::Move { + toggle_enabled: false, + distance: 2, + }, + Command::SetWeight(2), + ], + }; + let prefs = UserPreferencesV0::from_preferences(&pref_vals); + assert_eq!(prefs, expected_prefs); + } }