Skip to main content

nil_namegen/
lib.rs

1// Copyright (C) Call of Nil contributors
2// SPDX-License-Identifier: AGPL-3.0-only
3
4#![cfg_attr(docsrs, feature(doc_cfg))]
5#![doc(html_favicon_url = "https://nil.dev.br/favicon.png")]
6
7use itertools::Itertools;
8use rand::random_range;
9use rand::seq::IndexedRandom;
10use std::sync::Arc;
11use strum::VariantArray;
12
13const FEMALE: &str = include_str!("../data/female.csv");
14const MALE: &str = include_str!("../data/male.csv");
15const SURNAME: &str = include_str!("../data/surname.csv");
16
17pub fn generate(amount: usize) -> Vec<Arc<str>> {
18  let mut names = Vec::with_capacity(amount);
19  let mut female = lines(FEMALE);
20  let mut male = lines(MALE);
21  let mut surname = lines(SURNAME);
22
23  for _ in 0..=amount {
24    let gender = Gender::random();
25    let Some(first) = (match gender {
26      Gender::Female => take(&mut female),
27      Gender::Male => take(&mut male),
28    }) else {
29      break;
30    };
31
32    let Some(last) = take(&mut surname) else { break };
33    if first != last {
34      names.push(Arc::from(format!("{first} {last}")));
35    }
36  }
37
38  names.into_iter().unique().collect()
39}
40
41fn lines(file: &'static str) -> Vec<&'static str> {
42  file
43    .trim()
44    .lines()
45    .map(str::trim)
46    .filter(|line| !line.is_empty())
47    .unique()
48    .collect()
49}
50
51fn take(pool: &mut Vec<&'static str>) -> Option<&'static str> {
52  if pool.is_empty() {
53    None
54  } else {
55    let idx = random_range(0..pool.len());
56    Some(pool.swap_remove(idx))
57  }
58}
59
60#[derive(Clone, Copy, Debug, PartialEq, Eq, VariantArray)]
61enum Gender {
62  Female,
63  Male,
64}
65
66impl Gender {
67  fn random() -> Self {
68    Self::VARIANTS
69      .choose(&mut rand::rng())
70      .copied()
71      .expect("Self::VARIANTS should never be empty")
72  }
73}