dspg/
lib.rs

1mod words;
2
3use crate::words::WORDS;
4use clap::Parser;
5use rand::seq::{IteratorRandom, SliceRandom};
6use rand::Rng;
7
8/// Dan’s Password Generator
9#[derive(Parser, Debug)]
10#[command(version, about, long_about = None)]
11pub struct Args {
12    /// Number of words to include in the password
13    #[arg(
14        short = 'n',
15        long = "num",
16        default_value_t = 5,
17        value_parser = clap::value_parser!(u8).range(3..)
18    )]
19    number_of_words: u8,
20
21    /// Minimum word length to use
22    #[arg(
23        short = 'm',
24        long = "min",
25        default_value_t = 3,
26        value_parser = clap::value_parser!(u8).range(3..=8)
27    )]
28    min_word_length: u8,
29
30    /// Maximum word length to use
31    #[arg(
32        short = 'x',
33        long = "max",
34        default_value_t = 8,
35        value_parser = clap::value_parser!(u8).range(3..=8)
36    )]
37    max_word_length: u8,
38}
39
40const SYMBOLS: &str = "-!@$%&*_+=#^.";
41const DIGITS: &str = "0123456789";
42
43pub fn password(args: Args) -> String {
44    let number_of_words = args.number_of_words;
45    let caps_word = rand::thread_rng().gen_range(0..number_of_words);
46
47    let filtered_words = words(args.min_word_length, args.max_word_length);
48
49    let number_of_digits = number_of_words - 2;
50    let symbol_joiner = rand::thread_rng().gen_range(0..number_of_digits);
51
52    (0..number_of_words)
53        .map(|n| {
54            let word = if n == caps_word {
55                filtered_words
56                    .choose(&mut rand::thread_rng())
57                    .unwrap()
58                    .to_uppercase()
59            } else {
60                filtered_words
61                    .choose(&mut rand::thread_rng())
62                    .unwrap()
63                    .to_string()
64            };
65
66            if n + 1 < number_of_words {
67                let joiner = if n == symbol_joiner {
68                    random_char(SYMBOLS)
69                } else {
70                    random_char(DIGITS)
71                };
72                format!("{}{}", word, joiner)
73            } else {
74                word
75            }
76        })
77        .collect::<Vec<String>>()
78        .join("")
79}
80
81fn random_char(input: &str) -> char {
82    input.chars().choose(&mut rand::thread_rng()).unwrap()
83}
84
85fn words(min: u8, max: u8) -> Vec<&'static str> {
86    WORDS
87        .iter()
88        .filter(|&&word| {
89            let len: u8 = word.len().try_into().unwrap();
90            len >= min && len <= max
91        })
92        .copied()
93        .collect()
94}