use serde::{Deserialize, Serialize};
use std::borrow::Cow;
use std::fmt::{Debug, Display};
use crate::language::Word;
#[derive(Clone, Serialize, PartialEq, Eq, PartialOrd, Ord, Hash, Deserialize, Debug)]
pub enum Pluralization {
None,
Irregular(String),
Regular,
}
impl Pluralization {
pub(crate) fn is_some(&self) -> bool {
!matches!(self, Self::None)
}
pub(crate) fn as_cow<'a>(&'a self, singular: &'a str) -> Cow<'a, str> {
match self {
Self::None => Cow::Borrowed(singular),
Self::Irregular(plural) => Cow::Borrowed(plural),
Self::Regular => {
if singular.ends_with('s')
|| singular.ends_with('x')
|| singular.ends_with('z')
|| singular.ends_with("ch")
|| singular.ends_with("sh")
{
return Cow::Owned(format!("{}es", singular));
}
if let Some(stem) = singular.strip_suffix("fe") {
return Cow::Owned(format!("{}ves", stem));
}
if let Some(stem) = singular.strip_suffix('f') {
return Cow::Owned(format!("{}ves", stem));
}
if let Some(char_before_y) = singular.strip_suffix('y') {
if let Some(last_char) = char_before_y.chars().last() {
let is_vowel = matches!(last_char, 'a' | 'e' | 'i' | 'o' | 'u');
if !is_vowel {
return Cow::Owned(format!("{}ies", char_before_y));
}
}
}
Cow::Owned(format!("{}s", singular))
}
}
}
}
#[derive(Clone, Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct Noun(NounData);
impl Word for Noun {}
#[derive(Clone, Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[serde(rename = "Noun")]
enum NounData {
Proper {
word: String,
},
Common {
singular: String,
plural: Pluralization,
},
Collective {
singular: String,
plural: Pluralization,
},
}
impl Display for Noun {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match &self.0 {
NounData::Proper { word } => write!(f, "{}", word),
NounData::Common { singular, .. } => write!(f, "{}", singular),
NounData::Collective { singular, .. } => write!(f, "{}", singular),
}
}
}
impl Debug for Noun {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match &self.0 {
NounData::Proper { word } => f.debug_struct("Proper").field("word", word).finish(),
NounData::Common { singular, plural } => f
.debug_struct("Common")
.field("singular", singular)
.field("plural", plural)
.finish(),
NounData::Collective { singular, plural } => f
.debug_struct("Collective")
.field("singular", singular)
.field("plural", plural)
.finish(),
}
}
}
impl Noun {
pub fn new_proper<S: Into<String>>(word: S) -> Self {
Self(NounData::Proper {
word: word.into().capitalize(),
})
}
pub fn new_common<S: Into<String>>(singular: S) -> Self {
Self(NounData::Common {
singular: singular.into().to_lowercase(),
plural: Pluralization::Regular,
})
}
pub fn new_common_irregular<S: Into<String>>(singular: S, plural: S) -> Self {
Self(NounData::Common {
singular: singular.into().to_lowercase(),
plural: Pluralization::Irregular(plural.into().to_lowercase()),
})
}
pub fn new_common_uncountable<S: Into<String>>(singular: S) -> Self {
Self(NounData::Common {
singular: singular.into().to_lowercase(),
plural: Pluralization::None,
})
}
pub fn new_collective<S: Into<String>>(singular: S) -> Self {
Self(NounData::Collective {
singular: singular.into().to_lowercase(),
plural: Pluralization::Regular,
})
}
pub fn new_collective_uncountable<S: Into<String>>(singular: S) -> Self {
Self(NounData::Collective {
singular: singular.into().to_lowercase(),
plural: Pluralization::None,
})
}
pub fn new_collective_irregular<S: Into<String>>(singular: S, plural: S) -> Self {
Self(NounData::Collective {
singular: singular.into().to_lowercase(),
plural: Pluralization::Irregular(plural.into().to_lowercase()),
})
}
pub fn plural<'a>(&'a self) -> Cow<'a, str> {
match &self.0 {
NounData::Proper { word } => Cow::Borrowed(&word),
NounData::Common { singular, plural } => plural.as_cow(&singular),
NounData::Collective { singular, plural } => plural.as_cow(&singular),
}
}
pub fn is_countable(&self) -> bool {
match &self.0 {
NounData::Proper { .. } => false,
NounData::Common { plural, .. } => plural.is_some(),
NounData::Collective { plural, .. } => plural.is_some(),
}
}
pub fn is_proper(&self) -> bool {
matches!(&self.0, NounData::Proper { .. })
}
pub fn is_common(&self) -> bool {
matches!(&self.0, NounData::Common { .. })
}
pub fn is_collective(&self) -> bool {
matches!(&self.0, NounData::Collective { .. })
}
}
impl AsRef<str> for Noun {
fn as_ref(&self) -> &str {
match &self.0 {
NounData::Proper { word } => word,
NounData::Common { singular, .. } => singular,
NounData::Collective { singular, .. } => singular,
}
}
}
impl From<String> for Noun {
fn from(s: String) -> Self {
Self::new_proper(s)
}
}
impl From<&str> for Noun {
fn from(s: &str) -> Self {
Self::new_proper(s)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn noun_test() {
let noun = Noun::new_proper("bilbo");
assert_eq!(noun.as_ref(), "Bilbo");
}
#[test]
fn noun_from_test() {
let noun_from_str: Noun = "frodo".into();
assert_eq!(noun_from_str, Noun::new_proper("frodo"));
let noun_from_string: Noun = String::from("samwise").into();
assert_eq!(noun_from_string, Noun::new_proper("samwise"));
}
#[test]
fn common_noun_test() {
let countable_noun = Noun::new_common_irregular("dog", "dogs");
assert_eq!(countable_noun.as_ref(), "dog");
assert_eq!(countable_noun.plural(), "dogs");
assert!(countable_noun.is_countable());
let uncountable_noun = Noun::new_common_uncountable("water");
assert_eq!(uncountable_noun.as_ref(), "water");
assert_eq!(uncountable_noun.plural(), "water"); assert!(!uncountable_noun.is_countable());
}
#[test]
fn collective_noun_test() {
let collective_noun = Noun::new_collective_irregular("flock", "flocks");
assert_eq!(collective_noun.as_ref(), "flock");
assert_eq!(collective_noun.plural(), "flocks");
assert!(collective_noun.is_countable());
}
#[test]
fn noun_type_flags_test() {
assert!(Noun::new_proper("Gandalf").is_proper());
assert!(!Noun::new_proper("Gandalf").is_common());
assert!(Noun::new_common_uncountable("wizard").is_common());
assert!(Noun::new_collective("fellowship").is_collective());
}
#[test]
fn noun_casing_test() {
assert_eq!(Noun::new_proper("gandalf").as_ref(), "Gandalf");
assert_eq!(
Noun::new_common_irregular("Wizard", "WIZARDS").plural(),
"wizards"
);
assert_eq!(Noun::new_collective("Fellowship").as_ref(), "fellowship");
assert_eq!(Noun::new_common_uncountable("WATER").as_ref(), "water");
}
#[test]
fn noun_pluralization_test() {
assert_eq!(Noun::new_collective("company").plural(), "companies");
assert_eq!(Noun::new_common("boy").plural(), "boys");
assert_eq!(Noun::new_common("bus").plural(), "buses");
assert_eq!(Noun::new_common("box").plural(), "boxes");
assert_eq!(Noun::new_common("buzz").plural(), "buzzes");
assert_eq!(Noun::new_common("watch").plural(), "watches");
assert_eq!(Noun::new_common("dish").plural(), "dishes");
assert_eq!(Noun::new_common("cat").plural(), "cats");
assert_eq!(Noun::new_common("leaf").plural(), "leaves");
assert_eq!(Noun::new_common("wolf").plural(), "wolves");
assert_eq!(Noun::new_common("life").plural(), "lives");
assert_eq!(Noun::new_common("knife").plural(), "knives");
assert_eq!(Noun::new_common("dwarf").plural(), "dwarves");
}
}