Browse Source

change the RUST version to match the new parameter list

Pat Beirne 3 months ago
parent
commit
805917b26c
2 changed files with 133 additions and 76 deletions
  1. 2 10
      pwgen.py
  2. 131 66
      pwgen/src/main.rs

+ 2 - 10
pwgen.py

@@ -45,15 +45,7 @@ NUMBERS      = "0123456789"
 CONJUNCTIONS =  ["and", "and a", "and the","or", "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"]
-
-# delete these
-number = 1
-targetLen = 20
-
-useConjunction = False
-add_punc       = False
-add_number     = False
+    "for", "for a", "for the", "but", "so"]
 
 def toInt(s, chkRange, errStr = "sorry, that's not a number", rangeStr = "sorry, I can't do that number"):
   try:
@@ -72,7 +64,7 @@ def get_args(conf):
   while narg<nargs:
     a = sys.argv[narg]
     if a=="-h" or a=="--help" or a=="--version":
-      print ("pwgen.py  v1.3  generate passphrase; inspired by xkcd/936 and SteveGibson\n")
+      print ("pwgen.py  v1.4  generate passphrase; inspired by xkcd/936 and SteveGibson\n")
       print ("""Usage: pwgen.py [-sSpPkKnNcCaMX] [-L length] [num_phrases] 
          pwgen.py -h | --help | --version
 

+ 131 - 66
pwgen/src/main.rs

@@ -2,43 +2,69 @@
 
   generate passwords in useful ways
 
-usage: pwgen -c [number of phrases, default=1]
-      pwgen [len default=20] [number of phrases, default=1]     //no conjunctions
+usage: pwgen [options] [number of phrases, default=1]
     
 */
 use std::env;
-use std::cmp;
 use std::fs::File;
 use std::io::{BufReader, BufRead};
 use rand::Rng;
 
+#[derive(Debug)]  
+#[derive(PartialEq)]
+enum Padding {
+  Camel, Snake,
+  Dots, Spaces,
+  Punct, Numbers
+}
+
 #[derive(Debug)]
 struct Settings {
+  first_punct: bool,  last_punct: bool,
+  first_number: bool, last_number: bool,
+  capitalize: bool,
+  use_conjunctions: bool,
   target_len: usize,
   collecting_parameter: bool,
-  use_conjunction: bool,
   number: u16,
+  padding: Padding,
   help: bool,
-  pad_chars: String,
-  // debug: bool,
 }
 
-const CONJUNCTIONS: &[&str] = &["and", "and a", "and the","or", 
+const DOTS:    &str   = ".,";
+const PUNCTS:  &str   = " !@#$%^&*()-_=+[{]}\\|;:/?.>,<~";
+const NUMBERS: &str   = "0123456789";
+
+
+const CONJUNCTIONS : &[&str] = &["and", "and a", "and the","or", 
   "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 get_word(set:&Settings, words:&Vec<String>) -> String {
+  let mut rng = rand::thread_rng();
+  let m = &words[rng.gen_range(0 .. words.len())];
+  if set.capitalize || set.padding==Padding::Camel {
+    // m = m.capitalize();  from https://stackoverflow.com/questions/38406793/
+    let mut c = m.chars();
+    match c.next() {
+      None => String::new(),
+      Some(f) => f.to_uppercase().collect::<String>() + c.as_str(),
+    }
+  } else {
+    m.to_string()
+  }
+}
+
 fn main() -> std::io::Result<()> {
   let mut set = Settings {
-    number: 1, 
-    target_len: 20, 
+    first_punct: false, last_punct: false, first_number: false, last_number: false,
+    capitalize: false, use_conjunctions: false, 
+    number: 1, target_len: 20, 
     collecting_parameter: false, 
-    use_conjunction: false, 
     help: false, 
-      // owasp.org/www-community/password-special-characters
-    pad_chars: " !\"#$%&'()*+,-./:;<=>?@[\\]^_`{|}~".to_string() 
-      // princeton IT
-    //pad_chars: " ~`!@#$%^&*()-_+={}[]|\\;:\"<>,./?".to_string()
+    padding: Padding::Spaces,
   };
 
   let args: Vec<String> = env::args().collect();
@@ -55,78 +81,117 @@ fn main() -> std::io::Result<()> {
     .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 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');
+    let mut w = Vec::from([get_word(&set, &words)]);
+    let num_extra_chars = if set.first_punct {1} else {0}  
+        + if set.last_punct   {1} else {0}
+        + if set.first_number {1} else {0} 
+        + if set.last_number  {1} else {0};
+    while w.iter().map(|a| a.len()).sum::<usize>() 
+        + if set.padding==Padding::Camel {0} else {w.len()-1} 
+        + num_extra_chars < set.target_len {
+      if set.use_conjunctions {
+        let c = CONJUNCTIONS[rng.gen_range(0..CONJUNCTIONS.len())]; 
+        w.extend_from_slice(c.split_whitespace()
+            .map(|x| x.to_string())
+            .collect::<Vec<_>>()
+            .as_ref());
       }
-      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);
+
+      w.push(get_word(&set, &words));
+
+      let w_len = w.len();
+      if w[w_len-2] == "a" && "aeiouAEIOU".find(w[w_len-1].chars().nth(0).unwrap()).is_some() {
+        w[w_len - 2] = "an".to_string();
       }
-      println!("{}",pw);
     }
+    let mut ret : String;
+    match set.padding {
+    Padding::Camel   => ret = w.join(""),
+    Padding::Spaces  => ret = w.join(" "),
+    Padding::Dots    => ret = w.join(&DOTS.chars().nth(rng.gen_range(0 .. DOTS.len())).unwrap().to_string()),
+    Padding::Punct   => ret = w.join(&PUNCTS.chars().nth(rng.gen_range(0 .. PUNCTS.len())).unwrap().to_string()),
+    Padding::Numbers => ret = w.join(&NUMBERS.chars().nth(rng.gen_range(0 .. NUMBERS.len())).unwrap().to_string()),
+    Padding::Snake   => ret = w.join("_"),
+    }
+
+    let n = NUMBERS.chars().nth(rng.gen_range(0 .. NUMBERS.len())).unwrap().to_string();
+    if set.first_number {
+      ret = n.clone() + &ret;
+    }
+    if set.last_number {
+      ret = ret + &n;
+    }
+    let p = PUNCTS.chars().nth(rng.gen_range(0 .. PUNCTS.len())).unwrap().to_string();
+    if set.first_punct {
+      ret = p.clone() + &ret;
+    }
+    if set.last_punct {
+      ret = ret + &p;
+    }
+    println!("{}",ret);
   }
   Ok(())
 }
 
 fn usage() {
-  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
-    -p         pad with only spaces (for smartphone)
-    -P         pad with spaces, period, comma (for smartphone)
-    -n         pad with numbers
-    -N         pad with numbers, spaces, periods, comma
-    -L --length     add extra filler to reach length (default=20)
-    -c         make 2 word phrases with a conjunction filler
-    num_phrases make multiple passphrases (default=1)");
+  println!("pygen    v1.4    generate passphrase; inspired by xkcd/936
+Usage: pwgen.py [-sSpPkKnNcCaMX] [-L length] [num_phrases] 
+         pwgen.py -h | --help | --version
 
+       -s           pad with only spaces (default; for smartphone)
+       -S           pad with period and comma (for smartphone)
+       -p           pad with special characters
+       -P           pad with numbers
+       -k           connect the words as CamelCase
+       -K           connect the words as snake_case
+       -n           add a number at the beginning
+       -N           add a number at the end
+       -c           add a special character at the begnning
+       -C           add a special character at the end
+       -M           capitalize the words
+       -a           connect the words with a conjuction filler
+       -X           same as -MNCS (capital, periods & commas, extra number & char)
+       -L <n>       make pass phrases at least this long; default=20
+       num_phrases  make multiple pass phrases, one per line; default=1\n");
 }
 
 
 fn parse_args(a:&Vec<String>, s:&mut Settings) {
   // dbg!(a);
   for item in &a[1 .. ] {
-
     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.collecting_parameter = true, 
-      ("-c", ..) => s.use_conjunction = true,
-      ("-h", ..) | ("--help", ..) => s.help = true,
-      (.., Ok(x)) => s.number = x,
-      _ => println!("invalid option: {}", item),
+      let mut chr_itr = item.as_str().chars(); // pick off the first char
+      if chr_itr.next() == Some('-') {
+        for b in chr_itr {  // TODO: skip the first, we've already digested it
+          // println!("got a char {}", b);
+          match b {
+          'h' => s.help = true,
+          's' => s.padding = Padding::Spaces,
+          'S' => s.padding = Padding::Dots,
+          'p' => s.padding = Padding::Punct,
+          'P' => s.padding = Padding::Numbers,
+          'k' => s.padding = Padding::Camel,
+          'K' => s.padding = Padding::Snake,
+          'c' => s.first_punct  = true,
+          'C' => s.last_punct   = true,
+          'n' => s.first_number = true,
+          'N' => s.last_number  = true,
+          'X' => {s.capitalize = true; s.padding = Padding::Dots;
+                s.last_punct = true;  s.last_number = true; }, 
+          'M' => s.capitalize = true,
+          'a' => s.use_conjunctions = true,
+          'L' => s.collecting_parameter = true,
+          _ => println!("unexpected command option: {}", b),
+          }
+        }
+      } else { // not a hyphen-option phrase, check for an int
+        //println!("got a command line option {}",item.to_string());
+        s.number = item.parse::<u16>().expect("phrase count must be a positive integer");
       }
-    } else {
+    } else { // pick up the value of the -L argument
       s.collecting_parameter = false;
       s.target_len = item.parse::<usize>().expect("length must be a positive integer");
     }