//! The algorithms powering pronouns.today - Random pronouns generated daily //! //! This library contains all of the functionality for selecting a random pronoun based off of a //! user's preferences, including parsing various preference string (prefstring) versions, //! generating a weighted table based off of user preferences, and selecting a pronoun set based //! off of that weighted table. //! //! ## Basic Usage //! //! ``` //! # use pronouns_today::user_preferences::ParseError; //! use pronouns_today::InstanceSettings; //! //! let instance_settings = InstanceSettings::default(); // Or load from a config file //! //! // When you receive a request //! let user_name = Some("Emi"); //! let user_prefstr = Some("acaqqbykawbag"); //! let pronouns = instance_settings.select_pronouns(user_name, user_prefstr)?; //! //! println!("Your pronouns are: {}", pronouns); //! # Ok::<(), ParseError>(()) //! ``` //! //! ## Advanced Usage //! //! The `InstanceSettings::select_pronouns()` method is really just a shorthand for the //! more complex process going on behind the scenes. In reality, there are several steps //! used to select the pronouns. Each step can be modified or run individually for //! greater control. //! //! 1. Configure the [`InstanceSettings`] from a config or default //! 2. Parse the user's prefstring with [`UserPreferences::from_prefstring()`][up] //! 3. Produce a weighted table from the preferences using //! [`UserPreferences::into_weighted_table()`][up] //! 4. Roll a pronoun set from the weighted table with one of the methods in the //! [`WeightedTable`] struct. //! 5. Render the [`Pronoun`]s with one of the provided methods, or use the forms //! individually. //! //! [up]: UserPreferences pub mod user_preferences; pub mod util; mod weighted_table; use std::error::Error; use core::str::FromStr; use std::fmt; use serde::{Serialize, Deserialize, self}; use user_preferences::{ParseError, Preference}; pub use weighted_table::WeightedTable; pub use user_preferences::UserPreferences; /// Runtime-constant setting that apply to an entire pronouns.today instance /// /// These are values specified by the instance operator through the pronouns.today config file. /// /// This is also the struct that is used to perform the majority of operations pertaining parsing /// user preference strings (prefstrings). Utility methods are also provided for parsing a /// prefstring and then selecting a pronoun with it all at once. #[derive(Clone, Serialize, Deserialize, Debug)] pub struct InstanceSettings { /// A list of pronouns recognized by the instance pub pronoun_list: Vec, } impl InstanceSettings { /// Parse a prefstring and then immediately select pronouns from it. /// /// This is shorthand for /// /// ``` /// # use crate::pronouns_today::user_preferences::Preference; /// # use pronouns_today::user_preferences::ParseError; /// # use pronouns_today::InstanceSettings; /// # let settings = InstanceSettings::default(); /// # let name = String::from("Sashanoraa"); /// # let prefstring = String::from("acaqebicadbqaaa"); /// let pronouns = InstanceSettings::parse_prefstring(Some(&prefstring))?.select_pronoun(&settings, Some(&name)); /// # assert_eq!(pronouns, settings.select_pronouns(Some(&name), Some(&prefstring))); /// # Ok::<(), ParseError>(()) /// ``` pub fn select_pronouns(&self, name: Option<&str>, pref_string: Option<&str>) -> Result<&Pronoun, ParseError> { Self::parse_prefstring(pref_string)?.select_pronoun(&self, name) } /// Parse a pref_string. pub fn parse_prefstring(pref_string: Option<&str>) -> Result { match pref_string { Some(pref_string) => UserPreferences::from_prefstring(pref_string), None => Ok(UserPreferences::default()) } } /// Create a UserPreferences instance from a list of weights. /// /// This is shorthand for [`UserPreferences::from_preferences`]. pub fn create_preferences(prefs: &[u8]) -> UserPreferences { UserPreferences::from_preferences(prefs) } } impl Default for InstanceSettings { fn default() -> Self { let pronouns: Vec = 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(), ["ze", "zem", "zyr", "zyrs", "zemself" ].into(), ["fae", "faer", "faer", "faers", "faerself" ].into(), ["ne", "nem", "nir", "nirs", "nirself" ].into(), ["e", "em", "eir", "eirs", "eirself" ].into(), ["vey", "vem", "ver", "vers", "verself" ].into(), ]; InstanceSettings { pronoun_list: pronouns } } } /// A standard five-form pronoun set /// /// All pronouns are configured as five-form pronoun set. This is necessary in order to display /// example sentances to the user. However, typically only two or three forms of the pronoun are /// used to display the pronoun. For example, she/her/her/hers/herself would typically be written /// she/her or she/her/hers. /// /// Methods are provided to quickly generate these shorter forms, as well as example sentences. #[derive(Clone, Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord, Hash)] #[serde(from = "[String; 5]", into = "[String; 5]")] pub struct Pronoun { /// The pronoun's form when used as a subject /// /// For "she", this is "she", as in /// /// > *She* went to the park. pub subject_pronoun: String, /// The pronoun's form when used as an object /// /// For "she", this is "her", as in /// /// > I went with *her* pub object_pronoun: String, /// The pronoun's form when used as a possesive determiner /// /// For "she", this is "her", as in /// /// > I have *her* frisbee pub possesive_determiner: String, /// The pronoun's form when used as a possesive pronoun /// /// For "she", this is "hers", as in /// /// > At least I think it is *hers*. pub possesive_pronoun: String, /// The pronoun's form when used reflexively /// /// For "she", this is "herself", as in /// /// > She did it herself pub reflexive_pronoun: String, } impl Pronoun { pub fn render_threeform(&self) -> String { format!("{}/{}/{}", self.subject_pronoun, self.object_pronoun, self.possesive_pronoun) } } impl fmt::Debug for Pronoun { /// Pronouns are formatted with a vertical bar seperating forms /// /// ## Example /// /// ``` /// # use pronouns_today::Pronoun; /// let pronoun: Pronoun = ["she", "her", "her", "hers", "herself"].into(); /// assert_eq!( /// format!("{:?}", pronoun), /// "Pronoun(she | her | her | hers | herself)" /// ) /// ``` fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "Pronoun({} | {} | {} | {} | {})", self.subject_pronoun, self.object_pronoun, self.possesive_determiner, self.possesive_pronoun, self.reflexive_pronoun, ) } } impl fmt::Display for Pronoun { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.write_str(&self.render_threeform()) } } impl From<[String; 5]> for Pronoun { fn from(five_form: [String; 5]) -> Self { let mut five_form = IntoIterator::into_iter(five_form); // If anyone knows a better way of deconstructing this, *please* let me know, or PR Pronoun { subject_pronoun: five_form.next().unwrap(), object_pronoun: five_form.next().unwrap(), possesive_determiner: five_form.next().unwrap(), possesive_pronoun: five_form.next().unwrap(), reflexive_pronoun: five_form.next().unwrap(), } } } impl From<[&str; 5]> for Pronoun { fn from(five_form: [&str; 5]) -> Self { (&five_form).into() } } impl From<&[&str; 5]> for Pronoun { fn from(five_form: &[&str; 5]) -> Self { Pronoun { subject_pronoun: five_form[0].to_string(), object_pronoun: five_form[1].to_string(), possesive_determiner: five_form[2].to_string(), possesive_pronoun: five_form[3].to_string(), reflexive_pronoun: five_form[4].to_string(), } } } impl From for [String; 5] { fn from(pronoun: Pronoun) -> Self { [ pronoun.subject_pronoun, pronoun.object_pronoun, pronoun.possesive_determiner, pronoun.possesive_pronoun, pronoun.reflexive_pronoun, ] } } /// An intermediary struct to facilitate parsing and serializing lists of pronouns /// /// ## Examples /// /// ``` /// use pronouns_today::PronounList; /// /// let list: PronounList = vec![ /// ["she", "her", "her", "hers", "herself" ].into(), /// ["he", "him", "his", "his", "himself" ].into(), /// ].into(); /// /// assert_eq!( /// list.to_string(), /// "she/her/her/hers/herself,he/him/his/his/himself" /// ) /// ``` /// /// ``` /// use pronouns_today::PronounList; /// use pronouns_today::Pronoun; /// /// let unparsed = "sea/sear/sear/sears/seaself,po/pony/ponys/ponys/ponyself"; /// let parsed: PronounList = unparsed.parse().unwrap(); /// let parsed_vec: Vec = parsed.into(); /// /// assert_eq!( /// parsed_vec, /// vec![ /// ["sea", "sear", "sear", "sears", "seaself" ].into(), /// ["po", "pony", "ponys", "ponys", "ponyself" ].into(), /// ] /// ) /// ``` #[derive(Clone, Debug, Default, Eq, PartialEq)] pub struct PronounList(Vec); impl fmt::Display for PronounList { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { for set_i in 0..self.0.len() { let set = &self.0[set_i]; if set_i > 0 { write!(f, ",")?; } write!(f, "{}/{}/{}/{}/{}", set.subject_pronoun, set.object_pronoun, set.possesive_determiner, set.possesive_pronoun, set.reflexive_pronoun, )?; } Ok(()) } } impl FromStr for PronounList { type Err = PronounListError; fn from_str(s: &str) -> Result { s.split(',') .map(|s| { let split: Vec<&str> = s.split('/').collect(); if split.len() != 5 { Err(PronounListError) } else { Ok(Pronoun { subject_pronoun: split[0].into(), object_pronoun: split[1].into(), possesive_determiner: split[2].into(), possesive_pronoun: split[3].into(), reflexive_pronoun: split[4].into(), }) } }) .collect::, PronounListError>>() .map(PronounList) } } impl From for Vec { fn from(list: PronounList) -> Vec { list.0 } } impl From> for PronounList { fn from(list: Vec) -> PronounList { PronounList(list) } } #[derive(Clone, Copy, Debug, Eq, PartialEq)] pub struct PronounListError; impl fmt::Display for PronounListError { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { f.write_str("Pronouns must be written in their five-form form, with each form seperated by a slash (/), and each set seperated by a comma (,). For example: 'he/him/his/his/himself,she/her/her/hers/herself'") } } impl Error for PronounListError {}