use alloc::borrow::Cow;
use alloc::string::String;
use rand::{seq::IndexedRandom, RngExt};
use crate::{Generator, List, Lists, Namer};
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct Adjective<'a> {
pub word: &'a str,
pub emphatic: Option<&'a str>,
}
impl<'a> Adjective<'a> {
pub const fn plain(word: &'a str) -> Self {
Self { word, emphatic: None }
}
pub const fn emphatic(word: &'a str, emphatic: &'a str) -> Self {
Self { word, emphatic: Some(emphatic) }
}
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct Petnames<'a> {
pub adjectives: Cow<'a, [Adjective<'a>]>,
pub adverbs: Cow<'a, [&'a str]>,
pub nouns: Cow<'a, [&'a str]>,
}
impl<'a> Petnames<'a> {
#[cfg(feature = "default-words")]
pub fn small() -> Self {
crate::turkish!("words/turkish")
}
pub fn retain<F>(&mut self, mut predicate: F)
where
F: FnMut(&str) -> bool,
{
self.adjectives.to_mut().retain(|adjective| predicate(adjective.word));
self.adverbs.to_mut().retain(|word| predicate(word));
self.nouns.to_mut().retain(|word| predicate(word));
}
pub fn cardinality(&self, words: u8) -> u128 {
Lists::new(words)
.map(|list| match list {
List::Adverb => self.adverbs.len() as u128,
List::Adjective => self.adjectives.len() as u128,
List::Noun => self.nouns.len() as u128,
})
.reduce(u128::saturating_mul)
.unwrap_or(0u128)
}
pub fn namer<'b>(&'b self, words: u8, separator: &'b str) -> Namer<'b, Self> {
Namer { generator: self, words, separator }
}
}
impl Generator for Petnames<'_> {
fn generate_into(&self, buf: &mut String, rng: &mut dyn rand::Rng, words: u8, separator: &str) {
let allow_emphatic = words == 2;
for list in Lists::new(words) {
match list {
List::Adverb => {
if let Some(word) = self.adverbs.choose(rng).copied() {
buf.push_str(word);
buf.push_str(separator);
}
}
List::Adjective => {
if let Some(adjective) = self.adjectives.choose(rng) {
let word = match adjective.emphatic {
Some(form) if allow_emphatic && rng.random_bool(0.5) => form,
_ => adjective.word,
};
buf.push_str(word);
buf.push_str(separator);
}
}
List::Noun => {
if let Some(word) = self.nouns.choose(rng).copied() {
buf.push_str(word);
}
}
};
}
}
}
#[cfg(test)]
mod tests {
use alloc::borrow::Cow;
use alloc::vec;
use super::{Adjective, Petnames};
fn sample() -> Petnames<'static> {
Petnames {
adjectives: Cow::Owned(vec![
Adjective::emphatic("kırmızı", "kıpkırmızı"),
Adjective::plain("güzel"),
]),
adverbs: Cow::Owned(vec!["çok", "oldukça"]),
nouns: Cow::Owned(vec!["kedi", "köpek"]),
}
}
#[cfg(feature = "default-rng")]
fn generate(turkish: &Petnames, words: u8, seed: u64) -> alloc::vec::Vec<alloc::string::String> {
use rand::SeedableRng;
let namer = turkish.namer(words, "-");
let mut rng = rand::rngs::StdRng::seed_from_u64(seed);
(0..50)
.map(|_| {
let mut buf = alloc::string::String::new();
namer.generate_into(&mut buf, &mut rng);
buf
})
.collect()
}
#[cfg(feature = "default-rng")]
#[test]
fn token_count_matches_words() {
let turkish = sample();
for words in 1..=5u8 {
for name in generate(&turkish, words, 1) {
assert_eq!(name.split('-').count(), words as usize, "name was {name:?}");
}
}
}
#[cfg(feature = "default-rng")]
#[test]
fn emphatic_form_only_when_two_words() {
let turkish = sample();
assert!(generate(&turkish, 3, 7).iter().all(|name| !name.contains("kıpkırmızı")));
assert!(generate(&turkish, 2, 7).iter().any(|name| name.contains("kıpkırmızı")));
}
#[cfg(feature = "default-rng")]
#[test]
fn deterministic_under_seed() {
let turkish = sample();
assert_eq!(generate(&turkish, 3, 42), generate(&turkish, 3, 42));
}
#[test]
fn cardinality_counts_combinations() {
let turkish = sample(); assert_eq!(turkish.cardinality(1), 2); assert_eq!(turkish.cardinality(2), 4); assert_eq!(turkish.cardinality(3), 8); assert_eq!(turkish.cardinality(0), 0);
}
#[cfg(feature = "default-words")]
#[test]
fn small_parses_emphatic_and_strips_comments() {
let turkish = Petnames::small();
assert!(turkish.adjectives.contains(&Adjective::emphatic("kırmızı", "kıpkırmızı")));
assert!(turkish.adjectives.contains(&Adjective::plain("güzel")));
assert!(turkish.nouns.contains(&"kedi"));
assert!(!turkish.nouns.iter().any(|word| word.starts_with('#')));
assert!(!turkish.nouns.contains(&"Animals"));
assert!(!turkish.adjectives.iter().any(|adjective| adjective.word.contains('=')));
}
#[test]
fn retain_filters_all_lists() {
let mut turkish = sample();
turkish.retain(|word| word.chars().count() <= 5);
assert!(turkish.adjectives.iter().all(|adjective| adjective.word.chars().count() <= 5));
assert!(turkish.adverbs.iter().all(|word| word.chars().count() <= 5));
assert!(turkish.nouns.iter().all(|word| word.chars().count() <= 5));
assert_eq!(turkish.adverbs.len(), 1); }
}