1use rand::seq::SliceRandom;
7use rand::Rng;
8use serde::{Deserialize, Serialize};
9use std::collections::HashMap;
10
11#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize, Default)]
13#[serde(rename_all = "snake_case")]
14pub enum NameCulture {
15 #[default]
17 WesternUs,
18 German,
20 French,
22 Chinese,
24 Japanese,
26 Indian,
28 Hispanic,
30}
31
32impl NameCulture {
33 pub fn all() -> &'static [NameCulture] {
35 &[
36 NameCulture::WesternUs,
37 NameCulture::German,
38 NameCulture::French,
39 NameCulture::Chinese,
40 NameCulture::Japanese,
41 NameCulture::Indian,
42 NameCulture::Hispanic,
43 ]
44 }
45}
46
47#[derive(Debug, Clone, Serialize, Deserialize)]
49pub struct PersonName {
50 pub first_name: String,
52 pub last_name: String,
54 pub display_name: String,
56 pub culture: NameCulture,
58 pub is_male: bool,
60}
61
62impl PersonName {
63 pub fn to_user_id(&self, index: usize) -> String {
65 let first_initial = self.first_name.chars().next().unwrap_or('X');
66 let last_part: String = self
67 .last_name
68 .chars()
69 .filter(|c| c.is_ascii_alphabetic())
70 .take(5)
71 .collect();
72 format!("{}{}{:03}", first_initial, last_part.to_uppercase(), index)
73 }
74
75 pub fn to_email(&self, domain: &str) -> String {
77 let first = self
78 .first_name
79 .chars()
80 .filter(|c| c.is_ascii_alphabetic())
81 .collect::<String>()
82 .to_lowercase();
83 let last = self
84 .last_name
85 .chars()
86 .filter(|c| c.is_ascii_alphabetic())
87 .collect::<String>()
88 .to_lowercase();
89 format!("{}.{}@{}", first, last, domain)
90 }
91}
92
93#[derive(Debug, Clone)]
95pub struct NamePool {
96 pub first_names_male: Vec<&'static str>,
98 pub first_names_female: Vec<&'static str>,
100 pub last_names: Vec<&'static str>,
102 pub culture: NameCulture,
104}
105
106impl NamePool {
107 pub fn western_us() -> Self {
109 Self {
110 culture: NameCulture::WesternUs,
111 first_names_male: vec![
112 "James",
113 "John",
114 "Robert",
115 "Michael",
116 "William",
117 "David",
118 "Richard",
119 "Joseph",
120 "Thomas",
121 "Christopher",
122 "Charles",
123 "Daniel",
124 "Matthew",
125 "Anthony",
126 "Mark",
127 "Donald",
128 "Steven",
129 "Paul",
130 "Andrew",
131 "Joshua",
132 "Kenneth",
133 "Kevin",
134 "Brian",
135 "George",
136 "Timothy",
137 "Ronald",
138 "Edward",
139 "Jason",
140 "Jeffrey",
141 "Ryan",
142 "Jacob",
143 "Gary",
144 "Nicholas",
145 "Eric",
146 "Jonathan",
147 "Stephen",
148 "Larry",
149 "Justin",
150 "Scott",
151 "Brandon",
152 "Benjamin",
153 "Samuel",
154 "Raymond",
155 "Gregory",
156 "Frank",
157 "Alexander",
158 "Patrick",
159 "Jack",
160 "Dennis",
161 "Jerry",
162 "Tyler",
163 "Aaron",
164 "Jose",
165 "Adam",
166 "Nathan",
167 ],
168 first_names_female: vec![
169 "Mary",
170 "Patricia",
171 "Jennifer",
172 "Linda",
173 "Barbara",
174 "Elizabeth",
175 "Susan",
176 "Jessica",
177 "Sarah",
178 "Karen",
179 "Lisa",
180 "Nancy",
181 "Betty",
182 "Margaret",
183 "Sandra",
184 "Ashley",
185 "Kimberly",
186 "Emily",
187 "Donna",
188 "Michelle",
189 "Dorothy",
190 "Carol",
191 "Amanda",
192 "Melissa",
193 "Deborah",
194 "Stephanie",
195 "Rebecca",
196 "Sharon",
197 "Laura",
198 "Cynthia",
199 "Kathleen",
200 "Amy",
201 "Angela",
202 "Shirley",
203 "Anna",
204 "Brenda",
205 "Pamela",
206 "Emma",
207 "Nicole",
208 "Helen",
209 "Samantha",
210 "Katherine",
211 "Christine",
212 "Debra",
213 "Rachel",
214 "Carolyn",
215 "Janet",
216 "Catherine",
217 "Maria",
218 "Heather",
219 "Diane",
220 "Ruth",
221 "Julie",
222 ],
223 last_names: vec![
224 "Smith",
225 "Johnson",
226 "Williams",
227 "Brown",
228 "Jones",
229 "Garcia",
230 "Miller",
231 "Davis",
232 "Rodriguez",
233 "Martinez",
234 "Hernandez",
235 "Lopez",
236 "Gonzalez",
237 "Wilson",
238 "Anderson",
239 "Thomas",
240 "Taylor",
241 "Moore",
242 "Jackson",
243 "Martin",
244 "Lee",
245 "Perez",
246 "Thompson",
247 "White",
248 "Harris",
249 "Sanchez",
250 "Clark",
251 "Ramirez",
252 "Lewis",
253 "Robinson",
254 "Walker",
255 "Young",
256 "Allen",
257 "King",
258 "Wright",
259 "Scott",
260 "Torres",
261 "Nguyen",
262 "Hill",
263 "Flores",
264 "Green",
265 "Adams",
266 "Nelson",
267 "Baker",
268 "Hall",
269 "Rivera",
270 "Campbell",
271 "Mitchell",
272 "Carter",
273 "Roberts",
274 "Turner",
275 "Phillips",
276 "Evans",
277 "Parker",
278 "Edwards",
279 "Collins",
280 ],
281 }
282 }
283
284 pub fn german() -> Self {
286 Self {
287 culture: NameCulture::German,
288 first_names_male: vec![
289 "Hans",
290 "Klaus",
291 "Wolfgang",
292 "Dieter",
293 "Jürgen",
294 "Peter",
295 "Michael",
296 "Thomas",
297 "Andreas",
298 "Stefan",
299 "Markus",
300 "Christian",
301 "Martin",
302 "Frank",
303 "Bernd",
304 "Uwe",
305 "Ralf",
306 "Werner",
307 "Heinz",
308 "Helmut",
309 "Gerhard",
310 "Manfred",
311 "Horst",
312 "Karl",
313 "Heinrich",
314 "Friedrich",
315 "Wilhelm",
316 "Otto",
317 "Matthias",
318 "Tobias",
319 "Sebastian",
320 "Florian",
321 "Alexander",
322 "Maximilian",
323 "Felix",
324 "Lukas",
325 "Jonas",
326 "Leon",
327 "Paul",
328 "Philipp",
329 "Tim",
330 "Jan",
331 "Nico",
332 "Erik",
333 "Lars",
334 "Sven",
335 "Kai",
336 "Olaf",
337 "Rainer",
338 ],
339 first_names_female: vec![
340 "Anna",
341 "Maria",
342 "Elisabeth",
343 "Ursula",
344 "Helga",
345 "Monika",
346 "Petra",
347 "Sabine",
348 "Karin",
349 "Renate",
350 "Ingrid",
351 "Brigitte",
352 "Gisela",
353 "Erika",
354 "Christa",
355 "Andrea",
356 "Claudia",
357 "Susanne",
358 "Birgit",
359 "Heike",
360 "Martina",
361 "Nicole",
362 "Stefanie",
363 "Julia",
364 "Katharina",
365 "Christina",
366 "Sandra",
367 "Melanie",
368 "Daniela",
369 "Anja",
370 "Tanja",
371 "Simone",
372 "Silke",
373 "Nadine",
374 "Yvonne",
375 "Manuela",
376 "Sonja",
377 "Michaela",
378 "Angelika",
379 "Barbara",
380 "Gabriele",
381 "Beate",
382 "Doris",
383 "Eva",
384 "Franziska",
385 "Lena",
386 "Hannah",
387 "Sophie",
388 "Leonie",
389 "Laura",
390 ],
391 last_names: vec![
392 "Müller",
393 "Schmidt",
394 "Schneider",
395 "Fischer",
396 "Weber",
397 "Meyer",
398 "Wagner",
399 "Becker",
400 "Schulz",
401 "Hoffmann",
402 "Schäfer",
403 "Koch",
404 "Bauer",
405 "Richter",
406 "Klein",
407 "Wolf",
408 "Schröder",
409 "Neumann",
410 "Schwarz",
411 "Zimmermann",
412 "Braun",
413 "Krüger",
414 "Hofmann",
415 "Hartmann",
416 "Lange",
417 "Schmitt",
418 "Werner",
419 "Schmitz",
420 "Krause",
421 "Meier",
422 "Lehmann",
423 "Schmid",
424 "Schulze",
425 "Maier",
426 "Köhler",
427 "Herrmann",
428 "König",
429 "Walter",
430 "Mayer",
431 "Huber",
432 "Kaiser",
433 "Fuchs",
434 "Peters",
435 "Lang",
436 "Scholz",
437 "Möller",
438 "Weiß",
439 "Jung",
440 "Hahn",
441 "Schubert",
442 ],
443 }
444 }
445
446 pub fn french() -> Self {
448 Self {
449 culture: NameCulture::French,
450 first_names_male: vec![
451 "Jean",
452 "Pierre",
453 "Michel",
454 "André",
455 "Philippe",
456 "Jacques",
457 "Bernard",
458 "Alain",
459 "François",
460 "Robert",
461 "Marcel",
462 "René",
463 "Louis",
464 "Claude",
465 "Daniel",
466 "Yves",
467 "Christian",
468 "Patrick",
469 "Nicolas",
470 "Julien",
471 "Thomas",
472 "Antoine",
473 "Alexandre",
474 "Maxime",
475 "Lucas",
476 "Hugo",
477 "Théo",
478 "Mathieu",
479 "Guillaume",
480 "Laurent",
481 "Olivier",
482 "Christophe",
483 "Sébastien",
484 "Frédéric",
485 "Vincent",
486 "David",
487 "Eric",
488 "Pascal",
489 "Gilles",
490 "Thierry",
491 "Stéphane",
492 "Bruno",
493 "Dominique",
494 "Serge",
495 "Maurice",
496 "Henri",
497 "Paul",
498 "Charles",
499 "Emmanuel",
500 "Raphaël",
501 ],
502 first_names_female: vec![
503 "Marie",
504 "Jeanne",
505 "Françoise",
506 "Monique",
507 "Catherine",
508 "Nathalie",
509 "Isabelle",
510 "Sylvie",
511 "Anne",
512 "Martine",
513 "Nicole",
514 "Christine",
515 "Sophie",
516 "Valérie",
517 "Julie",
518 "Camille",
519 "Léa",
520 "Manon",
521 "Emma",
522 "Chloé",
523 "Inès",
524 "Sarah",
525 "Laura",
526 "Louise",
527 "Jade",
528 "Alice",
529 "Lola",
530 "Margot",
531 "Charlotte",
532 "Clara",
533 "Pauline",
534 "Marine",
535 "Aurélie",
536 "Céline",
537 "Sandrine",
538 "Virginie",
539 "Stéphanie",
540 "Élodie",
541 "Delphine",
542 "Laurence",
543 "Brigitte",
544 "Jacqueline",
545 "Simone",
546 "Denise",
547 "Madeleine",
548 "Thérèse",
549 "Hélène",
550 "Élise",
551 "Juliette",
552 "Marguerite",
553 ],
554 last_names: vec![
555 "Martin",
556 "Bernard",
557 "Dubois",
558 "Thomas",
559 "Robert",
560 "Richard",
561 "Petit",
562 "Durand",
563 "Leroy",
564 "Moreau",
565 "Simon",
566 "Laurent",
567 "Lefebvre",
568 "Michel",
569 "Garcia",
570 "David",
571 "Bertrand",
572 "Roux",
573 "Vincent",
574 "Fournier",
575 "Morel",
576 "Girard",
577 "André",
578 "Lefèvre",
579 "Mercier",
580 "Dupont",
581 "Lambert",
582 "Bonnet",
583 "François",
584 "Martinez",
585 "Legrand",
586 "Garnier",
587 "Faure",
588 "Rousseau",
589 "Blanc",
590 "Guérin",
591 "Muller",
592 "Henry",
593 "Roussel",
594 "Nicolas",
595 "Perrin",
596 "Morin",
597 "Mathieu",
598 "Clément",
599 "Gauthier",
600 "Dumont",
601 "Lopez",
602 "Fontaine",
603 "Chevalier",
604 "Robin",
605 ],
606 }
607 }
608
609 pub fn chinese() -> Self {
611 Self {
612 culture: NameCulture::Chinese,
613 first_names_male: vec![
614 "Wei", "Fang", "Lei", "Jun", "Jian", "Hao", "Chen", "Yang", "Ming", "Tao", "Long",
615 "Feng", "Bin", "Qiang", "Gang", "Hui", "Peng", "Xiang", "Bo", "Chao", "Dong",
616 "Liang", "Ning", "Kai", "Jie", "Yong", "Hai", "Lin", "Wen", "Zheng", "Hong", "Xin",
617 "Da", "Zhi", "Guang", "Cheng", "Yi", "Sheng", "Biao", "Ping", "Yun", "Song",
618 "Chang", "Kang", "Rui", "Nan", "Jia", "Xiao", "Yu", "Hua",
619 ],
620 first_names_female: vec![
621 "Fang", "Min", "Jing", "Li", "Yan", "Hong", "Juan", "Mei", "Ying", "Xia", "Hui",
622 "Lin", "Ling", "Ping", "Dan", "Yun", "Na", "Qian", "Xin", "Ya", "Wei", "Wen",
623 "Jie", "Qing", "Yu", "Hua", "Yue", "Xue", "Lan", "Zhen", "Rong", "Shu", "Fei",
624 "Lei", "Shan", "Ting", "Ni", "Ying", "Chen", "Huan", "Lu", "Ai", "Xiao", "Xiang",
625 "Yao", "Meng", "Qi", "Jun", "Bei", "Zhi",
626 ],
627 last_names: vec![
628 "Wang", "Li", "Zhang", "Liu", "Chen", "Yang", "Huang", "Zhao", "Wu", "Zhou", "Xu",
629 "Sun", "Ma", "Zhu", "Hu", "Guo", "He", "Lin", "Gao", "Luo", "Zheng", "Liang",
630 "Xie", "Tang", "Song", "Xu", "Han", "Deng", "Feng", "Cao", "Peng", "Xiao", "Cheng",
631 "Yuan", "Tian", "Dong", "Pan", "Cai", "Jiang", "Wei", "Yu", "Du", "Ye", "Shi",
632 "Lu", "Shen", "Su", "Jia", "Fan", "Jin",
633 ],
634 }
635 }
636
637 pub fn japanese() -> Self {
639 Self {
640 culture: NameCulture::Japanese,
641 first_names_male: vec![
642 "Hiroshi", "Takeshi", "Kenji", "Yuki", "Kazuki", "Ryota", "Daiki", "Shota", "Yuto",
643 "Kenta", "Haruto", "Sota", "Riku", "Yuma", "Kaito", "Ren", "Hayato", "Takumi",
644 "Kouki", "Ryuu", "Naoki", "Tsubasa", "Yuuki", "Akira", "Satoshi", "Makoto",
645 "Tetsuya", "Masaki", "Shin", "Kei", "Daisuke", "Shunsuke", "Tomoya", "Yusuke",
646 "Tatsuya", "Katsuki", "Shun", "Yamato", "Koji", "Hideo", "Takahiro", "Noboru",
647 "Shinji", "Osamu", "Minoru", "Hideki", "Jun", "Masaru", "Ken", "Ryo",
648 ],
649 first_names_female: vec![
650 "Yuki", "Sakura", "Hana", "Yui", "Mio", "Rin", "Aoi", "Mei", "Saki", "Miku",
651 "Nanami", "Ayaka", "Misaki", "Haruka", "Momoka", "Rina", "Yuna", "Hinata",
652 "Koharu", "Miyu", "Akari", "Hikari", "Kaede", "Natsuki", "Mai", "Ami", "Aya",
653 "Emi", "Kana", "Megumi", "Tomoko", "Yoko", "Keiko", "Naomi", "Mayumi", "Chika",
654 "Nana", "Risa", "Asuka", "Fumiko", "Kyoko", "Reiko", "Noriko", "Sachiko", "Mariko",
655 "Shiori", "Midori", "Kanako", "Minami", "Eriko",
656 ],
657 last_names: vec![
658 "Sato",
659 "Suzuki",
660 "Takahashi",
661 "Tanaka",
662 "Watanabe",
663 "Ito",
664 "Yamamoto",
665 "Nakamura",
666 "Kobayashi",
667 "Kato",
668 "Yoshida",
669 "Yamada",
670 "Sasaki",
671 "Yamaguchi",
672 "Matsumoto",
673 "Inoue",
674 "Kimura",
675 "Hayashi",
676 "Shimizu",
677 "Yamazaki",
678 "Mori",
679 "Abe",
680 "Ikeda",
681 "Hashimoto",
682 "Yamashita",
683 "Ishikawa",
684 "Nakajima",
685 "Maeda",
686 "Fujita",
687 "Ogawa",
688 "Goto",
689 "Okada",
690 "Hasegawa",
691 "Murakami",
692 "Kondo",
693 "Ishii",
694 "Saito",
695 "Sakamoto",
696 "Endo",
697 "Aoki",
698 "Fujii",
699 "Nishimura",
700 "Fukuda",
701 "Ota",
702 "Miura",
703 "Okamoto",
704 "Matsuda",
705 "Nakagawa",
706 "Fujiwara",
707 "Kawamura",
708 ],
709 }
710 }
711
712 pub fn indian() -> Self {
714 Self {
715 culture: NameCulture::Indian,
716 first_names_male: vec![
717 "Raj", "Amit", "Vikram", "Rahul", "Sanjay", "Arun", "Suresh", "Rajesh", "Deepak",
718 "Vijay", "Prakash", "Manoj", "Sunil", "Anil", "Ravi", "Ashok", "Ramesh", "Mukesh",
719 "Sandeep", "Ajay", "Naveen", "Pradeep", "Sachin", "Nitin", "Vinod", "Rakesh",
720 "Srinivas", "Ganesh", "Krishna", "Mohan", "Kiran", "Venkat", "Hari", "Shankar",
721 "Dinesh", "Mahesh", "Satish", "Girish", "Naresh", "Harish", "Pavan", "Arjun",
722 "Anand", "Vivek", "Rohit", "Gaurav", "Kunal", "Vishal", "Akhil", "Dev",
723 ],
724 first_names_female: vec![
725 "Priya",
726 "Anita",
727 "Sunita",
728 "Kavita",
729 "Neha",
730 "Pooja",
731 "Anjali",
732 "Deepa",
733 "Meena",
734 "Rekha",
735 "Lakshmi",
736 "Padma",
737 "Gita",
738 "Sita",
739 "Rani",
740 "Devi",
741 "Shalini",
742 "Nisha",
743 "Swati",
744 "Preeti",
745 "Divya",
746 "Shreya",
747 "Aishwarya",
748 "Ritu",
749 "Seema",
750 "Jyoti",
751 "Shweta",
752 "Pallavi",
753 "Rashmi",
754 "Smita",
755 "Varsha",
756 "Archana",
757 "Asha",
758 "Manisha",
759 "Usha",
760 "Vandana",
761 "Geeta",
762 "Rita",
763 "Maya",
764 "Radha",
765 "Sapna",
766 "Megha",
767 "Nikita",
768 "Tanvi",
769 "Aditi",
770 "Bhavna",
771 "Chitra",
772 "Komal",
773 "Madhuri",
774 "Parul",
775 ],
776 last_names: vec![
777 "Sharma",
778 "Patel",
779 "Singh",
780 "Kumar",
781 "Gupta",
782 "Joshi",
783 "Verma",
784 "Shah",
785 "Mehta",
786 "Reddy",
787 "Rao",
788 "Iyer",
789 "Nair",
790 "Menon",
791 "Pillai",
792 "Das",
793 "Bose",
794 "Sen",
795 "Chatterjee",
796 "Banerjee",
797 "Mukherjee",
798 "Ghosh",
799 "Roy",
800 "Dutta",
801 "Kapoor",
802 "Malhotra",
803 "Agarwal",
804 "Sinha",
805 "Thakur",
806 "Saxena",
807 "Mishra",
808 "Pandey",
809 "Trivedi",
810 "Desai",
811 "Modi",
812 "Kulkarni",
813 "Patil",
814 "Kaur",
815 "Chopra",
816 "Khanna",
817 "Bhatia",
818 "Choudhury",
819 "Rajan",
820 "Subramaniam",
821 "Venkatesh",
822 "Naidu",
823 "Hegde",
824 "Shukla",
825 "Prasad",
826 "Murthy",
827 ],
828 }
829 }
830
831 pub fn hispanic() -> Self {
833 Self {
834 culture: NameCulture::Hispanic,
835 first_names_male: vec![
836 "José",
837 "Juan",
838 "Carlos",
839 "Luis",
840 "Miguel",
841 "Francisco",
842 "Antonio",
843 "Manuel",
844 "Pedro",
845 "Javier",
846 "Fernando",
847 "Rafael",
848 "Alejandro",
849 "Ricardo",
850 "Eduardo",
851 "Roberto",
852 "Daniel",
853 "Pablo",
854 "Sergio",
855 "Jorge",
856 "Andrés",
857 "Raúl",
858 "Diego",
859 "Enrique",
860 "Óscar",
861 "Adrián",
862 "Víctor",
863 "Martín",
864 "Gabriel",
865 "Álvaro",
866 "Iván",
867 "Mario",
868 "César",
869 "Héctor",
870 "Alberto",
871 "Gustavo",
872 "Arturo",
873 "Ramón",
874 "Hugo",
875 "Salvador",
876 "Ernesto",
877 "Guillermo",
878 "Ignacio",
879 "Jaime",
880 "Felipe",
881 "Tomás",
882 "Santiago",
883 "Mateo",
884 "Sebastián",
885 "Nicolás",
886 ],
887 first_names_female: vec![
888 "María",
889 "Carmen",
890 "Ana",
891 "Laura",
892 "Rosa",
893 "Isabel",
894 "Elena",
895 "Patricia",
896 "Teresa",
897 "Lucia",
898 "Pilar",
899 "Dolores",
900 "Paula",
901 "Sara",
902 "Marta",
903 "Julia",
904 "Cristina",
905 "Claudia",
906 "Andrea",
907 "Mónica",
908 "Gloria",
909 "Beatriz",
910 "Alicia",
911 "Rocío",
912 "Victoria",
913 "Silvia",
914 "Eva",
915 "Raquel",
916 "Adriana",
917 "Lorena",
918 "Gabriela",
919 "Marina",
920 "Sandra",
921 "Verónica",
922 "Natalia",
923 "Carolina",
924 "Diana",
925 "Alejandra",
926 "Cecilia",
927 "Daniela",
928 "Sofía",
929 "Valentina",
930 "Camila",
931 "Isabella",
932 "Mariana",
933 "Fernanda",
934 "Paola",
935 "Liliana",
936 "Angela",
937 "Inés",
938 ],
939 last_names: vec![
940 "García",
941 "Rodríguez",
942 "Martínez",
943 "López",
944 "González",
945 "Hernández",
946 "Pérez",
947 "Sánchez",
948 "Ramírez",
949 "Torres",
950 "Flores",
951 "Rivera",
952 "Gómez",
953 "Díaz",
954 "Reyes",
955 "Cruz",
956 "Morales",
957 "Ortiz",
958 "Gutiérrez",
959 "Chávez",
960 "Ramos",
961 "Vargas",
962 "Castillo",
963 "Jiménez",
964 "Moreno",
965 "Romero",
966 "Herrera",
967 "Medina",
968 "Aguilar",
969 "Vega",
970 "Castro",
971 "Mendoza",
972 "Ruiz",
973 "Fernández",
974 "Álvarez",
975 "Muñoz",
976 "Rojas",
977 "Silva",
978 "Suárez",
979 "Delgado",
980 "Navarro",
981 "Santos",
982 "Molina",
983 "Espinoza",
984 "Guerrero",
985 "Cabrera",
986 "Campos",
987 "Cortés",
988 "Salazar",
989 "Luna",
990 ],
991 }
992 }
993
994 pub fn for_culture(culture: NameCulture) -> Self {
996 match culture {
997 NameCulture::WesternUs => Self::western_us(),
998 NameCulture::German => Self::german(),
999 NameCulture::French => Self::french(),
1000 NameCulture::Chinese => Self::chinese(),
1001 NameCulture::Japanese => Self::japanese(),
1002 NameCulture::Indian => Self::indian(),
1003 NameCulture::Hispanic => Self::hispanic(),
1004 }
1005 }
1006
1007 pub fn generate_name(&self, rng: &mut impl Rng) -> PersonName {
1009 let is_male = rng.gen_bool(0.5);
1010
1011 let first_name = if is_male {
1012 self.first_names_male
1013 .choose(rng)
1014 .expect("non-empty name list")
1015 } else {
1016 self.first_names_female
1017 .choose(rng)
1018 .expect("non-empty name list")
1019 };
1020
1021 let last_name = self.last_names.choose(rng).expect("non-empty name list");
1022
1023 PersonName {
1024 first_name: (*first_name).to_string(),
1025 last_name: (*last_name).to_string(),
1026 display_name: format!("{} {}", first_name, last_name),
1027 culture: self.culture,
1028 is_male,
1029 }
1030 }
1031}
1032
1033#[derive(Debug, Clone)]
1035pub struct MultiCultureNameGenerator {
1036 pools: HashMap<NameCulture, NamePool>,
1037 distribution: Vec<(NameCulture, f64)>,
1038 email_domain: String,
1039}
1040
1041impl Default for MultiCultureNameGenerator {
1042 fn default() -> Self {
1043 Self::new()
1044 }
1045}
1046
1047impl MultiCultureNameGenerator {
1048 pub fn new() -> Self {
1050 let mut pools = HashMap::new();
1051 for culture in NameCulture::all() {
1052 pools.insert(*culture, NamePool::for_culture(*culture));
1053 }
1054
1055 Self {
1056 pools,
1057 distribution: vec![
1058 (NameCulture::WesternUs, 0.40),
1059 (NameCulture::Hispanic, 0.20),
1060 (NameCulture::German, 0.10),
1061 (NameCulture::French, 0.05),
1062 (NameCulture::Chinese, 0.10),
1063 (NameCulture::Japanese, 0.05),
1064 (NameCulture::Indian, 0.10),
1065 ],
1066 email_domain: "company.com".to_string(),
1067 }
1068 }
1069
1070 pub fn with_distribution(distribution: Vec<(NameCulture, f64)>) -> Self {
1072 let mut gen = Self::new();
1073 gen.distribution = distribution;
1074 gen
1075 }
1076
1077 pub fn with_email_domain(mut self, domain: &str) -> Self {
1079 self.email_domain = domain.to_string();
1080 self
1081 }
1082
1083 pub fn set_email_domain(&mut self, domain: &str) {
1085 self.email_domain = domain.to_string();
1086 }
1087
1088 pub fn email_domain(&self) -> &str {
1090 &self.email_domain
1091 }
1092
1093 fn select_culture(&self, rng: &mut impl Rng) -> NameCulture {
1095 let roll: f64 = rng.gen();
1096 let mut cumulative = 0.0;
1097
1098 for (culture, weight) in &self.distribution {
1099 cumulative += weight;
1100 if roll < cumulative {
1101 return *culture;
1102 }
1103 }
1104
1105 NameCulture::WesternUs
1107 }
1108
1109 pub fn generate_name(&self, rng: &mut impl Rng) -> PersonName {
1111 let culture = self.select_culture(rng);
1112 self.pools
1113 .get(&culture)
1114 .map(|pool| pool.generate_name(rng))
1115 .unwrap_or_else(|| NamePool::western_us().generate_name(rng))
1116 }
1117
1118 pub fn generate_name_for_culture(
1120 &self,
1121 culture: NameCulture,
1122 rng: &mut impl Rng,
1123 ) -> PersonName {
1124 self.pools
1125 .get(&culture)
1126 .map(|pool| pool.generate_name(rng))
1127 .unwrap_or_else(|| NamePool::western_us().generate_name(rng))
1128 }
1129
1130 pub fn generate_user_id(&self, name: &PersonName, index: usize) -> String {
1132 name.to_user_id(index)
1133 }
1134
1135 pub fn generate_email(&self, name: &PersonName) -> String {
1137 name.to_email(&self.email_domain)
1138 }
1139}
1140
1141#[cfg(test)]
1142#[allow(clippy::unwrap_used)]
1143mod tests {
1144 use super::*;
1145 use rand::SeedableRng;
1146 use rand_chacha::ChaCha8Rng;
1147
1148 #[test]
1149 fn test_name_pool_generation() {
1150 let mut rng = ChaCha8Rng::seed_from_u64(42);
1151 let pool = NamePool::western_us();
1152 let name = pool.generate_name(&mut rng);
1153
1154 assert!(!name.first_name.is_empty());
1155 assert!(!name.last_name.is_empty());
1156 assert_eq!(name.culture, NameCulture::WesternUs);
1157 }
1158
1159 #[test]
1160 fn test_multi_culture_generator() {
1161 let mut rng = ChaCha8Rng::seed_from_u64(42);
1162 let generator = MultiCultureNameGenerator::new();
1163
1164 let mut culture_counts: HashMap<NameCulture, usize> = HashMap::new();
1166 for _ in 0..100 {
1167 let name = generator.generate_name(&mut rng);
1168 *culture_counts.entry(name.culture).or_insert(0) += 1;
1169 }
1170
1171 assert!(culture_counts.len() > 1);
1173 }
1174
1175 #[test]
1176 fn test_user_id_generation() {
1177 let name = PersonName {
1178 first_name: "John".to_string(),
1179 last_name: "Smith".to_string(),
1180 display_name: "John Smith".to_string(),
1181 culture: NameCulture::WesternUs,
1182 is_male: true,
1183 };
1184
1185 let user_id = name.to_user_id(42);
1186 assert_eq!(user_id, "JSMITH042");
1187 }
1188
1189 #[test]
1190 fn test_email_generation() {
1191 let name = PersonName {
1192 first_name: "María".to_string(),
1193 last_name: "García".to_string(),
1194 display_name: "María García".to_string(),
1195 culture: NameCulture::Hispanic,
1196 is_male: false,
1197 };
1198
1199 let email = name.to_email("acme.com");
1200 assert_eq!(email, "mara.garca@acme.com"); }
1202
1203 #[test]
1204 fn test_all_culture_pools() {
1205 let mut rng = ChaCha8Rng::seed_from_u64(42);
1206
1207 for culture in NameCulture::all() {
1208 let pool = NamePool::for_culture(*culture);
1209 let name = pool.generate_name(&mut rng);
1210 assert!(!name.first_name.is_empty());
1211 assert!(!name.last_name.is_empty());
1212 assert_eq!(name.culture, *culture);
1213 }
1214 }
1215}