Create a password generation program which will generate passwords containing random ASCII characters from the following groups:

         lower-case letters:  a ──► z
         upper-case letters:  A ──► Z
                     digits:  0 ──► 9
 other printable characters:  !"#$%&'()*+,-./:;<=>?@[]^_{|}~ 
 (the above character list excludes white-space, backslash and grave) 



The generated password(s) must include   at least one   (of each of the four groups):

   lower-case letter, 
   upper-case letter,
   digit  (numeral),   and 
   one  "other"  character. 



The user must be able to specify the password length and the number of passwords to generate.

The passwords should be displayed or written to a file, one per line.

The randomness should be from a system source or library.

The program should implement a help option or button which should describe the program and options when invoked.

You may also allow the user to specify a seed value, and give the option of excluding visually similar characters.

For example:            Il1     O0     5S     2Z            where the characters are:

   capital eye, lowercase ell, the digit one
   capital oh, the digit zero 
   the digit five, capital ess
   the digit two, capital zee


 
use rand::distributions::Alphanumeric;
use rand::prelude::IteratorRandom;
use rand::{thread_rng, Rng};
use std::iter;
use std::process;
use structopt::StructOpt;
const OTHER_VALUES: &str = "!\"#$%&'()*+,-./:;<=>?@[]^_{|}~";
 
// the core logic that creates our password
fn generate_password(length: u8) -> String {
    // cache thread_rng for better performance
    let mut rng = thread_rng();
    // the Alphanumeric struct provides 3/4
    // of the characters for passwords
    // so we can sample from it
    let mut base_password: Vec<char> = iter::repeat(())
        .map(|()| rng.sample(Alphanumeric))
        .take(length as usize)
        .collect();
    let mut end_range = 10;
    // if the user supplies a password length less than 10
    // we need to adjust the random sample range
    if length < end_range {
        end_range = length;
    }
    // create a random count of how many other characters to add
    let mut to_add = rng.gen_range(1, end_range as usize);
    loop {
        // create an iterator of required other characters
        let special = OTHER_VALUES.chars().choose(&mut rng).unwrap();
        to_add -= 1;
        base_password[to_add] = special;
        if to_add == 0 {
            break;
        }
    }
    base_password.iter().collect()
}
 
#[derive(StructOpt, Debug)]
#[structopt(name = "password-generator", about = "A simple password generator.")]
struct Opt {
    // make it SECURE by default
    // https://cheatsheetseries.owasp.org/cheatsheets/Password_Storage_Cheat_Sheet.html
    /// The password length
    #[structopt(default_value = "160")]
    length: u8,
    /// How many passwords to generate
    #[structopt(default_value = "1")]
    count: u8,
}
 
fn main() {
    // instantiate the options and use them as
    // arguments to our password generator
    let opt = Opt::from_args();
    const MINIMUM_LENGTH: u8 = 30;
    if opt.length < MINIMUM_LENGTH {
        eprintln!(
            "Please provide a password length greater than or equal to {}",
            MINIMUM_LENGTH
        );
        process::exit(1);
    }
    for index in 0..opt.count {
        let password = generate_password(opt.length);
        // do not print a newline after the last password
        match index + 1 == opt.count {
            true => print!("{}", password),
            _ => println!("{}", password),
        };
    }
}
 