1#![warn(clippy::pedantic)]
2
3mod rng_joiner;
4mod rng_syllable;
5mod rng_syllables;
6mod rng_weighted_rnd;
7
8#[macro_use]
9extern crate bitflags;
10extern crate log;
11
12#[derive(Debug, Eq, PartialEq)]
13pub enum RNGError {
14 GenerationError,
15 InvalidLanguageFile,
16 ParsingError,
17 ReadError,
18}
19
20use anyhow::Result;
21use rand::{
22 distributions::{Distribution, Standard},
23 prelude::*,
24};
25use rust_embed::RustEmbed;
26use std::fmt;
27use std::str::FromStr;
28use titlecase::titlecase;
29
30use crate::rng_syllable::{Classification, Syllable};
31use crate::rng_syllables::Syllables;
32use crate::rng_weighted_rnd::{NORMAL_WEIGHT, SHORT_WEIGHT};
33
34#[allow(clippy::upper_case_acronyms)]
49#[derive(Clone, Debug, PartialEq)]
50pub struct RNG {
51 pub name: String,
52 pub prefixes: Syllables,
53 pub centers: Syllables,
54 pub suffixes: Syllables,
55 pub bad_syllables: Vec<String>,
56}
57
58impl RNG {
59 pub fn new(language: &Language) -> Result<RNG, RNG> {
66 let rng = RNG::process(language);
67
68 if rng.is_valid() {
69 Ok(rng)
70 } else {
71 Err(rng)
72 }
73 }
74
75 pub fn new_from_file(filename: String) -> Result<RNG, RNGError> {
79 match std::fs::read_to_string(filename.clone()) {
80 Ok(f) => match std::str::from_utf8(f.as_ref()) {
81 Ok(s) => Ok(RNG::classify(s, filename)),
82 Err(_) => Err(RNGError::InvalidLanguageFile),
83 },
84 Err(_) => Err(RNGError::InvalidLanguageFile),
85 }
86 }
87
88 #[must_use]
89 pub fn random() -> RNG {
90 let my_dialect_type: Language = rand::random();
91 RNG::process(&my_dialect_type)
92 }
93
94 fn process(language: &Language) -> RNG {
95 let mut txt = Asset::get(language.get_filename().as_str()).unwrap();
96 RNG::classify(
97 std::str::from_utf8(txt.data.to_mut()).unwrap(),
98 language.to_string(),
99 )
100 }
101
102 fn classify(lines: &str, name: String) -> RNG {
103 let mut rng = RNG::empty(name);
104
105 for line in lines.lines() {
106 if let Ok(sy) = Syllable::from_str(line) {
107 match sy.classification {
108 Classification::Prefix => rng.prefixes.add(sy),
109 Classification::Center => rng.centers.add(sy),
110 Classification::Suffix => rng.suffixes.add(sy),
111 }
112 } else {
113 rng.bad_syllables.push(line.to_string());
114 }
115 }
116 rng
117 }
118
119 #[must_use]
120 pub fn empty(name: String) -> RNG {
121 RNG {
122 name,
123 prefixes: Syllables::new(),
124 centers: Syllables::new(),
125 suffixes: Syllables::new(),
126 bad_syllables: Vec::new(),
127 }
128 }
129
130 #[must_use]
131 pub fn is_empty(&self) -> bool {
132 self.name.is_empty()
133 && self.prefixes.is_empty()
134 && self.centers.is_empty()
135 && self.suffixes.is_empty()
136 && self.bad_syllables.is_empty()
137 }
138
139 #[must_use]
140 pub fn is_valid(&self) -> bool {
141 !self.name.is_empty()
142 && !self.prefixes.is_empty()
143 && !self.centers.is_empty()
144 && !self.suffixes.is_empty()
145 && self.bad_syllables.is_empty()
146 }
147
148 #[must_use]
149 pub fn generate_name(&self) -> String {
150 self.generate_name_by_count(NORMAL_WEIGHT.gen())
151 }
152
153 #[must_use]
156 pub fn generate_names(&self, number: usize, is_short: bool) -> Vec<String> {
157 let mut v: Vec<String> = Vec::new();
158
159 for _ in 0..number {
160 if is_short {
161 v.push(self.generate_short());
162 } else {
163 v.push(self.generate_name());
164 }
165 }
166
167 v
168 }
169
170 #[must_use]
171 pub fn generate_names_string(&self, n: usize, is_short: bool) -> String {
172 self.generate_names(n, is_short).join(" ")
173 }
174
175 #[must_use]
176 pub fn generate_short(&self) -> String {
177 self.generate_name_by_count(SHORT_WEIGHT.gen())
178 }
179
180 #[must_use]
181 pub fn generate_name_by_count(&self, count: u8) -> String {
182 let name = self.generate_syllables_by_count(count).collapse();
183 titlecase(name.as_str())
184 }
185
186 #[must_use]
187 pub fn generate_syllables(&self) -> Syllables {
188 self.generate_syllables_by_count(NORMAL_WEIGHT.gen())
189 }
190
191 #[must_use]
195 pub fn generate_syllables_by_count(&self, mut syllable_count: u8) -> Syllables {
196 let mut syllables = Syllables::new();
197 let mut last = self.prefixes.get_random().unwrap().clone();
198 syllables.add(last.clone());
199
200 while syllable_count > 2 {
201 let center_syllables = self.centers.filter_from(last.jnext);
202 last = center_syllables.get_random().unwrap().clone();
203 syllables.add(last.clone());
204 syllable_count -= 1;
205 }
206
207 let last_syllables = self.suffixes.filter_from(last.jnext);
208
209 syllables.add(last_syllables.get_random().unwrap().clone());
210
211 syllables
212 }
213
214 #[must_use]
215 pub fn syllables(&self) -> Syllables {
216 let v = [
217 self.prefixes.all().clone(),
218 self.centers.all().clone(),
219 self.suffixes.all().clone(),
220 ]
221 .concat();
222 Syllables::new_from_vector(v)
223 }
224}
225
226impl From<&Language> for RNG {
227 fn from(language: &Language) -> Self {
228 RNG::process(language)
229 }
230}
231
232#[derive(RustEmbed)]
233#[folder = "src/languages/"]
234struct Asset;
235
236#[cfg(test)]
237#[allow(non_snake_case)]
238mod lib_tests {
239 use super::*;
240 use proptest::prelude::*;
241
242 #[test]
243 fn try_from() {
244 let rng = RNG::try_from(&Language::Fantasy).unwrap();
245
246 assert_eq!(rng.name, Language::Fantasy.to_string());
247 assert!(rng.bad_syllables.len() < 1);
248 assert!(rng.prefixes.len() > 0);
249 assert!(rng.centers.len() > 0);
250 assert!(rng.suffixes.len() > 0);
251 }
252
253 #[test]
254 fn try_from__demonic() {
255 let rng = RNG::new(&Language::Demonic).unwrap();
256
257 assert!(rng.bad_syllables.len() < 1);
258 assert!(rng.prefixes.len() > 0);
259 assert!(rng.centers.len() > 0);
260 assert!(rng.suffixes.len() > 0);
261 }
262
263 #[test]
264 fn try_from__goblin() {
265 let result = RNG::try_from(&Language::Goblin).unwrap();
266
267 assert_eq!(result.name, Language::Goblin.to_string());
268 assert!(result.bad_syllables.len() < 1);
269 assert!(result.prefixes.len() > 0);
270 assert!(result.centers.len() > 0);
271 assert!(result.suffixes.len() > 0);
272 }
273
274 #[test]
275 fn try_from__roman() {
276 let result = RNG::try_from(&Language::Roman).unwrap();
277
278 assert_eq!(result.name, Language::Roman.to_string());
279 assert!(result.bad_syllables.len() < 1);
280 assert!(result.prefixes.len() > 0);
281 assert!(result.centers.len() > 0);
282 assert!(result.suffixes.len() > 0);
283 }
284
285 #[test]
286 fn try_from__fantasy_russian() {
287 let result = RNG::try_from(&Language::Фантазия).unwrap();
288
289 assert_eq!(result.name, Language::Фантазия.to_string());
290 assert!(result.bad_syllables.len() < 1);
291 assert!(result.prefixes.len() > 0);
292 assert!(result.centers.len() > 0);
293 assert!(result.suffixes.len() > 0);
294 }
295
296 #[test]
297 fn new_from_file() {
298 let filename = "src/languages/Test-micro.txt";
299
300 let rng = RNG::new_from_file(filename.to_string());
301 let result = rng.as_ref().unwrap();
302
303 assert!(!rng.is_err());
304 assert_eq!(result.name, filename.to_string());
305 assert_eq!(result.bad_syllables.len(), 0);
306 assert_eq!(result.prefixes.len(), 1);
307 assert_eq!(result.centers.len(), 1);
308 assert_eq!(result.suffixes.len(), 1);
309 }
310
311 #[test]
312 fn new_from_file__russian_goblin() {
313 let filename = "src/languages/Гоблин.txt";
314
315 let rng = RNG::new_from_file(filename.to_string());
316 let result = rng.as_ref().unwrap();
317
318 assert!(!rng.is_err());
319 assert_eq!(result.name, filename.to_string());
320 assert_eq!(result.bad_syllables.len(), 0);
321 assert_eq!(result.prefixes.len(), 19);
322 assert_eq!(result.centers.len(), 13);
323 assert_eq!(result.suffixes.len(), 16);
324 }
325
326 #[test]
327 fn new_from_file__with_error() {
328 let filename = "src/languages/none.txt";
329
330 let result = RNG::new_from_file(filename.to_string());
331
332 assert!(result.is_err());
333 }
334
335 #[test]
336 fn new_from_file__russian_fantasy() {
337 let filename = "src/languages/Фантазия.txt";
338
339 let rng = RNG::new_from_file(filename.to_string());
340 let result = rng.as_ref().unwrap();
341
342 assert!(!rng.is_err());
343 assert_eq!(result.name, filename.to_string());
344 assert_eq!(result.bad_syllables.len(), 0);
345 assert_eq!(result.prefixes.len(), 180);
346 assert_eq!(result.centers.len(), 157);
347 assert_eq!(result.suffixes.len(), 19);
348 }
349
350 #[test]
351 fn process_file__with_error() {
352 let filename = "src/languages/none.txt";
353
354 let result = RNG::new_from_file(filename.to_string());
355
356 assert!(result.is_err());
357 }
358
359 #[test]
360 fn classify() {
361 let raw = "-ваа +c\n-боо +c\n-гар\n-бар\n-дар\n-жар\n-вар\n-кра\n-гра\n-дра\n-зра\n-гоб\n-доб\n-роб\n-фоб\n-зоб\n-раг\n-наг\n-даг\nбра\nга\nда\nдо\nго\nзе\nша\nназ\nзуб\nзу\nна\nгор\nбу +c\n+быр\n+гыр\n+д";
362 let filename = "src/languages/goblinRU.txt".to_string();
363
364 let classified = RNG::classify(raw, filename.clone());
365
366 assert_eq!(classified.name, filename);
367 assert_eq!(classified.bad_syllables.len(), 0);
368 assert_eq!(classified.prefixes.len(), 19);
369 assert_eq!(classified.centers.len(), 13);
370 assert_eq!(classified.suffixes.len(), 3);
371 }
372
373 #[test]
374 fn classify__fantasy_russian() {
375 let raw = "-а +c\n-аб\n-ак\n-ац\n-ад\n-аф\n-ам\n-ан\n-ап\n-ар\n-ас\n-ат\n-ав\n-аз\n-аэль\n-аэл\n-ао\n-аэр\n-аш\n-арш +v";
376 let filename = "src/languages/goblinRU.txt".to_string();
377
378 let classified = RNG::classify(raw, filename.clone());
379
380 assert_eq!(classified.name, filename);
381 assert_eq!(classified.bad_syllables.len(), 0);
382 }
386
387 fn create_min() -> RNG {
388 RNG {
389 name: "Min".to_string(),
390 prefixes: Syllables::new_from_array(&["a"]),
391 centers: Syllables::new_from_array(&["b"]),
392 suffixes: Syllables::new_from_array(&["c"]),
393 bad_syllables: vec![],
394 }
395 }
396
397 #[test]
398 fn generate_name() {
399 let min = create_min();
400
401 let chain: Vec<String> = (1..10).map(|_| min.generate_name()).collect();
402
403 chain
404 .iter()
405 .for_each(|name| assert!(name.as_str().starts_with("A")));
406 chain
407 .iter()
408 .for_each(|name| assert!(name.as_str().ends_with("c")));
409 }
410
411 #[test]
412 fn generate_names() {
413 let rng = RNG::try_from(&Language::Roman).unwrap();
414
415 let names = rng.generate_names(5, true);
416
417 assert_eq!(names.len(), 5);
418 }
419
420 #[test]
421 fn generate_names_string() {
422 let rng = RNG::try_from(&Language::Demonic).unwrap();
423
424 let names = rng.generate_names_string(12, true);
425
426 assert_eq!(names.split_whitespace().count(), 12);
427 }
428
429 #[test]
430 fn generate_short() {
431 let min = create_min();
432
433 let chain: Vec<String> = (1..10).map(|_| min.generate_short()).collect();
434
435 chain
436 .iter()
437 .for_each(|name| assert!(name.as_str().starts_with("A")));
438 chain
439 .iter()
440 .for_each(|name| assert!(name.as_str().ends_with("c")));
441 }
442
443 #[test]
444 fn generate_name_by_count() {
445 let min = create_min();
446
447 let chain: Vec<String> = (1..10)
448 .map(|_| min.generate_name_by_count(NORMAL_WEIGHT.gen()))
449 .collect();
450
451 chain
452 .iter()
453 .for_each(|name| assert!(name.as_str().starts_with("A")));
454 chain
455 .iter()
456 .for_each(|name| assert!(name.as_str().ends_with("c")));
457 }
458
459 #[test]
460 fn generate_syllables() {
461 let rng = RNG::try_from(&Language::Elven).unwrap();
462 let non: Vec<u8> = vec![0, 1, 6, 7, 8];
463
464 let chain: Vec<Syllables> = (1..10).map(|_| rng.generate_syllables()).collect();
465
466 chain
467 .iter()
468 .for_each(|i| assert!(NORMAL_WEIGHT.counts.contains(&(i.len() as u8))));
469 chain
470 .iter()
471 .for_each(|i| assert!(!non.contains(&(i.len() as u8))));
472 }
473
474 #[test]
475 fn is_empty() {
476 assert!(RNG::empty("".to_string()).is_empty());
477 assert!(!create_min().is_empty());
478 }
479
480 #[test]
481 fn is_valid() {
482 let min = create_min();
483 assert!(min.is_valid())
484 }
485
486 #[test]
487 fn is_valid__not() {
488 let bad = RNG {
489 name: "bad".to_string(),
490 prefixes: Syllables::new(),
491 centers: Syllables::new(),
492 suffixes: Syllables::new(),
493 bad_syllables: vec!["#$@!".to_string()],
494 };
495 assert!(!bad.is_valid())
496 }
497
498 #[test]
499 fn generate_syllables_by_count__elven_two() {
500 general_generate_dialects_asserts(Language::Elven, 2);
501 }
502
503 #[test]
504 fn generate_syllables_by_count__elven_three() {
505 general_generate_dialects_asserts(Language::Elven, 3);
506 }
507
508 #[test]
509 fn generate_syllables_by_count__elven_four() {
510 general_generate_dialects_asserts(Language::Elven, 4);
511 }
512
513 #[test]
514 fn generate_syllables_by_count__elven_five() {
515 general_generate_dialects_asserts(Language::Elven, 5);
516 }
517
518 #[test]
519 fn generate_syllables_by_count__fantasy_two() {
520 general_generate_dialects_asserts(Language::Fantasy, 2);
521 }
522
523 #[test]
524 fn generate_syllables_by_count__fantasy_three() {
525 general_generate_dialects_asserts(Language::Fantasy, 3);
526 }
527
528 #[test]
529 fn generate_syllables_by_count__fantasy_four() {
530 general_generate_dialects_asserts(Language::Fantasy, 4);
531 }
532
533 #[test]
534 fn generate_syllables_by_count__fantasy_five() {
535 general_generate_dialects_asserts(Language::Fantasy, 5);
536 }
537
538 #[test]
539 fn generate_syllables_by_count__goblin_two() {
540 general_generate_dialects_asserts(Language::Goblin, 2);
541 }
542
543 #[test]
544 fn generate_syllables_by_count__goblin_three() {
545 general_generate_dialects_asserts(Language::Goblin, 3);
546 }
547
548 #[test]
549 fn generate_syllables_by_count__goblin_four() {
550 general_generate_dialects_asserts(Language::Goblin, 4);
551 }
552
553 #[test]
554 fn generate_syllables_by_count__goblin_five() {
555 general_generate_dialects_asserts(Language::Goblin, 5);
556 }
557
558 #[test]
559 fn generate_syllables_by_count__roman_two() {
560 general_generate_dialects_asserts(Language::Roman, 2);
561 }
562
563 #[test]
564 fn generate_syllables_by_count__roman_three() {
565 general_generate_dialects_asserts(Language::Roman, 3);
566 }
567
568 #[test]
569 fn generate_syllables_by_count__roman_four() {
570 general_generate_dialects_asserts(Language::Roman, 4);
571 }
572
573 #[test]
574 fn generate_syllables_by_count__roman_five() {
575 general_generate_dialects_asserts(Language::Roman, 5);
576 }
577
578 fn general_generate_dialects_asserts(language: Language, count: usize) {
580 for _ in 0..9 {
581 let rng = RNG::try_from(&language).unwrap();
582
583 let name = rng.generate_syllables_by_count(count as u8);
584
585 general_generate_syllables_asserts(&rng, &name);
586 assert_eq!(name.len(), count);
587 }
588 }
589
590 fn general_generate_syllables_asserts(rng: &RNG, syllables: &Syllables) {
591 assert!(rng.prefixes.contains(syllables.first().unwrap()));
592 assert!(!rng.centers.contains(syllables.first().unwrap()));
593 assert!(!rng.suffixes.contains(syllables.first().unwrap()));
594
595 let count = syllables.len();
596 let mut guard = 1;
597 while guard < count - 1 {
598 guard += 1;
599 assert!(!rng.prefixes.contains(syllables.get(guard - 1).unwrap()));
600 assert!(rng.centers.contains(syllables.get(guard - 1).unwrap()));
601 assert!(!rng.suffixes.contains(syllables.get(guard - 1).unwrap()));
602 }
603
604 assert!(!rng.prefixes.contains(syllables.last().unwrap()));
605 assert!(!rng.centers.contains(syllables.last().unwrap()));
606 assert!(rng.suffixes.contains(syllables.last().unwrap()));
607 }
608 proptest! {
611 #[test]
612 fn test_gen_rnd_syllable_count(_ in 0..100i32) {
613 let count = NORMAL_WEIGHT.gen();
614 assert!((count < 6) && (count > 1), "count of {} should be less than 6 and greater than 1", count);
615 }
616 }
617}
618
619#[derive(Clone, Debug, PartialEq)]
622pub enum Language {
623 Curse,
624 Demonic,
625 Elven,
626 Эльфийский,
627 Fantasy,
628 Фантазия,
629 Goblin,
630 Гоблин,
631 Roman,
632 Римский,
633}
634
635impl fmt::Display for Language {
636 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
637 write!(f, "{self:?}")
638 }
639}
640
641impl Distribution<Language> for Standard {
642 fn sample<R: Rng + ?Sized>(&self, rng: &mut R) -> Language {
643 match rng.gen_range(0..5) {
644 0 => Language::Demonic,
645 1 => Language::Elven,
646 2 => Language::Fantasy,
647 3 => Language::Goblin,
648 _ => Language::Roman,
649 }
650 }
651}
652
653impl Language {
654 #[must_use]
655 pub fn get_filename(&self) -> String {
656 format!("{self}.txt")
657 }
658
659 #[must_use]
660 pub fn get_path(&self) -> String {
661 format!("./src/languages/{}", self.get_filename())
662 }
663}
664
665#[derive(Debug, Clone, PartialEq)]
667pub struct BadLanguage;
668
669impl fmt::Display for BadLanguage {
670 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
671 write!(f, "Invalid Language")
672 }
673}
674
675#[cfg(test)]
676#[allow(non_snake_case)]
677mod test_language {
678 use super::*;
679
680 #[test]
681 fn to_filename() {
682 assert_eq!(String::from("Elven.txt"), Language::Elven.get_filename());
683 }
684
685 #[test]
686 fn to_string() {
687 assert_eq!(String::from("Elven"), Language::Elven.to_string());
688 }
689
690 #[test]
691 fn get_path() {
692 assert_eq!(
693 "./src/languages/Fantasy.txt".to_string(),
694 Language::Fantasy.get_path()
695 );
696 }
697}
698