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


require "random/secure"
 
special_chars = true
similar = 0
require_groups = true
length = 20
count = 1
 
def show_usage
  puts <<-USAGE
 
       Passwords generator
       Usage: pass2 [count] [-l<length>] [-s{0|1|2}] [-ng] [-ns]
 
         count: number of passwords to be generated (default: 1)
         -l<length>: length of passwords (default: 20)
         -s{0|1|2}: exclude visually similar characters. 
                      0 - don't exclude (default)
                      1 - exclude 0, O, 1, I, l, |
                      2 - also exclude 2, Z, 5, S
        -ng: don''t require password to include character from every group
        -ns: don''t include special chars in password
 
        Default value of switches is choosen to match the task in page header, but I suggest to use the "-s1 -ng -ns".
       USAGE
end
 
count_set = false
begin
  ARGV.each do |arg|
    case arg
    when /-l(\d+)/
      length = $1.to_i
      raise "Error: minimal length is 4" if length < 4
    when /-s(\d)/
      similar = $1.to_i
      raise "Error: minimal length is 4" unless {0, 1, 2}.includes? similar
    when /-ng/
      require_groups = false
    when /-ns/
      special_chars = false
    when /(\d+)/
      raise "incorrect arguments" if count_set
      count_set = true
      count = $1.to_i
    else
      raise "incorrect argument"
    end
  end
rescue ex
  puts ex.message
  show_usage
  exit
end
 
# actual program
GROUPS = [('a'..'z').to_a, ('A'..'Z').to_a, ('0'..'9').to_a]
GROUPS << "!\"#$%&'()*+,-./:;<=>?@[]^_{|}~".chars if special_chars
 
letters = GROUPS.flatten
letters -= "1l|I0O".chars if similar > 0
letters -= "5S2Z".chars if similar > 1
 
loop do
  s = Array(Char).new(length) { letters.sample(Random::Secure) }
  # it's better to just discard in order to don't lose enthropy
  next if require_groups && GROUPS.any? { |x| (s & x).size == 0 }
  puts s.join
  count -= 1
  break if count <= 0
end
 