#![feature(iter_intersperse)] #[macro_use] extern crate serde; use std::collections::{HashMap, HashSet}; use rand::Rng; use rand::distributions::Slice as SliceDist; #[derive(Debug, Deserialize)] #[serde(untagged)] enum RuleSet { Password { length: usize, lowercase: bool, uppercase: bool, numbers: bool, #[serde(default = "default_special_characters")] special_characters: Vec, }, Passphrase { count: usize, delimiters: Vec, /// Path to words list. words: String, }, } fn default_special_characters() -> Vec { (0..128) .map(|b| char::from_u32(b).unwrap()) .filter(|c| c.is_ascii_graphic() && !c.is_ascii_alphanumeric()) .collect() } fn main() { let mut rng = rand::thread_rng(); let mut rule_sets: HashMap = toml::from_str(&std::fs::read_to_string("rules.toml").unwrap()).unwrap(); let mut args = std::env::args(); _ = args.next().unwrap(); let rule_set_name = args.next().expect("Must specify a rule set name"); let password = match rule_sets.remove(&rule_set_name).unwrap() { RuleSet::Password { length, lowercase, uppercase, numbers, special_characters } => { let mut chars = HashSet::new(); if lowercase { chars.extend('a'..='z'); } if uppercase { chars.extend('A'..='Z'); } if numbers { chars.extend('0'..='9'); } chars.extend(&special_characters); let chars = chars.into_iter().collect::>(); rng.sample_iter(SliceDist::new(&chars).unwrap()) .take(length) .collect::() } RuleSet::Passphrase { count, delimiters, words } => { assert!(count > 0); let words = std::fs::read_to_string(&words).unwrap(); let words = words.lines().filter(|s| !s.is_empty()).collect::>(); let words = SliceDist::new(&words).unwrap(); let delimiters = delimiters.iter().map(char::to_string).collect::>(); let delimiters = SliceDist::new(&delimiters).unwrap(); let mut s = String::new(); let mut word = rng.sample(&words); for _ in 0..count-1 { s.push_str(word); word = rng.sample(&words); s.push_str(rng.sample(&delimiters)); } s.push_str(word); s } }; println!("{}", password); }