Add coversion to a weighted table to v0
This commit is contained in:
parent
01ef419505
commit
8a486323df
|
@ -12,7 +12,7 @@ use std::{
|
|||
use data_encoding::DecodeError;
|
||||
|
||||
/// An error occured while trying to parse a user's prefstring
|
||||
#[derive(Clone, Debug)]
|
||||
#[derive(Clone, Debug, Eq, PartialEq)]
|
||||
#[non_exhaustive]
|
||||
pub enum ParseError {
|
||||
|
||||
|
@ -58,6 +58,18 @@ pub enum ParseError {
|
|||
/// was zero bytes long. Often, this means that the developer should simply use the
|
||||
/// instance default preferences.
|
||||
ZeroLengthPrefstring,
|
||||
|
||||
/// The prefstring assumes the existance of more pronouns than exist
|
||||
///
|
||||
/// Often a symptom of a malformed prefstring, this error occurs when the prefstring
|
||||
/// is correctly formatted, but when a [`pronouns_today::WeightedTable`] is formed
|
||||
/// from it, the prefstring refers to a pronoun with an index greater than is in the
|
||||
/// list. For example, setting the weight of pronoun set number 20 when there's only
|
||||
/// 15 pronoun sets known by the instance.
|
||||
///
|
||||
/// This can be caused by reducing the number of available pronouns after a prefstring
|
||||
/// has been generated, or by trying to parse a lucky malformed prefstring.
|
||||
PrefstringExceedsPronounCount,
|
||||
}
|
||||
|
||||
impl fmt::Display for ParseError {
|
||||
|
@ -92,6 +104,9 @@ impl fmt::Display for ParseError {
|
|||
},
|
||||
ParseError::ZeroLengthPrefstring => {
|
||||
write!(f, "Tried to parse a zero-length prefstring")
|
||||
},
|
||||
ParseError::PrefstringExceedsPronounCount => {
|
||||
write!(f, "The user preferences refers to pronouns beyond those that are known")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -39,7 +39,7 @@ pub trait Preference {
|
|||
/// crucial step to randomly selecting a pronoun set based on a user's preferences, as
|
||||
/// any selection is done by using a [`WeightedTable`]. All preference versions must
|
||||
/// implement this method.
|
||||
fn into_weighted_table<'a>(&self, settings: &'a InstanceSettings) -> WeightedTable<'a>;
|
||||
fn into_weighted_table<'a>(&self, settings: &'a InstanceSettings) -> Result<WeightedTable<'a>, ParseError>;
|
||||
|
||||
/// Parse a given prefstring, after it's extraction from base64
|
||||
///
|
||||
|
@ -78,7 +78,7 @@ pub trait Preference {
|
|||
}
|
||||
|
||||
impl Preference for UserPreferences {
|
||||
fn into_weighted_table<'a>(&self, settings: &'a InstanceSettings) -> WeightedTable<'a> {
|
||||
fn into_weighted_table<'a>(&self, settings: &'a InstanceSettings) -> Result<WeightedTable<'a>, ParseError> {
|
||||
match self {
|
||||
UserPreferences::V0(pref) => pref,
|
||||
}.into_weighted_table(settings)
|
||||
|
|
|
@ -6,6 +6,11 @@ use crate::{
|
|||
WeightedTable,
|
||||
};
|
||||
|
||||
use std::{
|
||||
collections::HashMap,
|
||||
iter::{Extend, self},
|
||||
};
|
||||
|
||||
/// A parsed version of the V0 prefstring
|
||||
///
|
||||
/// See the [prefstring specification][1] for more information about how this is interpretted.
|
||||
|
@ -18,8 +23,58 @@ pub struct UserPreferencesV0 {
|
|||
}
|
||||
|
||||
impl Preference for UserPreferencesV0 {
|
||||
fn into_weighted_table<'a>(&self, settings: &'a InstanceSettings) -> WeightedTable<'a> {
|
||||
todo!()
|
||||
fn into_weighted_table<'a>(&self, settings: &'a InstanceSettings) -> Result<WeightedTable<'a>, ParseError> {
|
||||
let mut remaining_pronouns = settings.pronoun_list.iter();
|
||||
let mut weighted_pronouns = HashMap::with_capacity(settings.pronoun_list.len());
|
||||
|
||||
for command in &self.commands {
|
||||
match command {
|
||||
|
||||
Command::SetWeight(weight) => {
|
||||
if *weight > 0 {
|
||||
weighted_pronouns.insert(
|
||||
remaining_pronouns.next()
|
||||
.ok_or(ParseError::PrefstringExceedsPronounCount)?,
|
||||
*weight
|
||||
);
|
||||
}
|
||||
},
|
||||
|
||||
Command::Move { toggle_enabled, distance } => {
|
||||
|
||||
let skipped_pronouns = iter::repeat_with(|| remaining_pronouns.next())
|
||||
.take(*distance as usize)
|
||||
.collect::<Option<Vec<_>>>()
|
||||
.ok_or(ParseError::PrefstringExceedsPronounCount)?;
|
||||
|
||||
if self.default_enabled {
|
||||
weighted_pronouns.extend(
|
||||
skipped_pronouns
|
||||
.into_iter()
|
||||
.map(|pronoun| (pronoun, self.default_weight))
|
||||
)
|
||||
}
|
||||
|
||||
let last_enabled = *toggle_enabled != self.default_enabled;
|
||||
let last_pronoun = remaining_pronouns.next()
|
||||
.ok_or(ParseError::PrefstringExceedsPronounCount)?;
|
||||
if last_enabled {
|
||||
weighted_pronouns.insert(last_pronoun, self.default_weight);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
if self.default_enabled {
|
||||
weighted_pronouns.extend(
|
||||
remaining_pronouns
|
||||
.into_iter()
|
||||
.map(|pronoun| (pronoun, self.default_weight))
|
||||
);
|
||||
}
|
||||
|
||||
Ok(WeightedTable(weighted_pronouns))
|
||||
}
|
||||
|
||||
/// ```
|
||||
|
@ -109,3 +164,198 @@ impl From<u8> for Command {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::{InstanceSettings, WeightedTable};
|
||||
use crate::user_preferences::{Preference, ParseError};
|
||||
use crate::user_preferences::v0::{UserPreferencesV0, Command};
|
||||
|
||||
use std::collections::HashMap;
|
||||
|
||||
fn testing_instance_settings() -> InstanceSettings {
|
||||
InstanceSettings {
|
||||
pronoun_list: vec![
|
||||
["she", "her", "her", "hers", "herself" ].into(),
|
||||
["he", "him", "his", "his", "himself" ].into(),
|
||||
["they", "them", "their", "theirs", "themself" ].into(),
|
||||
["it", "it", "its", "its", "itself" ].into(),
|
||||
["xe", "xem", "xyr", "xyrs", "xemself" ].into(),
|
||||
],
|
||||
}
|
||||
}
|
||||
|
||||
fn check_table(actual: WeightedTable, expected: Vec<(&str, u8)>) {
|
||||
let actual_simplified = actual.0.into_iter()
|
||||
.filter(|(pronoun, weight)| *weight > 0)
|
||||
.map(|(pronoun, weight)| (pronoun.subject_pronoun.as_str(), weight))
|
||||
.collect::<HashMap<&str, u8>>();
|
||||
let expected_owned = expected.into_iter()
|
||||
.map(|(pronoun, weight)| (pronoun, weight))
|
||||
.collect::<HashMap<&str, u8>>();
|
||||
|
||||
assert_eq!(actual_simplified, expected_owned);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_into_weighted_table_no_commands() {
|
||||
let s = testing_instance_settings();
|
||||
let p = UserPreferencesV0 {
|
||||
default_enabled: true,
|
||||
default_weight: 2,
|
||||
commands: Vec::new(),
|
||||
};
|
||||
|
||||
let table = p.into_weighted_table(&s).unwrap();
|
||||
let expected_table = vec![
|
||||
("she", 2),
|
||||
("he", 2),
|
||||
("they", 2),
|
||||
("it", 2),
|
||||
("xe", 2),
|
||||
];
|
||||
|
||||
check_table(table, expected_table);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_into_weighted_table_move_commands_select_deactivation() {
|
||||
let s = testing_instance_settings();
|
||||
let p = UserPreferencesV0 {
|
||||
default_enabled: true,
|
||||
default_weight: 3,
|
||||
commands: vec![
|
||||
Command::Move {
|
||||
toggle_enabled: false,
|
||||
distance: 1
|
||||
},
|
||||
Command::Move {
|
||||
toggle_enabled: true,
|
||||
distance: 2
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
let table = p.into_weighted_table(&s).unwrap();
|
||||
let expected_table = vec![
|
||||
("she", 3),
|
||||
("he", 3),
|
||||
("they", 3),
|
||||
("it", 3),
|
||||
];
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_into_weighted_table_weight_commands() {
|
||||
let s = testing_instance_settings();
|
||||
let p = UserPreferencesV0 {
|
||||
default_enabled: false,
|
||||
default_weight: 1,
|
||||
commands: vec![
|
||||
Command::SetWeight(5),
|
||||
Command::SetWeight(4),
|
||||
Command::SetWeight(3),
|
||||
],
|
||||
};
|
||||
|
||||
let table = p.into_weighted_table(&s).unwrap();
|
||||
let expected_table = vec![
|
||||
("she", 5),
|
||||
("he", 4),
|
||||
("they", 3),
|
||||
];
|
||||
|
||||
check_table(table, expected_table);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_into_weighted_table_combined_commands() {
|
||||
let s = testing_instance_settings();
|
||||
let p = UserPreferencesV0 {
|
||||
default_enabled: false,
|
||||
default_weight: 9,
|
||||
commands: vec![
|
||||
Command::Move {
|
||||
toggle_enabled: true,
|
||||
distance: 1
|
||||
},
|
||||
Command::SetWeight(5),
|
||||
Command::Move {
|
||||
toggle_enabled: false,
|
||||
distance: 0
|
||||
},
|
||||
Command::SetWeight(4),
|
||||
],
|
||||
};
|
||||
|
||||
let table = p.into_weighted_table(&s).unwrap();
|
||||
let expected_table = vec![
|
||||
("he", 9),
|
||||
("they", 5),
|
||||
("xe", 4),
|
||||
];
|
||||
|
||||
check_table(table, expected_table);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_into_weighted_table_move_past_boundry() {
|
||||
let s = testing_instance_settings();
|
||||
let p = UserPreferencesV0 {
|
||||
default_enabled: false,
|
||||
default_weight: 1,
|
||||
commands: vec![
|
||||
Command::Move {
|
||||
toggle_enabled: true,
|
||||
distance: 5
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
let err = p.into_weighted_table(&s).unwrap_err();
|
||||
assert_eq!(err, ParseError::PrefstringExceedsPronounCount);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_into_weighted_table_set_weight_past_boundry() {
|
||||
let s = testing_instance_settings();
|
||||
let p = UserPreferencesV0 {
|
||||
default_enabled: false,
|
||||
default_weight: 1,
|
||||
commands: vec![
|
||||
Command::Move {
|
||||
toggle_enabled: true,
|
||||
distance: 4
|
||||
},
|
||||
Command::SetWeight(9),
|
||||
],
|
||||
};
|
||||
|
||||
let err = p.into_weighted_table(&s).unwrap_err();
|
||||
assert_eq!(err, ParseError::PrefstringExceedsPronounCount);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_into_weighted_table_zero_default_weight() {
|
||||
let s = testing_instance_settings();
|
||||
let p = UserPreferencesV0 {
|
||||
default_enabled: true,
|
||||
default_weight: 0,
|
||||
commands: vec![
|
||||
Command::Move {
|
||||
toggle_enabled: true,
|
||||
distance: 2
|
||||
},
|
||||
Command::SetWeight(3),
|
||||
],
|
||||
};
|
||||
|
||||
let table = p.into_weighted_table(&s).unwrap();
|
||||
let expected_table = vec![
|
||||
("it", 3),
|
||||
];
|
||||
|
||||
check_table(table, expected_table);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
use crate::Pronoun;
|
||||
|
||||
use std::collections::HashMap;
|
||||
|
||||
use time::{Date, Month, OffsetDateTime};
|
||||
|
||||
/// The start of the COVID-19 lockdowns
|
||||
|
@ -19,7 +21,8 @@ pub const COVID_EPOCH: Date = match Date::from_calendar_date(2020, Month::Januar
|
|||
/// This struct is use to represent these weights before they are used to randomly select a
|
||||
/// pronoun. Additional methods are provided to perform this random selection on a weighted list,
|
||||
/// using as a seed both an arbitrary string of bytes and a Date.
|
||||
pub struct WeightedTable<'a>(pub Vec<(&'a Pronoun, u8)>);
|
||||
#[derive(Clone, PartialEq, Eq, Debug)]
|
||||
pub struct WeightedTable<'a>(pub HashMap<&'a Pronoun, u8>);
|
||||
|
||||
impl<'a> WeightedTable<'a> {
|
||||
|
||||
|
|
Loading…
Reference in a new issue