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


import "random" for Random
import "/ioutil" for FileUtil, File, Input
import "/fmt" for Fmt
import "os" for Process
 
var r  = Random.new()
var rr = Random.new() // use a separate generator for shuffles
var lb = FileUtil.lineBreak
 
var lower = "abcdefghijklmnopqrstuvwxyz"
var upper = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
var digit = "0123456789"
var other = """!"#$%&'()*+,-./:;<=>?@[]^_{|}~"""
 
var exclChars = [
    "'I', 'l' and '1'",
    "'O' and '0'     ",
    "'5' and 'S'     ",
    "'2' and 'Z'     "
]
 
var shuffle = Fn.new { |s|
    var sl = s.toList
    rr.shuffle(sl)
    return sl.join()
}
 
var generatePasswords = Fn.new { |pwdLen, pwdNum, toConsole, toFile|
    var ll = lower.count
    var ul = upper.count
    var dl = digit.count
    var ol = other.count
    var tl = ll + ul + dl + ol
    var fw = toFile ? File.create("pwds.txt") : null
 
    if (toConsole) System.print("\nThe generated passwords are:")
    for (i in 0...pwdNum) {
        var pwd = lower[r.int(ll)] + upper[r.int(ul)] + digit[r.int(dl)] + other[r.int(ol)]
        for (j in 0...pwdLen - 4) {
            var k = r.int(tl)
            pwd = pwd + ((k < ll)      ? lower[k] :
                         (k < ll + ul) ? upper[k - ll] :
                         (k < tl - ol) ? digit[k - ll - ul] : other[tl - 1 - k])
        }
        for (i in 1..5) pwd = shuffle.call(pwd) // shuffle 5 times say
        if (toConsole) Fmt.print("  $2d: $s", i + 1, pwd)
        if (toFile) {
            fw.writeBytes(pwd)
            if (i < pwdNum - 1) fw.writeBytes(lb)
        }
    }
    if (toFile) {
       System.print("\nThe generated passwords have been written to the file pwds.txt")
       fw.close()
    }
}
 
var printHelp = Fn.new {
    System.print("""
This program generates up to 99 passwords of between 5 and 20 characters in
length.
 
You will be prompted for the values of all parameters when the program is run
- there are no command line options to memorize.
 
The passwords can either be written to the console or to a file (pwds.txt),
or both.
 
The passwords must contain at least one each of the following character types:
   lower-case letters :  a -> z
   upper-case letters :  A -> Z
   digits             :  0 -> 9
   other characters   :  !"#$%&'()*+,-./:;<=>?@[]^_{|}~
 
Optionally, a seed can be set for the random generator
(any non-zero number) otherwise the default seed will be used.
Even if the same seed is set, the passwords won't necessarily be exactly
the same on each run as additional random shuffles are always performed.
 
You can also specify that various sets of visually similar characters
will be excluded (or not) from the passwords, namely: Il1  O0  5S  2Z
 
Finally, the only command line options permitted are -h and -help which
will display this page and then exit.
 
Any other command line parameters will simply be ignored and the program
will be run normally.
 
""")
}
 
var args = Process.arguments
if (args.count == 1 && (args[0] == "-h" || args[0] == "-help")) {
   printHelp.call()
   return
}
 
System.print("Please enter the following and press return after each one")
 
var pwdLen = Input.integer("  Password length (5 to 20)     : ", 5, 20)
var pwdNum = Input.integer("  Number to generate (1 to 99)  : ", 1, 99)
 
var seed   = Input.number ("  Seed value (0 to use default) : ")
if (seed != 0) r = Random.new(seed)
 
System.print("  Exclude the following visually similar characters")
for (i in 0..3) {
    var yn = Input.option("    %(exclChars[i]) y/n : ", "ynYN")
    if (yn == "y" || yn == "Y") {
        if (i == 0) {
            upper = upper.replace("I", "")
            lower = lower.replace("l", "")
            digit = digit.replace("1", "")
        } else if (i == 1) {
            upper = upper.replace("O", "")
            digit = digit.replace("0", "")
        } else if (i == 2) {
            upper = upper.replace("S", "")
            digit = digit.replace("5", "")
        } else if (i == 3) {
            upper = upper.replace("Z", "")
            digit = digit.replace("2", "")
        }
    }
}
 
var toConsole = Input.option("  Write to console   y/n : ", "ynYN")
toConsole = toConsole == "y" || toConsole == "Y"
var toFile = true
if (toConsole) {
    toFile = Input.option("  Write to file      y/n : ", "ynYN")
    toFile = toFile == "y" || toFile == "Y"
}
 
generatePasswords.call(pwdLen, pwdNum, toConsole, toFile)