PronounsToday/src/lib.rs

365 lines
11 KiB
Rust

//! 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
#![allow(clippy::tabs_in_doc_comments)]
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<Pronoun>,
}
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<UserPreferences, ParseError> {
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<Pronoun> = 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<Pronoun> 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<Pronoun> = 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<Pronoun>);
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<PronounList, Self::Err> {
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::<Result<Vec<Pronoun>, PronounListError>>()
.map(PronounList)
}
}
impl From<PronounList> for Vec<Pronoun> {
fn from(list: PronounList) -> Vec<Pronoun> {
list.0
}
}
impl From<Vec<Pronoun>> for PronounList {
fn from(list: Vec<Pronoun>) -> 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 {}