|  | @@ -7,6 +7,7 @@ usage: pwgen -c [number of phrases, default=1]
 | 
	
		
			
				|  |  |      
 | 
	
		
			
				|  |  |  */
 | 
	
		
			
				|  |  |  use std::env;
 | 
	
		
			
				|  |  | +use std::cmp;
 | 
	
		
			
				|  |  |  use std::fs::File;
 | 
	
		
			
				|  |  |  use std::io::{BufReader, BufRead};
 | 
	
		
			
				|  |  |  use rand::Rng;
 | 
	
	
		
			
				|  | @@ -14,7 +15,7 @@ use rand::Rng;
 | 
	
		
			
				|  |  |  #[derive(Debug)]
 | 
	
		
			
				|  |  |  struct Settings {
 | 
	
		
			
				|  |  |    target_len: usize,
 | 
	
		
			
				|  |  | -  target_len_set: bool,
 | 
	
		
			
				|  |  | +  collecting_parameter: bool,
 | 
	
		
			
				|  |  |    use_conjunction: bool,
 | 
	
		
			
				|  |  |    number: u16,
 | 
	
		
			
				|  |  |    help: bool,
 | 
	
	
		
			
				|  | @@ -23,23 +24,21 @@ struct Settings {
 | 
	
		
			
				|  |  |  }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |  const CONJUNCTIONS: &[&str] = &["and", "and a", "and the","or", 
 | 
	
		
			
				|  |  | -	"or the", "or a", "with a", "with", "with the", "by a", "by the",
 | 
	
		
			
				|  |  | +  "or the", "or a", "with a", "with", "with the", "by a", "by the",
 | 
	
		
			
				|  |  |     "on a", "on the","on", "in a", "in the", "in",
 | 
	
		
			
				|  |  |     "for", "for a", "for the", "but", "so"];
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  |  fn main() -> std::io::Result<()> {
 | 
	
		
			
				|  |  |    let mut set = Settings {
 | 
	
		
			
				|  |  |      number: 1, 
 | 
	
		
			
				|  |  | -    target_len: 20, target_len_set: false, 
 | 
	
		
			
				|  |  | +    target_len: 20, 
 | 
	
		
			
				|  |  | +    collecting_parameter: false, 
 | 
	
		
			
				|  |  |      use_conjunction: false, 
 | 
	
		
			
				|  |  | -    // debug: false,
 | 
	
		
			
				|  |  |      help: false, 
 | 
	
		
			
				|  |  | -		// owasp.org/www-community/password-special-characters
 | 
	
		
			
				|  |  | +      // owasp.org/www-community/password-special-characters
 | 
	
		
			
				|  |  |      pad_chars: " !\"#$%&'()*+,-./:;<=>?@[\\]^_`{|}~".to_string() 
 | 
	
		
			
				|  |  | -		// princeton IT
 | 
	
		
			
				|  |  | -	 //pad_chars: " ~`!@#$%^&*()-_+={}[]|\\;:\"<>,./?".to_string()
 | 
	
		
			
				|  |  | +      // princeton IT
 | 
	
		
			
				|  |  | +    //pad_chars: " ~`!@#$%^&*()-_+={}[]|\\;:\"<>,./?".to_string()
 | 
	
		
			
				|  |  |    };
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |    let args: Vec<String> = env::args().collect();
 | 
	
	
		
			
				|  | @@ -48,50 +47,55 @@ fn main() -> std::io::Result<()> {
 | 
	
		
			
				|  |  |      usage();
 | 
	
		
			
				|  |  |      return Ok(())
 | 
	
		
			
				|  |  |    }
 | 
	
		
			
				|  |  | -  
 | 
	
		
			
				|  |  | +    
 | 
	
		
			
				|  |  |    let file = File::open("/usr/share/dict/words")
 | 
	
		
			
				|  |  | -		.expect("Dictionary file /usr/share/dict/words not found");
 | 
	
		
			
				|  |  | +    .expect("Dictionary file /usr/share/dict/words not found");
 | 
	
		
			
				|  |  |    let buf = BufReader::new(file);
 | 
	
		
			
				|  |  | -  let words: Vec<String> = buf.lines().map(|x| x.expect("bad line")).collect();
 | 
	
		
			
				|  |  | +  let words: Vec<String> = buf.lines()
 | 
	
		
			
				|  |  | +    .map(|x| x.expect("bad line"))
 | 
	
		
			
				|  |  | +    .filter(|x| !x.ends_with("'s"))
 | 
	
		
			
				|  |  | +    .collect();
 | 
	
		
			
				|  |  |    let num_words = words.len();
 | 
	
		
			
				|  |  | -  
 | 
	
		
			
				|  |  | -	let mut rng = rand::thread_rng();
 | 
	
		
			
				|  |  | -	    
 | 
	
		
			
				|  |  | -	for _i in 0 .. set.number {
 | 
	
		
			
				|  |  | -		let w0 = &words[rng.gen_range(0 .. num_words)];
 | 
	
		
			
				|  |  | -		let w1 = &words[rng.gen_range(0 .. num_words)];
 | 
	
		
			
				|  |  | -		let w2 = &words[rng.gen_range(0 .. num_words)];
 | 
	
		
			
				|  |  | -		let w3 = &words[rng.gen_range(0 .. num_words)];
 | 
	
		
			
				|  |  | -		
 | 
	
		
			
				|  |  | -		let padding = set.pad_chars.chars()
 | 
	
		
			
				|  |  | -			.nth(rng.gen_range(0 .. set.pad_chars.len()))
 | 
	
		
			
				|  |  | -			.unwrap()
 | 
	
		
			
				|  |  | -			.to_string();
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -		if set.use_conjunction {
 | 
	
		
			
				|  |  | -		  let mut conj = CONJUNCTIONS[rng.gen_range(0 .. CONJUNCTIONS.len())].to_string();
 | 
	
		
			
				|  |  | -		  if conj.ends_with(" a") && w1.starts_with(|x| "aAeEiIoOuU".find(x).is_some()) {
 | 
	
		
			
				|  |  | -			  conj.push('n');
 | 
	
		
			
				|  |  | -		  }
 | 
	
		
			
				|  |  | -		  println!("{} {} {}", w0, conj, w1);
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -		} else {
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -			let mut pw = format!("{}{}{}",w0,padding,w1);
 | 
	
		
			
				|  |  | -			if pw.len() < set.target_len {
 | 
	
		
			
				|  |  | -				pw = format!("{}{}{}",pw,padding,w2);
 | 
	
		
			
				|  |  | -				if pw.len() < set.target_len {
 | 
	
		
			
				|  |  | -					pw = format!("{}{}{}",pw,padding,w3);
 | 
	
		
			
				|  |  | -				}
 | 
	
		
			
				|  |  | -			}
 | 
	
		
			
				|  |  | -			println!("{}",pw);
 | 
	
		
			
				|  |  | -		}
 | 
	
		
			
				|  |  | +      
 | 
	
		
			
				|  |  | +  let mut rng = rand::thread_rng();
 | 
	
		
			
				|  |  | +  for _i in 0 .. set.number {
 | 
	
		
			
				|  |  | +    let w0 = &words[rng.gen_range(0 .. num_words)];
 | 
	
		
			
				|  |  | +    let w1 = &words[rng.gen_range(0 .. num_words)];
 | 
	
		
			
				|  |  | +    let w2 = &words[rng.gen_range(0 .. num_words)];
 | 
	
		
			
				|  |  | +    let mut words_len = w0.len() + w1.len();
 | 
	
		
			
				|  |  | +      
 | 
	
		
			
				|  |  | +    let padding = set.pad_chars.chars()
 | 
	
		
			
				|  |  | +      .nth(rng.gen_range(0 .. set.pad_chars.len()))
 | 
	
		
			
				|  |  | +      .unwrap()
 | 
	
		
			
				|  |  | +      .to_string();
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +    if set.use_conjunction {
 | 
	
		
			
				|  |  | +      let mut conj = CONJUNCTIONS[rng.gen_range(0 .. CONJUNCTIONS.len())].to_string();
 | 
	
		
			
				|  |  | +      if conj.ends_with(" a") && w1.starts_with(|x| "aAeEiIoOuU".contains(x)) {
 | 
	
		
			
				|  |  | +        conj.push('n');
 | 
	
		
			
				|  |  | +      }
 | 
	
		
			
				|  |  | +      println!("{} {} {}", w0, conj, w1);
 | 
	
		
			
				|  |  | +    } else {
 | 
	
		
			
				|  |  | +      let pw;
 | 
	
		
			
				|  |  | +      let r:usize;
 | 
	
		
			
				|  |  | +      // if 2 words is enough
 | 
	
		
			
				|  |  | +      if words_len+3 >= set.target_len {
 | 
	
		
			
				|  |  | +        r = cmp::max(set.target_len, words_len+1) - words_len;
 | 
	
		
			
				|  |  | +        pw = format!("{}{}{}",w0,padding.repeat(r),w1); 
 | 
	
		
			
				|  |  | +      } else {
 | 
	
		
			
				|  |  | +          // we need 3 words
 | 
	
		
			
				|  |  | +          words_len = words_len + w2.len();
 | 
	
		
			
				|  |  | +          r = cmp::max(set.target_len, words_len+2) - words_len;
 | 
	
		
			
				|  |  | +          pw = format!("{}{}{}{}{}",w0,padding.repeat(r/2),w1,padding.repeat(r-r/2),w2);
 | 
	
		
			
				|  |  | +      }
 | 
	
		
			
				|  |  | +      println!("{}",pw);
 | 
	
		
			
				|  |  | +    }
 | 
	
		
			
				|  |  |    }
 | 
	
		
			
				|  |  |    Ok(())
 | 
	
		
			
				|  |  |  }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |  fn usage() {
 | 
	
		
			
				|  |  | -  println!("pygen    v1.0    generate passphrase; inspired by xkcd/936
 | 
	
		
			
				|  |  | +  println!("pygen    v1.1    generate passphrase; inspired by xkcd/936
 | 
	
		
			
				|  |  |  Usage: pygen [-p | -P | -n | -N] [-L <n> | --length <n>] [num_phrases]
 | 
	
		
			
				|  |  |         pygen -c [num_phrases]
 | 
	
		
			
				|  |  |         pygen -h | --help
 | 
	
	
		
			
				|  | @@ -109,23 +113,23 @@ Usage: pygen [-p | -P | -n | -N] [-L <n> | --length <n>] [num_phrases]
 | 
	
		
			
				|  |  |  fn parse_args(a:&Vec<String>, s:&mut Settings) {
 | 
	
		
			
				|  |  |    // dbg!(a);
 | 
	
		
			
				|  |  |    for item in &a[1 .. ] {
 | 
	
		
			
				|  |  | -	  if s.target_len_set {
 | 
	
		
			
				|  |  | -		  s.target_len_set = true;
 | 
	
		
			
				|  |  | -		  s.target_len = item.parse::<usize>().expect("length must be a positive integer");
 | 
	
		
			
				|  |  | -		  continue;
 | 
	
		
			
				|  |  | -		}
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -    match (item.as_str(),item.parse::<u16>()) {
 | 
	
		
			
				|  |  | +    if s.collecting_parameter == false {
 | 
	
		
			
				|  |  | +      match (item.as_str(),item.parse::<u16>()) {
 | 
	
		
			
				|  |  |        ("-p", ..) => s.pad_chars = " ".to_string(),
 | 
	
		
			
				|  |  |        ("-P", ..) => s.pad_chars = " ,.".to_string(),
 | 
	
		
			
				|  |  |        ("-n", ..) => s.pad_chars = "0123456789".to_string(),
 | 
	
		
			
				|  |  |        ("-N", ..) => s.pad_chars = " ,.0123456789".to_string(),
 | 
	
		
			
				|  |  | -      ("-L", ..) | ("--length", ..) => s.target_len_set = true, 
 | 
	
		
			
				|  |  | +      ("-L", ..) | ("--length", ..) => s.collecting_parameter = true, 
 | 
	
		
			
				|  |  |        ("-c", ..) => s.use_conjunction = true,
 | 
	
		
			
				|  |  |        ("-h", ..) | ("--help", ..) => s.help = true,
 | 
	
		
			
				|  |  |        (.., Ok(x)) => s.number = x,
 | 
	
		
			
				|  |  |        _ => println!("invalid option: {}", item),
 | 
	
		
			
				|  |  | +      }
 | 
	
		
			
				|  |  | +    } else {
 | 
	
		
			
				|  |  | +      s.collecting_parameter = false;
 | 
	
		
			
				|  |  | +      s.target_len = item.parse::<usize>().expect("length must be a positive integer");
 | 
	
		
			
				|  |  |      }
 | 
	
		
			
				|  |  |    }
 | 
	
		
			
				|  |  | -  //dbg!(s);
 | 
	
		
			
				|  |  | +  // dbg!(s);
 | 
	
		
			
				|  |  |  }
 |