#![no_std]
extern crate alloc;
#[cfg(feature = "macros")]
extern crate self as petname;
use alloc::{borrow::Cow, collections::BTreeMap, string::String, vec::Vec};
use rand::seq::{IndexedRandom, IteratorRandom};
#[allow(dead_code)]
#[cfg(all(feature = "default-rng", feature = "default-words"))]
pub fn petname(words: u8, separator: &str) -> Option<String> {
Petnames::default().namer(words, separator).iter(&mut rand::rng()).next()
}
pub type Words<'a> = Cow<'a, [&'a str]>;
#[cfg(feature = "macros")]
pub use petname_macros::petnames;
pub trait Generator {
fn generate_into(&self, buf: &mut String, rng: &mut dyn rand::Rng, words: u8, separator: &str);
}
pub struct Namer<'a, G: ?Sized> {
generator: &'a G,
words: u8,
separator: &'a str,
}
impl<'a, G: Generator + ?Sized> Namer<'a, G> {
pub fn generate_into(&self, buf: &mut String, rng: &mut dyn rand::Rng) {
self.generator.generate_into(buf, rng, self.words, self.separator);
}
pub fn iter<'b>(&'b self, rng: &'b mut dyn rand::Rng) -> impl Iterator<Item = String> + 'b {
core::iter::from_fn(move || {
let mut buf = String::new();
self.generate_into(&mut buf, rng);
(!buf.is_empty()).then_some(buf)
})
}
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct Petnames<'a> {
pub adjectives: Words<'a>,
pub adverbs: Words<'a>,
pub nouns: Words<'a>,
}
impl<'a> Petnames<'a> {
#[cfg(feature = "default-words")]
pub fn small() -> Self {
petnames!("words/small")
}
#[cfg(feature = "default-words")]
pub fn medium() -> Self {
petnames!("words/medium")
}
#[cfg(feature = "default-words")]
pub fn large() -> Self {
petnames!("words/large")
}
pub fn new(adjectives: &'a str, adverbs: &'a str, nouns: &'a str) -> Self {
Self {
adjectives: Cow::Owned(adjectives.split_whitespace().collect()),
adverbs: Cow::Owned(adverbs.split_whitespace().collect()),
nouns: Cow::Owned(nouns.split_whitespace().collect()),
}
}
pub fn retain<F>(&mut self, mut predicate: F)
where
F: FnMut(&str) -> bool,
{
self.adjectives.to_mut().retain(|word| predicate(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) {
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(word) = self.adjectives.choose(rng).copied() {
buf.push_str(word);
buf.push_str(separator);
}
}
List::Noun => {
if let Some(word) = self.nouns.choose(rng).copied() {
buf.push_str(word);
}
}
};
}
}
}
#[cfg(feature = "default-words")]
impl Default for Petnames<'_> {
fn default() -> Self {
Self::medium()
}
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct Alliterations<'a> {
groups: BTreeMap<char, Petnames<'a>>,
}
impl Alliterations<'_> {
pub fn retain<F>(&mut self, predicate: F)
where
F: FnMut(&char, &mut Petnames) -> bool,
{
self.groups.retain(predicate)
}
pub fn cardinality(&self, words: u8) -> u128 {
self.groups
.values()
.map(|petnames| petnames.cardinality(words))
.reduce(u128::saturating_add)
.unwrap_or(0u128)
}
pub fn namer<'b>(&'b self, words: u8, separator: &'b str) -> Namer<'b, Self> {
Namer { generator: self, words, separator }
}
}
impl<'a> From<Petnames<'a>> for Alliterations<'a> {
fn from(petnames: Petnames<'a>) -> Self {
let mut adjectives: BTreeMap<char, Vec<&str>> = group_words_by_first_letter(petnames.adjectives);
let mut adverbs: BTreeMap<char, Vec<&str>> = group_words_by_first_letter(petnames.adverbs);
let nouns: BTreeMap<char, Vec<&str>> = group_words_by_first_letter(petnames.nouns);
Alliterations {
groups: nouns.into_iter().fold(BTreeMap::default(), |mut acc, (first_letter, nouns)| {
acc.insert(
first_letter,
Petnames {
adjectives: adjectives.remove(&first_letter).unwrap_or_default().into(),
adverbs: adverbs.remove(&first_letter).unwrap_or_default().into(),
nouns: Cow::from(nouns),
},
);
acc
}),
}
}
}
impl<'a, GROUPS> From<GROUPS> for Alliterations<'a>
where
GROUPS: IntoIterator<Item = (char, Petnames<'a>)>,
{
fn from(groups: GROUPS) -> Self {
Self { groups: groups.into_iter().collect() }
}
}
fn group_words_by_first_letter(words: Words<'_>) -> BTreeMap<char, Vec<&str>> {
words.iter().fold(BTreeMap::default(), |mut acc, s| match s.chars().next() {
Some(first_letter) => {
acc.entry(first_letter).or_default().push(s);
acc
}
None => acc,
})
}
impl Generator for Alliterations<'_> {
fn generate_into(&self, buf: &mut String, rng: &mut dyn rand::Rng, words: u8, separator: &str) {
if let Some(group) = self.groups.values().choose(rng) {
group.generate_into(buf, rng, words, separator);
}
}
}
#[cfg(feature = "default-words")]
impl Default for Alliterations<'_> {
fn default() -> Self {
Petnames::default().into()
}
}
#[derive(Debug, PartialEq)]
enum List {
Adverb,
Adjective,
Noun,
}
#[derive(Debug, PartialEq)]
enum Lists {
Adverb(u8),
Adjective,
Noun,
Done,
}
impl Lists {
fn new(words: u8) -> Self {
match words {
0 => Self::Done,
1 => Self::Noun,
2 => Self::Adjective,
n => Self::Adverb(n - 3),
}
}
fn current(&self) -> Option<List> {
match self {
Self::Adjective => Some(List::Adjective),
Self::Adverb(_) => Some(List::Adverb),
Self::Noun => Some(List::Noun),
Self::Done => None,
}
}
fn advance(&mut self) {
*self = match self {
Self::Adverb(0) => Self::Adjective,
Self::Adverb(remaining) => Self::Adverb(*remaining - 1),
Self::Adjective => Self::Noun,
Self::Noun | Self::Done => Self::Done,
}
}
fn remaining(&self) -> usize {
match self {
Self::Adverb(n) => (n + 3) as usize,
Self::Adjective => 2,
Self::Noun => 1,
Self::Done => 0,
}
}
}
impl Iterator for Lists {
type Item = List;
fn next(&mut self) -> Option<Self::Item> {
let current = self.current();
self.advance();
current
}
fn size_hint(&self) -> (usize, Option<usize>) {
let remaining = self.remaining();
(remaining, Some(remaining))
}
}
#[cfg(test)]
mod tests {
#[test]
fn lists_sequences_adverbs_adjectives_then_names() {
let mut lists = super::Lists::new(4);
assert_eq!(super::Lists::Adverb(1), lists);
assert_eq!(Some(super::List::Adverb), lists.next());
assert_eq!(super::Lists::Adverb(0), lists);
assert_eq!(Some(super::List::Adverb), lists.next());
assert_eq!(super::Lists::Adjective, lists);
assert_eq!(Some(super::List::Adjective), lists.next());
assert_eq!(super::Lists::Noun, lists);
assert_eq!(Some(super::List::Noun), lists.next());
assert_eq!(super::Lists::Done, lists);
assert_eq!(None, lists.next());
}
#[test]
fn lists_size_hint() {
let mut lists = super::Lists::new(3);
assert_eq!((3, Some(3)), lists.size_hint());
assert!(lists.next().is_some());
assert_eq!((2, Some(2)), lists.size_hint());
assert!(lists.next().is_some());
assert_eq!((1, Some(1)), lists.size_hint());
assert!(lists.next().is_some());
assert_eq!((0, Some(0)), lists.size_hint());
assert_eq!(None, lists.next());
assert_eq!((0, Some(0)), lists.size_hint());
}
}