https://git.spwbk.site/swatson/swmkp/raw/master/src/main.rs
___________________________________
use rand::seq::SliceRandom;
use std::path::{Path};
use std::fs::File;
use std::io;
use std::io::{BufRead, BufReader};
use clap::{Arg,App};
use regex::Regex;

fn no_word_file() -> io::Result<String> {

   // Fall back to this if we cant find the word file
   let chars: Vec<char> = "abcdefghijklmnopqrstuvwxyzABCDEFGIJKLMNOPQRSTUVWXYZ0123456789!@$%^&*()-+".chars().collect();
   let mut password: String = String::new();
   for _x in 0..63 {
       let random_char = chars.choose(&mut rand::thread_rng());
       password.push_str(&random_char.unwrap().to_string());
   }

   Ok(password)

}

fn character_weave(passphrase: String) -> io::Result<String> {

   /*
    * a - @
    * i - !
    * o - 0
    * s - $
    * e - 3
   */
   let mut new_passphrase: String = String::new();
   for c in passphrase.chars() {
       // Probably a better way to do this as opposed to a giant if / elsif
       if c.to_string() == "a" {
           new_passphrase.push_str(&"@".to_string());
       } else if c.to_string() == "i" {
           new_passphrase.push_str(&"!".to_string());
       } else if c.to_string() == "o" {
           new_passphrase.push_str(&"0".to_string());
       } else if c.to_string() == "s" {
           new_passphrase.push_str(&"$".to_string());
       } else if c.to_string() == "e" {
           new_passphrase.push_str(&"3".to_string());
       } else {
           new_passphrase.push_str(&c.to_string());
       }
   }

   Ok(new_passphrase)

}

// This is stupid TODO
fn print_password(password: String) {

   println!("{}",password);

}

fn read_phrase_file(word_file: String) -> io::Result<Vec<String>> {

   if ! Path::new(&word_file).exists() {
       println!("Could not find {}, generating a password and exiting", word_file);
       print_password(no_word_file().unwrap());
       std::process::exit(1);
   }

   let words_fh = File::open(word_file)?;
   let mut words = vec![];
   let re = Regex::new(r"^([0-9]{5})\s(.*)$").unwrap();
   for line in BufReader::new(words_fh).lines() {
       for re_capture in re.captures_iter(&line.unwrap()) {
           // println!("Capture 1: {}, Capture 2: {}", &re_capture[1], &re_capture[2]);
           // Only push word for now, need to figure out multidimentional arrays
           words.push(re_capture[2].to_string());
       }
   }

   Ok(words)

}

fn main() {

   // This variable is used as the default word file path, see Arg::with_name("words_file")
   // This is probably wrong/bad, and limits the functionality to Linux only
   let mut default_words_path = dirs::home_dir().unwrap().to_str().unwrap().to_string();
   default_words_path.push_str(&"/.local/bin/eff_large_wordlist.txt".to_string());

   let args = App::new("swmkp")
       .version("0.1")
       .about("My pw gen. Based on EFF passphrase guidelines\nhttps://www.eff.org/dice")
       .arg(Arg::with_name("words-file")
            .short("f")
            .long("words-file")
            .required(true)
            .takes_value(true)
            .default_value(&default_words_path)
            .help("Path to passphrase file, expects file provided by EFF\nSee:\nhttps://www.eff.org/files/2016/07/18/eff_large_wordlist.txt\nhttps://eff.org/files/2016/09/08/eff_short_wordlist_1.txt\nhttps://eff.org/files/2016/09/08/eff_short_wordlist_2_0.txt\n"))
       .arg(Arg::with_name("length")
            .short("l")
            .long("length")
            .required(false)
            .takes_value(true)
            .default_value("5")
            .help("How many words to use"))
       .arg(Arg::with_name("delimiter")
            .short("d")
            .long("delimiter")
            .required(false)
            .takes_value(true)
            .default_value("-")
            .help("What word delimiter to use"))
       .arg(Arg::with_name("with-characters")
            .short("w")
            .long("with-characters")
            .required(false)
            .takes_value(false)
            .help("Replaces some letters with special characters"))
       .arg(Arg::with_name("password")
            .short("p")
            .long("password")
            .required(false)
            .takes_value(false)
            .help("Generate a 64 character password, as opposed to a passphrase\nFallback here if no passphrase file provided/found"))
   .get_matches();


   if args.is_present("password") {
       print_password(no_word_file().unwrap());
       std::process::exit(0);
   }

   let roll_count: usize = args.value_of("length").unwrap().parse().unwrap();
   let delim: String = args.value_of("delimiter").unwrap().to_string();

   let words = read_phrase_file(args.value_of("words-file").unwrap().to_string()).unwrap();

   let mut passphrase: String = String::new();
   for x in 0..roll_count {
       let random_word = words.choose(&mut rand::thread_rng());
       if x == ( roll_count - 1 ) {
           passphrase.push_str(random_word.unwrap());
       } else {
           passphrase.push_str(random_word.unwrap());
           passphrase.push_str(&delim);
       }
   }

   if args.is_present("with-characters") {
       let new_passphrase = character_weave(passphrase).unwrap();
       println!("{}",new_passphrase);
   } else {
       println!("{}",passphrase);
   }
}