{---------------------------------------------------------------------------------------------------- passwords.txt ~ rev. 2011.08.11 XGB Web and Software Design ~ www.xgbdesign.com This program implements a simple yet flexible and powerful password generator. The user can specify any of 15 different character configurations, including upper-case and/or lower-case letters, and/or numerals, and/or symbols from the keyboard. The user also specifies the number of passwords to generate, and the password length. The program outputs a two-dimensional array of passwords, arranged in rows of five across. PLEASE NOTE: This source code file has been saved with the ".txt" extension to obviate browser difficulties, particularly with Internet Explorer. To compile and run this code, just copy and paste this text into a new document, but save the file with the extension ".hs" PLEASE NOTE: This code is licensed under the terms of the GNU General Public License, Version 3 (http://www.gnu.org/copyleft/gpl.html#header). You are free to use and modify this source code, but if you do, kindly cite XGB Web and Software Design, whether in a link from your website, or or in your own source code. Thanks! ----------------------------------------------------------------------------------------------------} import Data.Char import Data.List import System.Random -- "Impure" IO functions: -------------------------------------------------------------------------- main :: IO () main = do greetings ioActions greetings :: IO () greetings = putStrLn "\n*** WELCOME TO PASSWORDS DELUXE, THE ULTIMATE PASSWORD GENERATOR! ***" ioActions :: IO () ioActions = do charTypes <- getCharacterTypes if valid charTypes then do makePasswords charTypes promptToRepeat else invalidCharTypes -- This function is intended to be a crude emulation of check-boxes on a proper GUI. -- In lieu of checking off boxes, the user enters a string of numerals denoting the -- desired character types. Had this program been tied to a GUI, check-boxes would -- have been the obvious choice for getting user input here (as opposed to radio -- buttons, for instance): getCharacterTypes :: IO String getCharacterTypes = do putStrLn "\nPlease enter the types of characters you want" putStrLn "(e.g., \"2 3\", \"321\", \"1 2 3 4\", etc.):\n" putStrLn " (1) Upper-case letters: A B C ... Z" putStrLn " (2) Lower-case letters: a b c ... z" putStrLn " (3) Numerals: 0 1 2 3 ... 9" putStrLn " (4) Symbols: ~ ! @ # $ % ..." getLine makePasswords :: String -> IO () makePasswords types = do -- Inputs: putStrLn "Now enter the desired number of passwords:" number <- fmap read getLine putStrLn "Lastly, enter the desired password length (longer passwords are more secure):" length <- fmap read getLine generator <- getStdGen -- Output: let billet = passwordBillet types (number * length) generator putStrLn $ "\n" ++ format (slices length billet) promptToRepeat :: IO () promptToRepeat = do putStrLn "Would you like another set of passwords? (y/_)" reply <- getLine if reply `elem` ["Y","y", "Yes","yes"] then do newStdGen ioActions else putStrLn "Exiting program, then...'bye!\n" invalidCharTypes :: IO () invalidCharTypes = do putStrLn "\n *** INVALID ENTRY: Not recognized as a set of character types. ***" ioActions -- "Pure" functions: ------------------------------------------------------------------------------ -- Although 'valid' and 'toIndices' are used to validate input, they're placed here -- because, well...they're PURE! (Note also the use of point-free style in each of -- the following three functions.) valid :: String -> Bool valid = not . null . toIndices -- This function is designed to be very fault-tolerant. Note that it filters out any -- characters other than '1' through '4' that the user may have entered--including -- spaces. The function also doesn't care whether the numerals are out of order, or -- whether they've been entered several times each. It is possible for the function -- to return an empty list, in which case the function 'valid' above triggers a -- message for the user to try again: toIndices :: String -> [Int] toIndices = sort . nub . map (\c -> ord c - ord '1') . filter (`elem` "1234") -- This function takes a list of integers denoting the types of characters that the user -- makes available for generating passwords, and returns a string containing those characters: availableChars :: [Int] -> String availableChars = concat . map (chars !!) where chars = [['A'..'Z'], ['a'..'z'], "0123456789", "!\"#$%&'()*+,-./:;<=>?@[\\]^_`{|}~"] -- This function takes a string denoting the types of characters chosen by the user; an -- integer denoting string length; and a random generator, and returns a "billet" string -- of random characters. This string will eventually be sliced up into individual passwords: passwordBillet :: (RandomGen g) => String -> Int -> g -> String passwordBillet str len gen = map (chars !!) $ take len (randomRs (0, max) gen) where chars = availableChars (toIndices str) max = length chars - 1 -- This is a recursive function that "slices" the billet string into a list of strings, -- each representing an individual password that is padded on the left with three spaces: slices :: Int -> String -> [String] slices 0 _ = [] slices _ "" = [] slices len str = (" " ++ take len str) : slices len (drop len str) -- This is another recursive function that takes the list of padded strings, and -- concatenates the strings into rows of five, with a line break at the end of each row: format :: [String] -> String format [] = "" format strs = (concat . take 5) strs ++ "\n" ++ format (drop 5 strs)