Skip to main content

xpg/
lib.rs

1/*!
2xkcd-style password generator
3*/
4
5use {
6    anyhow::{Result, anyhow},
7    rand::prelude::*,
8    serde::{Deserialize, Serialize},
9    std::{
10        collections::{BTreeMap, BTreeSet, HashMap},
11        fmt::Write,
12        fs::File,
13        io::BufReader,
14        path::Path,
15    },
16    ucfirst::ucfirst,
17};
18
19//--------------------------------------------------------------------------------------------------
20
21pub const CONFIG: &str = include_str!("../config.json");
22
23//--------------------------------------------------------------------------------------------------
24
25#[derive(Clone, Copy, Debug, Deserialize, Eq, Hash, Ord, PartialEq, PartialOrd, Serialize)]
26pub enum WordKind {
27    Adjective,
28    AdjectiveSyllables(usize),
29    Adverb,
30    AdverbSyllables(usize),
31    All,
32    AllSyllables(usize),
33    AllExtended,
34    AllExtendedSyllables(usize),
35    Astronomy,
36    AstronomySyllables(usize),
37    AuxiliaryVerb,
38    AuxiliaryVerbSyllables(usize),
39    Chthonic,
40    ChthonicSyllables(usize),
41    City,
42    CitySyllables(usize),
43    Color,
44    ColorSyllables(usize),
45    Conjunction,
46    ConjunctionSyllables(usize),
47    Continent,
48    ContinentSyllables(usize),
49    Country,
50    CountrySyllables(usize),
51    Day,
52    DaySyllables(usize),
53    Element,
54    ElementSyllables(usize),
55    Extended,
56    ExtendedSyllables(usize),
57    FemaleName,
58    FemaleNameSyllables(usize),
59    GreekMyth,
60    GreekMythSyllables(usize),
61    Interjection,
62    InterjectionSyllables(usize),
63    IntransitiveVerb,
64    IntransitiveVerbSyllables(usize),
65    JupiterMoon,
66    JupiterMoonSyllables(usize),
67    MaleName,
68    MaleNameSyllables(usize),
69    MarsMoon,
70    MarsMoonSyllables(usize),
71    Month,
72    MonthSyllables(usize),
73    Moon,
74    MoonSyllables(usize),
75    Mythology,
76    MythologySyllables(usize),
77    Name,
78    NameSyllables(usize),
79    Nationality,
80    NationalitySyllables(usize),
81    NeptuneMoon,
82    NeptuneMoonSyllables(usize),
83    Noun,
84    NounSyllables(usize),
85    Olympian,
86    OlympianSyllables(usize),
87    Place,
88    PlaceSyllables(usize),
89    Planet,
90    PlanetSyllables(usize),
91    PluralNoun,
92    PluralNounSyllables(usize),
93    Preposition,
94    PrepositionSyllables(usize),
95    Pronoun,
96    PronounSyllables(usize),
97    ProperNoun,
98    ProperNounSyllables(usize),
99    RomanMyth,
100    RomanMythSyllables(usize),
101    SaturnMoon,
102    SaturnMoonSyllables(usize),
103    SingularNoun,
104    SingularNounSyllables(usize),
105    TransitiveVerb,
106    TransitiveVerbSyllables(usize),
107    UranusMoon,
108    UranusMoonSyllables(usize),
109    UsState,
110    UsStateSyllables(usize),
111    Verb,
112    VerbSyllables(usize),
113    VerbPast,
114    VerbPastSyllables(usize),
115}
116
117use WordKind::{
118    Adjective, AdjectiveSyllables, Adverb, AdverbSyllables, All, AllExtended, AllExtendedSyllables,
119    AllSyllables, Astronomy, AstronomySyllables, AuxiliaryVerb, AuxiliaryVerbSyllables, Chthonic,
120    ChthonicSyllables, City, CitySyllables, Color, ColorSyllables, Conjunction,
121    ConjunctionSyllables, Continent, ContinentSyllables, Country, CountrySyllables, Day,
122    DaySyllables, Element, ElementSyllables, Extended, ExtendedSyllables, FemaleName,
123    FemaleNameSyllables, GreekMyth, GreekMythSyllables, Interjection, InterjectionSyllables,
124    IntransitiveVerb, IntransitiveVerbSyllables, JupiterMoon, JupiterMoonSyllables, MaleName,
125    MaleNameSyllables, MarsMoon, MarsMoonSyllables, Month, MonthSyllables, Moon, MoonSyllables,
126    Mythology, MythologySyllables, Name, NameSyllables, Nationality, NationalitySyllables,
127    NeptuneMoon, NeptuneMoonSyllables, Noun, NounSyllables, Olympian, OlympianSyllables, Place,
128    PlaceSyllables, Planet, PlanetSyllables, PluralNoun, PluralNounSyllables, Preposition,
129    PrepositionSyllables, Pronoun, PronounSyllables, ProperNoun, ProperNounSyllables, RomanMyth,
130    RomanMythSyllables, SaturnMoon, SaturnMoonSyllables, SingularNoun, SingularNounSyllables,
131    TransitiveVerb, TransitiveVerbSyllables, UranusMoon, UranusMoonSyllables, UsState,
132    UsStateSyllables, Verb, VerbPast, VerbPastSyllables, VerbSyllables,
133};
134
135const WORD_KINDS_EXTENDED: [WordKind; 9] = [
136    AllExtended,
137    Chthonic,
138    Extended,
139    JupiterMoon,
140    MarsMoon,
141    NeptuneMoon,
142    Olympian,
143    SaturnMoon,
144    UranusMoon,
145];
146
147impl WordKind {
148    fn enumerate(&self, extended: bool) -> Vec<WordKind> {
149        let mut r = vec![*self];
150        match self {
151            Astronomy | Day => r.append(&mut vec![SingularNoun, Noun]),
152            AuxiliaryVerb | IntransitiveVerb | TransitiveVerb | VerbPast => r.push(Verb),
153            Chthonic | Olympian => {
154                r.append(&mut vec![GreekMyth, Mythology, ProperNoun, Noun, Extended]);
155            }
156            City | Country | UsState => r.append(&mut vec![Place, ProperNoun]),
157            Color => r.append(&mut vec![SingularNoun, Noun, Verb]),
158            Continent => r.append(&mut vec![Place, ProperNoun, SingularNoun, Noun]),
159            Element | Mythology => r.append(&mut vec![ProperNoun, Noun]),
160            FemaleName | MaleName => r.append(&mut vec![Name, ProperNoun]),
161            GreekMyth | RomanMyth => r.append(&mut vec![Mythology, ProperNoun, Noun]),
162            JupiterMoon | MarsMoon | NeptuneMoon | SaturnMoon | UranusMoon => {
163                r.append(&mut vec![Moon, Astronomy, SingularNoun, Noun, Extended]);
164            }
165            Month => r.append(&mut vec![SingularNoun, Noun, ProperNoun]),
166            Moon | Planet => r.append(&mut vec![Astronomy, SingularNoun, Noun]),
167            Name => r.push(ProperNoun),
168            Nationality => r.push(Adjective),
169            PluralNoun | SingularNoun => r.push(Noun),
170            _ => {}
171        }
172        if extended || !r.contains(&Extended) {
173            r.push(All);
174        }
175        r.push(AllExtended);
176        r
177    }
178
179    fn sub(&self) -> String {
180        match self {
181            Adjective => String::from("{adj}"),
182            Adverb => String::from("{adv}"),
183            All => String::from("{a}"),
184            AllExtended => String::from("{a.ext}"),
185            Astronomy => String::from("{ast}"),
186            AuxiliaryVerb => String::from("{v.aux}"),
187            Chthonic => String::from("{chthonic}"),
188            City => String::from("{city}"),
189            Color => String::from("{color}"),
190            Conjunction => String::from("{conj}"),
191            Continent => String::from("{cont}"),
192            Country => String::from("{country}"),
193            Day => String::from("{day}"),
194            Element => String::from("{el}"),
195            Extended => String::from("{ext}"),
196            FemaleName => String::from("{fname}"),
197            GreekMyth => String::from("{greekmyth}"),
198            Interjection => String::from("{i}"),
199            IntransitiveVerb => String::from("{v.int}"),
200            JupiterMoon => String::from("{jupitermoon}"),
201            MaleName => String::from("{mname}"),
202            MarsMoon => String::from("{marsmoon}"),
203            Month => String::from("{mon}"),
204            Moon => String::from("{moon}"),
205            Mythology => String::from("{myth}"),
206            Name => String::from("{name}"),
207            Nationality => String::from("{nat}"),
208            NeptuneMoon => String::from("{neptunemoon}"),
209            Noun => String::from("{n}"),
210            Olympian => String::from("{olympian}"),
211            Place => String::from("{place}"),
212            Planet => String::from("{planet}"),
213            PluralNoun => String::from("{n.pl}"),
214            Preposition => String::from("{prep}"),
215            Pronoun => String::from("{n.pro}"),
216            ProperNoun => String::from("{n.prop}"),
217            RomanMyth => String::from("{romanmyth}"),
218            SaturnMoon => String::from("{saturnmoon}"),
219            SingularNoun => String::from("{n.s}"),
220            TransitiveVerb => String::from("{v.tr}"),
221            UranusMoon => String::from("{uranusmoon}"),
222            UsState => String::from("{us-state}"),
223            Verb => String::from("{v}"),
224            VerbPast => String::from("{v.past}"),
225            AdjectiveSyllables(n) => format!("{{adj:{n}}}"),
226            AdverbSyllables(n) => format!("{{adv:{n}}}"),
227            AllSyllables(n) => format!("{{a:{n}}}"),
228            AllExtendedSyllables(n) => format!("{{a.ext:{n}}}"),
229            AstronomySyllables(n) => format!("{{ast:{n}}}"),
230            AuxiliaryVerbSyllables(n) => format!("{{v.aux:{n}}}"),
231            ChthonicSyllables(n) => format!("{{chthonic:{n}}}"),
232            CitySyllables(n) => format!("{{city:{n}}}"),
233            ColorSyllables(n) => format!("{{color:{n}}}"),
234            ConjunctionSyllables(n) => format!("{{conj:{n}}}"),
235            ContinentSyllables(n) => format!("{{cont:{n}}}"),
236            CountrySyllables(n) => format!("{{country:{n}}}"),
237            DaySyllables(n) => format!("{{day:{n}}}"),
238            ElementSyllables(n) => format!("{{el:{n}}}"),
239            ExtendedSyllables(n) => format!("{{ext:{n}}}"),
240            FemaleNameSyllables(n) => format!("{{fname:{n}}}"),
241            GreekMythSyllables(n) => format!("{{greekmyth:{n}}}"),
242            InterjectionSyllables(n) => format!("{{i:{n}}}"),
243            IntransitiveVerbSyllables(n) => format!("{{v.int:{n}}}"),
244            JupiterMoonSyllables(n) => format!("{{jupitermoon:{n}}}"),
245            MaleNameSyllables(n) => format!("{{mname:{n}}}"),
246            MarsMoonSyllables(n) => format!("{{marsmoon:{n}}}"),
247            MonthSyllables(n) => format!("{{mon:{n}}}"),
248            MoonSyllables(n) => format!("{{moon:{n}}}"),
249            MythologySyllables(n) => format!("{{myth:{n}}}"),
250            NameSyllables(n) => format!("{{name:{n}}}"),
251            NationalitySyllables(n) => format!("{{nat:{n}}}"),
252            NeptuneMoonSyllables(n) => format!("{{neptunemoon:{n}}}"),
253            NounSyllables(n) => format!("{{n:{n}}}"),
254            OlympianSyllables(n) => format!("{{olympian:{n}}}"),
255            PlaceSyllables(n) => format!("{{place:{n}}}"),
256            PlanetSyllables(n) => format!("{{planet:{n}}}"),
257            PluralNounSyllables(n) => format!("{{n.pl:{n}}}"),
258            PrepositionSyllables(n) => format!("{{prep:{n}}}"),
259            PronounSyllables(n) => format!("{{n.pro:{n}}}"),
260            ProperNounSyllables(n) => format!("{{n.prop:{n}}}"),
261            RomanMythSyllables(n) => format!("{{romanmyth:{n}}}"),
262            SaturnMoonSyllables(n) => format!("{{saturnmoon:{n}}}"),
263            SingularNounSyllables(n) => format!("{{n.s:{n}}}"),
264            TransitiveVerbSyllables(n) => format!("{{v.tr:{n}}}"),
265            UranusMoonSyllables(n) => format!("{{uranusmoon:{n}}}"),
266            UsStateSyllables(n) => format!("{{us-state:{n}}}"),
267            VerbSyllables(n) => format!("{{v:{n}}}"),
268            VerbPastSyllables(n) => format!("{{v.past:{n}}}"),
269        }
270    }
271
272    fn with_syllables(&self, syllables: usize) -> WordKind {
273        match self {
274            Adjective => AdjectiveSyllables(syllables),
275            Adverb => AdverbSyllables(syllables),
276            All => AllSyllables(syllables),
277            AllExtended => AllExtendedSyllables(syllables),
278            Astronomy => AstronomySyllables(syllables),
279            AuxiliaryVerb => AuxiliaryVerbSyllables(syllables),
280            Chthonic => ChthonicSyllables(syllables),
281            City => CitySyllables(syllables),
282            Color => ColorSyllables(syllables),
283            Conjunction => ConjunctionSyllables(syllables),
284            Continent => ContinentSyllables(syllables),
285            Country => CountrySyllables(syllables),
286            Day => DaySyllables(syllables),
287            Element => ElementSyllables(syllables),
288            Extended => ExtendedSyllables(syllables),
289            FemaleName => FemaleNameSyllables(syllables),
290            GreekMyth => GreekMythSyllables(syllables),
291            Interjection => InterjectionSyllables(syllables),
292            IntransitiveVerb => IntransitiveVerbSyllables(syllables),
293            JupiterMoon => JupiterMoonSyllables(syllables),
294            MaleName => MaleNameSyllables(syllables),
295            MarsMoon => MarsMoonSyllables(syllables),
296            Month => MonthSyllables(syllables),
297            Moon => MoonSyllables(syllables),
298            Mythology => MythologySyllables(syllables),
299            Name => NameSyllables(syllables),
300            Nationality => NationalitySyllables(syllables),
301            NeptuneMoon => NeptuneMoonSyllables(syllables),
302            Noun => NounSyllables(syllables),
303            Olympian => OlympianSyllables(syllables),
304            Place => PlaceSyllables(syllables),
305            Planet => PlanetSyllables(syllables),
306            PluralNoun => PluralNounSyllables(syllables),
307            Preposition => PrepositionSyllables(syllables),
308            Pronoun => PronounSyllables(syllables),
309            ProperNoun => ProperNounSyllables(syllables),
310            RomanMyth => RomanMythSyllables(syllables),
311            SaturnMoon => SaturnMoonSyllables(syllables),
312            SingularNoun => SingularNounSyllables(syllables),
313            TransitiveVerb => TransitiveVerbSyllables(syllables),
314            UranusMoon => UranusMoonSyllables(syllables),
315            UsState => UsStateSyllables(syllables),
316            Verb => VerbSyllables(syllables),
317            VerbPast => VerbPastSyllables(syllables),
318            _ => *self,
319        }
320    }
321}
322
323//--------------------------------------------------------------------------------------------------
324
325#[derive(Clone, Deserialize, Serialize)]
326pub struct WordDetails {
327    kinds: Vec<WordKind>,
328    syllables: usize,
329}
330
331//--------------------------------------------------------------------------------------------------
332
333#[derive(Clone, Deserialize, Serialize)]
334pub struct Config {
335    characters: BTreeMap<char, String>,
336    words: BTreeMap<String, WordDetails>,
337
338    #[serde(skip)]
339    words_built: BTreeMap<String, WordDetails>,
340
341    #[serde(skip)]
342    kinds: BTreeMap<WordKind, Vec<String>>,
343
344    #[serde(skip)]
345    alphabets: BTreeMap<char, Vec<char>>,
346
347    #[serde(skip)]
348    pub subs: BTreeMap<String, WordKind>,
349
350    #[serde(skip)]
351    pub extended: bool,
352}
353
354impl Config {
355    /**
356    # Errors
357
358    Returns an error if not able to open and read the file at the given path
359    */
360    pub fn from_path(path: &Path, extended: bool) -> Result<Config> {
361        let r: Config = serde_json::from_reader(BufReader::new(File::open(path)?))?;
362        Ok(r.build(extended))
363    }
364
365    /**
366    # Errors
367
368    Returns an error if not able to deserialize the given JSON `&str` as a [`Config`]
369    */
370    pub fn from_str(s: &str, extended: bool) -> Result<Config> {
371        Ok(serde_json::from_str::<Config>(s)?.build(extended))
372    }
373
374    /**
375    # Errors
376
377    Returns an error if not able to serialize to a pretty JSON string
378    */
379    pub fn dump(&self) -> Result<String> {
380        Ok(serde_json::to_string_pretty(self)?)
381    }
382
383    fn build(mut self, extended: bool) -> Config {
384        // Build word lists for each kind and syllable count
385        let mut kinds: HashMap<WordKind, BTreeSet<String>> = HashMap::new();
386        self.words_built = BTreeMap::new();
387        for (word, word_details) in &self.words {
388            let word_kinds = word_details
389                .kinds
390                .iter()
391                .flat_map(|x| x.enumerate(extended))
392                .collect::<Vec<_>>();
393            let word_is_extended = word_kinds.contains(&Extended);
394            for kind in &word_kinds {
395                let kind_is_extended = WORD_KINDS_EXTENDED.contains(kind);
396                if extended || !word_is_extended || kind_is_extended {
397                    let s = kinds.entry(*kind).or_default();
398                    s.insert(word.clone());
399                    let s = kinds
400                        .entry(kind.with_syllables(word_details.syllables))
401                        .or_default();
402                    s.insert(word.clone());
403                }
404            }
405            self.words_built.insert(
406                word.clone(),
407                WordDetails {
408                    kinds: word_kinds,
409                    syllables: word_details.syllables,
410                },
411            );
412        }
413        self.kinds = kinds
414            .into_iter()
415            .map(|(k, v)| (k, v.into_iter().collect()))
416            .collect();
417        self.subs = self
418            .kinds
419            .keys()
420            .flat_map(|x| {
421                let sub = x.sub();
422                [
423                    (sub.clone(), *x),
424                    (format!("{{W:{}", &sub[1..]), *x),
425                    (format!("{{T:{}", &sub[1..]), *x),
426                ]
427            })
428            .collect();
429
430        // Build alphabets
431        for (c, s) in &self.characters {
432            self.alphabets.insert(*c, s.chars().collect());
433        }
434
435        self.extended = extended;
436
437        self
438    }
439
440    /**
441    # Errors
442
443    Returns an error if not able to resolve the given sub `&str`
444    */
445    pub fn list(&self, sub: &str) -> Result<Vec<String>> {
446        if let Some(kind) = self.subs.get(sub) {
447            if let Some(list) = self.kinds.get(kind) {
448                Ok(if sub.starts_with("{W:") {
449                    list.iter().map(|x| x.to_uppercase()).collect()
450                } else if sub.starts_with("{T:") {
451                    list.iter().map(|x| ucfirst(x)).collect()
452                } else {
453                    list.clone()
454                })
455            } else {
456                Err(anyhow!("Unknown"))
457            }
458        } else {
459            Err(anyhow!("Invalid sub: `{sub}`!"))
460        }
461    }
462
463    fn get_n(&self, n: usize, kind: WordKind) -> Vec<String> {
464        self.kinds
465            .get(&kind)
466            .unwrap()
467            .sample(&mut rand::rng(), n)
468            .cloned()
469            .collect()
470    }
471
472    fn get(&self, kind: WordKind) -> String {
473        self.kinds
474            .get(&kind)
475            .unwrap()
476            .choose(&mut rand::rng())
477            .unwrap()
478            .clone()
479    }
480
481    /// Generate a keychain word type 1 (5 lowercase letters, 1 uppercase letter)
482    fn keychain_word_1(&self) -> String {
483        shuffle(&format!(
484            "{}{}",
485            random_str(5, self.alphabets.get(&'c').unwrap()),
486            random_str(1, self.alphabets.get(&'C').unwrap()),
487        ))
488    }
489
490    /// Generate a keychain word type 2 (5 lowercase letters, 1 digit)
491    fn keychain_word_2(&self) -> String {
492        shuffle(&format!(
493            "{}{}",
494            random_str(5, self.alphabets.get(&'c').unwrap()),
495            random_str(1, self.alphabets.get(&'d').unwrap()),
496        ))
497    }
498
499    /// Generate a keychain word type 3 (6 lowercase letters)
500    fn keychain_word_3(&self) -> String {
501        random_str(6, self.alphabets.get(&'c').unwrap())
502    }
503
504    /**
505    Generate a keychain-style password with `n` "words"
506
507    First 3 "words" are types 1, 2, and 3.
508    Additional words are of random type.
509    Words are shuffled.
510    */
511    fn keychain_words(&self, n: usize) -> Vec<String> {
512        let mut words = vec![];
513        if n == 0 {
514            return words;
515        }
516        words.push(self.keychain_word_1());
517        if n >= 2 {
518            words.push(self.keychain_word_2());
519        }
520        if n >= 3 {
521            words.push(self.keychain_word_3());
522        }
523        let mut rng = rand::rng();
524        for _ in 4..=n {
525            words.push(match KEYCHAIN_WORDS.choose(&mut rng).unwrap() {
526                KeychainWord1 => self.keychain_word_1(),
527                KeychainWord2 => self.keychain_word_2(),
528                KeychainWord3 => self.keychain_word_3(),
529            });
530        }
531        words.shuffle(&mut rng);
532        words
533    }
534
535    /// Generate a keychain-style password like `plvifc-z9kedn-imcbDp`
536    #[must_use]
537    pub fn keychain(&self) -> String {
538        self.keychain_words(3).join("-")
539    }
540
541    /// Generate a "code name" like `BLUE STEEL`
542    #[must_use]
543    pub fn codename(&self) -> String {
544        format!(
545            "{} {}",
546            self.get(Adjective).to_uppercase(),
547            self.get(Noun).to_uppercase(),
548        )
549    }
550
551    /**
552    Generate a "code name series"
553
554    ```text
555    TAKENSTAR PRINTEDBOARD
556    TAKENSTAR GONEWAGON
557    TAKENSTAR PROUDCOOK
558    TAKENSTAR YOURLEAVE
559    TAKENSTAR DONEPARTY
560    TAKENSTAR USUALENTER
561    TAKENSTAR FOURBOAT
562    TAKENSTAR NICEABOVE
563    TAKENSTAR FRENCHPASS
564    TAKENSTAR FINEWAVE
565    ```
566    */
567    #[must_use]
568    pub fn codename_series(&self, n: usize) -> String {
569        let mut adjectives = self
570            .get_n(n + 1, Adjective)
571            .iter()
572            .map(|x| x.to_uppercase())
573            .collect::<Vec<_>>();
574        let mut nouns = self
575            .get_n(n + 1, Noun)
576            .iter()
577            .map(|x| x.to_uppercase())
578            .collect::<Vec<_>>();
579        let umbrella = format!("{}{}", adjectives.remove(0), nouns.remove(0));
580        let mut r = vec![];
581        while !adjectives.is_empty() {
582            r.push(format!(
583                "{umbrella} {}{}",
584                adjectives.remove(0),
585                nouns.remove(0),
586            ));
587        }
588        r.join("\n")
589    }
590
591    /// Generate a haiku
592    #[allow(clippy::missing_panics_doc)]
593    #[must_use]
594    pub fn haiku(&self, variant: HaikuVariant) -> String {
595        let (add_syllables, kebab, newline) = variant.options();
596        let (word_sep, line_sep, syl_sep) = if newline {
597            (" ", "\n", " ")
598        } else if kebab {
599            ("-", "/", "")
600        } else {
601            (" ", " / ", " ")
602        };
603        let max = 5;
604
605        let mut rng = rand::rng();
606
607        let mut lines = vec![];
608        let mut w = BTreeMap::new();
609
610        // Pick random syllable pattern
611        for mut c in [5, 7, 5] {
612            let mut words = vec![];
613            while c > 0 {
614                let poss = (1..=std::cmp::min(c, max)).collect::<Vec<_>>();
615                let i = poss.choose(&mut rng).unwrap();
616                words.push(*i);
617                c -= *i;
618                w.entry(*i).and_modify(|e| *e += 1).or_insert(1);
619            }
620            lines.push(words);
621        }
622
623        // Get words without repetion
624        let mut w = w
625            .iter()
626            .map(|(syllables, words)| {
627                (
628                    *syllables,
629                    self.get_n(
630                        *words,
631                        if self.extended {
632                            AllExtendedSyllables(*syllables)
633                        } else {
634                            AllSyllables(*syllables)
635                        },
636                    ),
637                )
638            })
639            .collect::<BTreeMap<usize, Vec<String>>>();
640
641        let mut r = vec![];
642
643        for line in &lines {
644            let mut words = vec![];
645            for (i, syllables) in line.iter().enumerate() {
646                let s = w.get_mut(syllables).unwrap();
647                let word = s.remove(0);
648                let details = self.words_built.get(&word).unwrap();
649                let word = if i == 0 || details.kinds.contains(&ProperNoun) {
650                    ucfirst(&word)
651                } else {
652                    word
653                };
654                let word = word
655                    .replace("Newhampshire", "New Hampshire")
656                    .replace("Newjersey", "New Jersey")
657                    .replace("Newmexico", "New Mexico")
658                    .replace("Newyork", "New York")
659                    .replace("Northamerica", "North America")
660                    .replace("Northcarolina", "North Carolina")
661                    .replace("Northdakota", "North Dakota")
662                    .replace("Rhodeisland", "Rhode Island")
663                    .replace("Southamerica", "South America")
664                    .replace("Southcarolina", "South Carolina")
665                    .replace("Southdakota", "South Dakota")
666                    .replace("Westvirginia", "West Virginia");
667                words.push(if i == 0 { ucfirst(&word) } else { word });
668            }
669            let mut words = words.join(word_sep);
670            if add_syllables {
671                write!(
672                    words,
673                    "{syl_sep}({})",
674                    line.iter()
675                        .map(ToString::to_string)
676                        .collect::<Vec<_>>()
677                        .join(",")
678                )
679                .unwrap();
680            }
681            r.push(words);
682        }
683
684        r.join(line_sep)
685    }
686
687    /// Generate a password from the given pattern
688    #[allow(clippy::missing_panics_doc)]
689    #[must_use]
690    pub fn generate(&self, pattern: &str) -> String {
691        let j = pattern.len();
692        let mut subs = vec![];
693        let mut words = 0;
694        let mut kc_words = 0;
695        let mut pre = BTreeMap::new();
696        'outer: for (sub, kind) in &self.subs {
697            let mut i = 0;
698            while i < j {
699                if let Some(p) = pattern[i..].find(sub) {
700                    let p = i + p;
701                    pre.insert(p, (sub, *kind));
702                    i = p + sub.len();
703                } else {
704                    continue 'outer;
705                }
706            }
707        }
708        let mut i = 0;
709        for (p, (sub, kind)) in &pre {
710            let s = &pattern[i..*p];
711            if *p > i {
712                words += s
713                    .chars()
714                    .map(|c| usize::from(['w', 'W', 'T'].contains(&c)))
715                    .sum::<usize>();
716                kc_words += s.chars().map(|c| usize::from(c == 'k')).sum::<usize>();
717                subs.push(Sub::Pattern(s));
718            }
719            subs.push(Sub::Special(((*sub).clone(), *kind)));
720            i = *p + sub.len();
721        }
722        if subs.is_empty() {
723            words += pattern
724                .chars()
725                .map(|c| usize::from(['w', 'W', 'T'].contains(&c)))
726                .sum::<usize>();
727            kc_words += pattern
728                .chars()
729                .map(|c| usize::from(c == 'k'))
730                .sum::<usize>();
731            subs.push(Sub::Pattern(pattern));
732        } else if i < j {
733            subs.push(Sub::Pattern(&pattern[i..j]));
734        }
735
736        let mut words = self.get_n(words, if self.extended { AllExtended } else { All });
737        let mut kc_words = self.keychain_words(kc_words);
738
739        let mut rng = rand::rng();
740        let mut r = String::new();
741
742        for sub in subs {
743            match sub {
744                Sub::Pattern(s) => {
745                    for c in s.chars() {
746                        match c {
747                            'W' => r.push_str(&words.remove(0).to_uppercase()),
748                            'w' => r.push_str(&words.remove(0)),
749                            'T' => r.push_str(&ucfirst(&words.remove(0))),
750                            'k' => r.push_str(&kc_words.remove(0)),
751                            _ => {
752                                if let Some(alphabet) = self.alphabets.get(&c) {
753                                    r.push(*alphabet.choose(&mut rng).unwrap());
754                                } else {
755                                    r.push(c);
756                                }
757                            }
758                        }
759                    }
760                }
761                Sub::Special((sub, kind)) => {
762                    let word = self.get(kind);
763                    let word = if sub.starts_with("{W:") {
764                        word.to_uppercase()
765                    } else if sub.starts_with("{T:") {
766                        ucfirst(&word)
767                    } else {
768                        word
769                    };
770                    r.push_str(&word);
771                }
772            }
773        }
774        r
775    }
776}
777
778impl Default for Config {
779    fn default() -> Config {
780        Config::from_str(CONFIG, false).unwrap()
781    }
782}
783
784//--------------------------------------------------------------------------------------------------
785
786#[derive(Clone, Copy)]
787pub enum HaikuVariant {
788    Normal,
789    WithSyllables,
790    WithSyllablesCondensed,
791    Condensed,
792    Full,
793    FullWithSyllables,
794}
795
796impl HaikuVariant {
797    fn options(self) -> (bool, bool, bool) {
798        match self {
799            Self::Condensed => (false, true, false),
800            Self::WithSyllablesCondensed => (true, true, false),
801            Self::Normal => (false, false, false),
802            Self::WithSyllables => (true, false, false),
803            Self::Full => (false, false, true),
804            Self::FullWithSyllables => (true, false, true),
805        }
806    }
807}
808//--------------------------------------------------------------------------------------------------
809
810#[derive(Debug)]
811enum Sub<'a> {
812    Pattern(&'a str),
813    Special((String, WordKind)),
814}
815
816//--------------------------------------------------------------------------------------------------
817
818enum KeychainWord {
819    KeychainWord1,
820    KeychainWord2,
821    KeychainWord3,
822}
823
824use KeychainWord::{KeychainWord1, KeychainWord2, KeychainWord3};
825
826const KEYCHAIN_WORDS: [KeychainWord; 3] = [KeychainWord1, KeychainWord2, KeychainWord3];
827
828//--------------------------------------------------------------------------------------------------
829
830/// Generate a random string of length `n` from `alphabet`
831fn random_str(n: usize, alphabet: &[char]) -> String {
832    let len = alphabet.len();
833    let mut r = String::new();
834    for _ in 0..n {
835        r.push(alphabet[rand::rng().random_range(0..len)]);
836    }
837    r
838}
839
840/// Shuffle a string slice
841#[must_use]
842pub fn shuffle(s: &str) -> String {
843    let mut rng = rand::rng();
844    let mut r = s.chars().collect::<Vec<_>>();
845    r.shuffle(&mut rng);
846    r.into_iter().collect()
847}