1mod words;
2
3use crate::words::WORDS;
4use clap::Parser;
5use rand::seq::{IteratorRandom, SliceRandom};
6use rand::Rng;
7
8#[derive(Parser, Debug)]
10#[command(version, about, long_about = None)]
11pub struct Args {
12 #[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 #[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 #[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}