elocrypt 2.0.0 → 2.0.1
raw patch · 7 files changed
+264/−267 lines, 7 filesPVP: major bump suggested
API removals or changes: PVP suggests a major version bump
API changes (from Hackage documentation)
- Data.Elocrypt: nextLetter :: MonadRandom m => String -> m Char
+ Data.Elocrypt: capitalize1 :: MonadRandom m => Int -> String -> m String
- Data.Elocrypt: capitalizeR :: MonadRandom m => Bool -> Char -> m Char
+ Data.Elocrypt: capitalizeR :: MonadRandom m => Int -> String -> m String
- Data.Elocrypt: first2 :: MonadRandom m => GenOptions -> m String
+ Data.Elocrypt: first2 :: MonadRandom m => m String
- Data.Elocrypt: lastN :: MonadRandom m => GenOptions -> Int -> String -> m String
+ Data.Elocrypt: lastN :: MonadRandom m => Int -> String -> m String
- Data.Elocrypt: next :: MonadRandom m => GenOptions -> String -> m Char
+ Data.Elocrypt: next :: MonadRandom m => String -> m Char
Files
- README.md +2/−2
- elocrypt.cabal +2/−2
- src/cli/Main.hs +77/−80
- src/lib/Data/Elocrypt.hs +96/−86
- test/IntegTest/Elocrypt/PassphraseTest.hs +2/−2
- test/IntegTest/Elocrypt/PasswordTest.hs +2/−2
- test/Test/ElocryptTest.hs +83/−93
README.md view
@@ -15,8 +15,8 @@ ``` Binaries are also available:- * [elocrypt-2.0.0-linux-bin.tar.gz](https://github.com/sgillespie/elocrypt/releases/download/v1.0.0/elocrypt-1.0.0-linux-bin.tar.gz)- * [elocrypt-2.0.0-windows-bin.exe](https://github.com/sgillespie/elocrypt/releases/download/v1.0.0/elocrypt-1.0.0-windows-bin.exe)+ * [elocrypt-2.0.0-linux-bin.tar.gz](https://github.com/sgillespie/elocrypt/releases/download/v2.0.0/elocrypt-2.0.0-linux-bin.tar.gz)+ * [elocrypt-2.0.0-windows-bin.exe](https://github.com/sgillespie/elocrypt/releases/download/v2.0.0/elocrypt-2.0.0-windows-bin.exe) ## Running Generate a list passwords:
elocrypt.cabal view
@@ -1,5 +1,5 @@ name: elocrypt-version: 2.0.0+version: 2.0.1 synopsis: Generate easy-to-remember, hard-to-guess passwords homepage: https://www.github.com/sgillespie/elocrypt license: OtherLicense@@ -26,7 +26,7 @@ source-repository this type: git location: https://github.com/sgillespie/elocrypt.git- tag: v1.0.0+ tag: v2.0.1 library exposed-modules: Data.Elocrypt,
src/cli/Main.hs view
@@ -12,12 +12,12 @@ import Data.Elocrypt -version :: String-termLen :: Int+version :: String+termLen :: Int termHeight :: Int -version = "elocrypt 2.0.0"-termLen = 80+version = "elocrypt 2.0.1"+termLen = 80 termHeight = 10 data Options = Options {@@ -36,44 +36,46 @@ deriving (Eq, Show) defaultOptions :: Options-defaultOptions = Options {- optCapitals = False,- optLength = 8,- optMaxLength = 10,- optNumber = Nothing,- optPassType = Word,- optHelp = False,- optVersion = False+defaultOptions = Options+ { optCapitals = False+ , optLength = 8+ , optMaxLength = 10+ , optNumber = Nothing+ , optPassType = Word+ , optHelp = False+ , optVersion = False } options :: [OptDescr (Options -> Options)]-options = [- Option ['c'] ["capitals"] - (NoArg (\o -> o { optCapitals = True }))- "Include capital letters",-- Option ['n'] ["number"]- (ReqArg (\n o -> o { optNumber = Just (read n) }) "NUMBER")- "The number of passwords to generate",-- Option ['p'] ["passphrase"]- (NoArg (\o -> o { optPassType = Phrase }))- "Generate passphrases instead of passwords",-- Option ['h'] ["help"]- (NoArg (\o -> o { optHelp = True }))- "Show this help",-- Option ['v'] ["version"]- (NoArg (\o -> o { optVersion = True }))- "Show version and exit"+options =+ [ Option+ ['c']+ ["capitals"]+ (NoArg (\o -> o { optCapitals = True }))+ "Include at least one capital letter"+ , Option+ ['n']+ ["number"]+ (ReqArg (\n o -> o { optNumber = Just (read n) }) "NUMBER")+ "The number of passwords to generate"+ , Option+ ['p']+ ["passphrase"]+ (NoArg (\o -> o { optPassType = Phrase }))+ "Generate passphrases instead of passwords"+ , Option ['h'] ["help"] (NoArg (\o -> o { optHelp = True })) "Show this help"+ , Option+ ['v']+ ["version"]+ (NoArg (\o -> o { optVersion = True }))+ "Show version and exit" ] main :: IO () main = do args <- getArgs opts <- elocryptOpts args- gen <- getStdGen+ gen <- getStdGen when (optHelp opts) $ do hPutStrLn stderr usage@@ -83,8 +85,7 @@ hPutStrLn stderr version exitSuccess - when (optLength opts == 0)- exitSuccess -- Nothing to do+ when (optLength opts == 0) exitSuccess -- Nothing to do putStrLn (generate opts gen) @@ -93,9 +94,9 @@ (opts, nonopts) <- elocryptOpts' args return $ case nonopts of- (o:os:_) -> opts { optLength = read o, optMaxLength = read os }- (o:_) -> opts { optLength = read o }- [] -> opts+ (o : os : _) -> opts { optLength = read o, optMaxLength = read os }+ (o : _) -> opts { optLength = read o }+ [] -> opts elocryptOpts' :: [String] -> IO (Options, [String]) elocryptOpts' args = case getOpt Permute options args of@@ -103,50 +104,52 @@ let opts' = foldl (flip id) defaultOptions opts return (opts', nonopts) - (_ , _ , errs) -> do+ (_, _, errs) -> do hPutStrLn stderr (concat errs) hPutStrLn stderr usage exitFailure generate :: RandomGen g => Options -> g -> String-generate opts@Options{optPassType=Word} = passwords opts-generate opts@Options{optPassType=Phrase} = passphrases opts+generate opts@Options { optPassType = Word } = passwords opts+generate opts@Options { optPassType = Phrase } = passphrases opts passwords :: RandomGen g => Options -> g -> String-passwords opts@Options{optLength = len, optNumber = n} gen- = format " " . groupWith splitAt' width " " $ ps- where ps = newPasswords len num (getGenOptions opts) gen- cols = columns len- num = fromMaybe (nWords cols) n- width = max termLen (len + 2)+passwords opts@Options { optLength = len, optNumber = n } gen =+ format " " . groupWith splitAt' width " " $ ps+ where+ ps = newPasswords len num (getGenOptions opts) gen+ cols = columns len+ num = fromMaybe (nWords cols) n+ width = max termLen (len + 2) passphrases :: RandomGen g => Options -> g -> String-passphrases opts@Options{optCapitals = caps,- optLength = minLen,- optMaxLength = maxLen,- optNumber = n} gen+passphrases opts@Options { optCapitals = caps, optLength = minLen, optMaxLength = maxLen, optNumber = n } gen = format " " . take lines' . groupWith splitAt' width " " $ passphrase- where passphrase = newPassphrase words' minLen maxLen (getGenOptions opts) gen- words' = columns minLen * lines'- width = max termLen (maxLen + 1)- lines' = fromMaybe termHeight n+ where+ passphrase = newPassphrase words' minLen maxLen (getGenOptions opts) gen+ words' = columns minLen * lines'+ width = max termLen (maxLen + 1)+ lines' = fromMaybe termHeight n getGenOptions :: Options -> GenOptions-getGenOptions Options{optCapitals=caps} = genOptions{genCapitals=caps}+getGenOptions Options { optCapitals = caps } =+ genOptions { genCapitals = caps } usage :: String usage = usageInfo (intercalate "\n" headerLines) options- where headerLines = [- "Usage: elocrypt [option...] length",- " elocrypt -p [option...] min-length max-length"- ]+ where+ headerLines =+ [ "Usage: elocrypt [option...] length"+ , " elocrypt -p [option...] min-length max-length"+ ] -- Utilities -- Calculate the number of passwords to print per line columns :: Int -> Int-columns len | len <= termLen - 2 = termLen `div` (len + 2)- | otherwise = 1+columns len+ | len <= termLen - 2 = termLen `div` (len + 2)+ | otherwise = 1 -- Format a 2D list of Strings, -- 1 list per line@@ -159,31 +162,25 @@ -- Group a 2D array with a function by total length groupWith- :: (Int -> [a] -> [[a]] -> ([[a]], [[a]]))- -> Int- -> [a]- -> [[a]]- -> [[[a]]]+ :: (Int -> [a] -> [[a]] -> ([[a]], [[a]])) -> Int -> [a] -> [[a]] -> [[[a]]] groupWith _ _ _ [] = []-groupWith f i sep ls | null groups = [[head ls]]- | otherwise = groups+groupWith f i sep ls+ | null groups = [[head ls]]+ | otherwise = groups where groups = groupWith' f i sep ls groupWith'- :: (Int -> [a] -> [[a]] -> ([[a]], [[a]]))- -> Int- -> [a]- -> [[a]]- -> [[[a]]]+ :: (Int -> [a] -> [[a]] -> ([[a]], [[a]])) -> Int -> [a] -> [[a]] -> [[[a]]] groupWith' _ _ _ [] = []-groupWith' f i sep ls = g : groupWith f i sep ls'- where (g, ls') = f i sep ls+groupWith' f i sep ls = g : groupWith f i sep ls' where (g, ls') = f i sep ls -- Split a 2D array by the total length splitAt' :: Int -> [a] -> [[a]] -> ([[a]], [[a]]) splitAt' 0 _ ls = ([], ls) splitAt' _ _ [] = ([], [])-splitAt' n sep (l:ls) | n >= length l + sl = (l:xs, xs')- | otherwise = ([], l:ls)- where (xs, xs') = splitAt' (n - length l - sl) sep ls- sl = length sep+splitAt' n sep (l : ls)+ | n >= length l + sl = (l : xs, xs')+ | otherwise = ([], l : ls)+ where+ (xs, xs') = splitAt' (n - length l - sl) sep ls+ sl = length sep
src/lib/Data/Elocrypt.hs view
@@ -31,9 +31,7 @@ -- |Default options for generating passwords or passphrases. This is -- the preferred way to construct 'GenOptions'. genOptions :: GenOptions-genOptions = GenOptions {- genCapitals = False-}+genOptions = GenOptions {genCapitals = False} -- * Random password generators @@ -45,11 +43,12 @@ -- myGenPassword :: IO (String, StdGen) -- myGenPassword = genPassword 10 genOptions \`liftM\` getStdGen -- @-genPassword :: RandomGen g- => Int -- ^ password length- -> GenOptions -- ^ options- -> g -- ^ random generator- -> (String, g)+genPassword+ :: RandomGen g+ => Int -- ^ password length+ -> GenOptions -- ^ options+ -> g -- ^ random generator+ -> (String, g) genPassword len = runRand . mkPassword len -- |Plural version of genPassword. Generates an infinite list of passwords@@ -60,12 +59,13 @@ -- myGenPasswords :: IO ([String], StdGen) -- myGenPasswords = (\(ls, g) -> (ls, g) `liftM` genPasswords 10 10 genOptions `liftM` getStdGen -- @-genPasswords :: RandomGen g- => Int -- ^ password length- -> Int -- ^ number of passwords- -> GenOptions -- ^ options- -> g -- ^ random generator- -> ([String], g)+genPasswords+ :: RandomGen g+ => Int -- ^ password length+ -> Int -- ^ number of passwords+ -> GenOptions -- ^ options+ -> g -- ^ random generator+ -> ([String], g) genPasswords len n = runRand . mkPasswords len n -- |Generate a password using the generator g, returning the result.@@ -75,11 +75,12 @@ -- myNewPassword :: IO String -- myNewPassword = newPassword 10 genOptions \`liftM\` getStdGen -- @-newPassword :: RandomGen g- => Int -- ^ password length- -> GenOptions -- ^ options- -> g -- ^ random generator- -> String+newPassword+ :: RandomGen g+ => Int -- ^ password length+ -> GenOptions -- ^ options+ -> g -- ^ random generator+ -> String newPassword len = evalRand . mkPassword len -- |Plural version of newPassword. Generates an infinite list of passwords@@ -90,12 +91,13 @@ -- myNewPasswords :: IO [String] -- myNewPasswords = genPasswords 10 10 genOptions `liftM` getStdGen -- @-newPasswords :: RandomGen g- => Int -- ^ password length- -> Int -- ^ number of passwords- -> GenOptions -- ^ options- -> g -- ^ random generator- -> [String]+newPasswords+ :: RandomGen g+ => Int -- ^ password length+ -> Int -- ^ number of passwords+ -> GenOptions -- ^ options+ -> g -- ^ random generator+ -> [String] newPasswords len n = evalRand . mkPasswords len n -- |Generate a password using the MonadRandom m. MonadRandom is exposed here@@ -106,16 +108,20 @@ -- myPassword :: IO String -- myPassword = evalRand (mkPassword 10 genOptions) \`liftM\` getStdGen -- @-mkPassword :: MonadRandom m- => Int -- ^ password length- -> GenOptions -- ^ options- -> m String+mkPassword+ :: MonadRandom m+ => Int -- ^ password length+ -> GenOptions -- ^ options+ -> m String mkPassword len opts = do- f2 <- reverse `liftM` first2 opts- if len > 2- then reverse `liftM` lastN opts (len - 2) f2- else return . reverse . take len $ f2+ f2 <- first2+ let f2' = reverse f2 + pass <- if len > 2 then lastN (len - 2) f2' else return (take len f2')+ let pass' = reverse pass++ if genCapitals opts then capitalizeR len pass' else return pass'+ -- |Plural version of mkPassword. Generate an infinite list of passwords using -- the MonadRandom m. MonadRandom is exposed here for extra control. --@@ -124,11 +130,12 @@ -- myMkPasswords :: IO [String] -- myMkPasswords = evalRand (mkPasswords 10 20 genOptions) \`liftM\` getStdGen -- @-mkPasswords :: MonadRandom m- => Int -- ^ password length- -> Int -- ^ number of passwords- -> GenOptions -- ^ options- -> m [String]+mkPasswords+ :: MonadRandom m+ => Int -- ^ password length+ -> Int -- ^ number of passwords+ -> GenOptions -- ^ options+ -> m [String] mkPasswords len n = replicateM n . mkPassword len -- * Random passphrase generators@@ -142,13 +149,14 @@ -- myGenPassphrase :: IO (String, StdGen) -- myGenPassphrase = genPassword 10 6 10 genOptions \`liftM\` getStdGen -- @-genPassphrase :: RandomGen g- => Int -- ^ number of words- -> Int -- ^ minimum word length- -> Int -- ^ maximum word length- -> GenOptions -- ^ options- -> g -- ^ random generator- -> ([String], g)+genPassphrase+ :: RandomGen g+ => Int -- ^ number of words+ -> Int -- ^ minimum word length+ -> Int -- ^ maximum word length+ -> GenOptions -- ^ options+ -> g -- ^ random generator+ -> ([String], g) genPassphrase n min max = runRand . mkPassphrase n min max -- |Generate a passphrase using the generator g, returning the result.@@ -159,13 +167,14 @@ -- myNewPassphrase :: IO String -- myNewPassphrase = newPassphrase 10 6 12 \`liftM\` getStdGen -- @-newPassphrase :: RandomGen g- => Int -- ^ number of words- -> Int -- ^ minimum word length- -> Int -- ^ maximum word length- -> GenOptions -- ^ options- -> g -- ^ random generator- -> [String]+newPassphrase+ :: RandomGen g+ => Int -- ^ number of words+ -> Int -- ^ minimum word length+ -> Int -- ^ maximum word length+ -> GenOptions -- ^ options+ -> g -- ^ random generator+ -> [String] newPassphrase n min max = evalRand . mkPassphrase n min max -- |Generate a finite number of words of random length (between @min@ and @max@ chars)@@ -176,51 +185,52 @@ -- myPassphrase :: IO String -- myPassphrase = evalRand (mkPassphrase 10 6 12) \`liftM\` getStdGen -- @-mkPassphrase :: MonadRandom m- => Int -- ^ number of words- -> Int -- ^ minimum word length- -> Int -- ^ maximum word length- -> GenOptions -- ^ options- -> m [String]-mkPassphrase n min max opts = replicateM n $- getRandomR (min, max) >>= flip mkPassword opts+mkPassphrase+ :: MonadRandom m+ => Int -- ^ number of words+ -> Int -- ^ minimum word length+ -> Int -- ^ maximum word length+ -> GenOptions -- ^ options+ -> m [String]+mkPassphrase n min max opts =+ replicateM n $ getRandomR (min, max) >>= flip mkPassword opts -- * Internal -- |Generate two random characters. Uses 'Elocrypt.Trigraph.trigragh' -- to generate a weighted list.-first2 :: MonadRandom m - => GenOptions- -> m String-first2 opts = fromList (map toWeight frequencies) >>= mapM (capitalizeR caps)+first2 :: MonadRandom m => m String+first2 = fromList (map toWeight frequencies) where toWeight (s, w) = (s, sum w)- GenOptions{genCapitals=caps} = opts -- |Generate the last n characters using previous two characters -- and their 'Elocrypt.Trigraph.trigraph'-lastN :: MonadRandom m => GenOptions -> Int -> String -> m String-lastN _ 0 ls = return ls-lastN opts len ls = next opts ls >>= lastN opts (len - 1) . (:ls)+lastN :: MonadRandom m => Int -> String -> m String+lastN 0 ls = return ls+lastN len ls = next ls >>= lastN (len - 1) . (: ls) -- |Generate a random character based on the previous two characters and -- their 'Elocrypt.Trigraph.trigraph'-next :: MonadRandom m - => GenOptions -- ^ options- -> String -- ^ the prefix- -> m Char-next opts prefix = nextLetter prefix >>= capitalizeR caps- where GenOptions{genCapitals=caps} = opts+next+ :: MonadRandom m+ => String -- ^ the prefix+ -> m Char+next = fromList . fromJust . findWeights . reverse . take 2 --- |Randomly choose a letter from the trigraph-nextLetter :: MonadRandom m- => String -- ^ the prefix- -> m Char-nextLetter = fromList . fromJust . findWeights . reverse . take 2+-- |Randomly capitalize at least 1 character. Additional characters capitalize+-- at a probability of 1/12+capitalizeR :: MonadRandom m => Int -> String -> m String+capitalizeR len s = mapM capitalize s >>= capitalize1 len+ where capitalize ch = fromList [(ch, 12), (toUpper ch, 1)] --- |Randomly capitalize a character 10% of the time-capitalizeR :: MonadRandom m- => Bool -- ^ Whether to do the capitalization- -> Char -- ^ The character to capitalize- -> m Char-capitalizeR cap c | cap = fromList [(c, 6), (toUpper c, 1)]- | otherwise = return c+-- |Randomly capitalize 1 character+capitalize1+ :: MonadRandom m+ => Int -- ^ length+ -> String -- ^ the string to capitalize+ -> m String+capitalize1 len s = capitalize1' <$> getRandomR (0, len - 1)+ where+ capitalize1' pos =+ let (prefix, ch : suffix) = splitAt (pos - 1) s+ in prefix ++ (toUpper ch : suffix)
test/IntegTest/Elocrypt/PassphraseTest.hs view
@@ -103,7 +103,7 @@ response <- readHandle out return $- cover (minLen > 80) 20 "long" $ + cover 20 (minLen > 80) "long" $ all (>=1) . map (length . words) . lines $ response -- |Prints capitals when specified@@ -118,4 +118,4 @@ let phrases = lines response return $- cover (any (any isUpper) phrases) 80 "has caps" True+ cover 80 (any (any isUpper) phrases) "has caps" True
test/IntegTest/Elocrypt/PasswordTest.hs view
@@ -54,7 +54,7 @@ response <- readHandle out return $- cover (len' > 80) 30 "long" $+ cover 30 (len' > 80) "long" $ all (>=1) . map (length . words) . lines $ response -- |Prints the specified number of passwords@@ -95,4 +95,4 @@ let passes = words response return $- cover (any (any isUpper) passes) 80 "has caps" True+ cover 80 (any (any isUpper) passes) "has caps" True
test/Test/ElocryptTest.hs view
@@ -22,116 +22,105 @@ -- |Passwords have the specified length prop_newPasswordHasLen :: Positive Int -> GenOptions -> StdGen -> Bool-prop_newPasswordHasLen (Positive len) opts gen- = length pass == len+prop_newPasswordHasLen (Positive len) opts gen = length pass == len where pass = newPassword len opts gen -- |Passwords are all lowercase when caps is false prop_newPasswordIsLower :: Positive Int -> StdGen -> Bool-prop_newPasswordIsLower (Positive len) gen- = all isLower pass- where pass = newPassword len opts gen- opts = genOptions{genCapitals = False}+prop_newPasswordIsLower (Positive len) gen = all isLower pass+ where+ pass = newPassword len opts gen+ opts = genOptions { genCapitals = False } --- |Passwords with caps generates caps-prop_newPasswordHasCaps :: Positive Int -> StdGen -> Property-prop_newPasswordHasCaps (Positive len) gen- = cover (any isUpper pass) 50 "has caps" $- any isLower pass- where pass = newPassword (len + 2) opts gen- opts = genOptions{genCapitals = True}+-- |Passwords with caps have at least one capital+prop_newPasswordHasCaps :: Positive Int -> StdGen -> Bool+prop_newPasswordHasCaps (Positive len) gen = any isUpper pass+ where+ pass = newPassword (len + 2) opts gen+ opts = genOptions { genCapitals = True } +-- |Most passwords have more than one capital+prop_newPasswordHasMultipleCaps :: Positive Int -> StdGen -> Property+prop_newPasswordHasMultipleCaps (Positive len) gen =+ cover 50 (length (filter isUpper pass) > 1) "has multiple caps"+ $ any isLower pass+ where+ pass = newPassword (len + 2) opts gen+ opts = genOptions { genCapitals = True }+ -- |Third and each successive character is taken from the trigraph-prop_3rdCharHasPositiveFrequency - :: Positive Int - -> GenOptions - -> StdGen - -> Property-prop_3rdCharHasPositiveFrequency (Positive len) opts gen- = conjoin $ loop pass- where pass = newPassword (len+2) opts gen- loop (f:s:t:xs) = thirdCharIsInTrigraph [f, s, t] : loop (s:t:xs)- loop _ = []+prop_3rdCharHasPositiveFrequency+ :: Positive Int -> GenOptions -> StdGen -> Property+prop_3rdCharHasPositiveFrequency (Positive len) opts gen = conjoin $ loop pass+ where+ pass = newPassword (len + 2) opts gen+ loop (f : s : t : xs) = thirdCharIsInTrigraph [f, s, t] : loop (s : t : xs)+ loop _ = [] thirdCharIsInTrigraph :: String -> Property-thirdCharIsInTrigraph pass- = counterexample failMsg $ property (t `elem` candidates)- where (f:s:t:_) = map toLower pass- candidates = map fst . filter ((0/=) . snd) $ frequencies- frequencies = zip ['a'..'z'] .- defaultFrequencies .- fromJust .- findFrequency $ [f, s]+thirdCharIsInTrigraph pass = counterexample failMsg+ $ property (t `elem` candidates)+ where+ (f : s : t : _) = map toLower pass+ candidates = map fst . filter ((0 /=) . snd) $ frequencies+ frequencies =+ zip ['a' .. 'z'] . defaultFrequencies . fromJust . findFrequency $ [f, s] - failMsg = t : " not in [" ++ candidates ++ "]"+ failMsg = t : " not in [" ++ candidates ++ "]" -- First 2 characters have total non-zero frequencies-prop_first2HavePositiveFrequencies - :: Positive Int- -> GenOptions- -> StdGen- -> Property-prop_first2HavePositiveFrequencies (Positive len) opts gen- = counterexample failMsg $ property (sum frequencies > 0)- where pass = newPassword (len+1) opts gen- (f:s:_) = map toLower pass- frequencies = zipWith (curry snd) ['a'..'z'] .- fromJust .- findFrequency $ [f, s]- failMsg = "no candidates for '" ++ [f, s] ++ "'"+prop_first2HavePositiveFrequencies+ :: Positive Int -> GenOptions -> StdGen -> Property+prop_first2HavePositiveFrequencies (Positive len) opts gen =+ counterexample failMsg $ property (sum frequencies > 0)+ where+ pass = newPassword (len + 1) opts gen+ (f : s : _) = map toLower pass+ frequencies =+ zipWith (curry snd) ['a' .. 'z'] . fromJust . findFrequency $ [f, s]+ failMsg = "no candidates for '" ++ [f, s] ++ "'" -- (len . fst) (genPasswords _ x _ _) = x prop_newPasswordsHasLen- :: Positive Int- -> Positive Int- -> GenOptions- -> StdGen- -> Property-prop_newPasswordsHasLen (Positive len) (Positive num) opts gen- = counterexample failMsg $ property (length passes == num)- where passes = newPasswords len num opts gen- failMsg = show (length passes) ++ " /= " ++ show num+ :: Positive Int -> Positive Int -> GenOptions -> StdGen -> Property+prop_newPasswordsHasLen (Positive len) (Positive num) opts gen =+ counterexample failMsg $ property (length passes == num)+ where+ passes = newPasswords len num opts gen+ failMsg = show (length passes) ++ " /= " ++ show num -- (all ((x==) . length) . fst) (genPasswords x _ _ _) = x prop_newPasswordsAllHaveLen- :: Positive Int- -> Positive Int- -> GenOptions- -> StdGen- -> Bool-prop_newPasswordsAllHaveLen (Positive len) (Positive num) opts gen- = all ((len==) . length) passes+ :: Positive Int -> Positive Int -> GenOptions -> StdGen -> Bool+prop_newPasswordsAllHaveLen (Positive len) (Positive num) opts gen = all+ ((len ==) . length)+ passes where passes = newPasswords len num opts gen -- |Given the same generator, newPassword and genPassword generates the -- same password. prop_newPasswordMatchesGenPassword- :: Positive Int- -> GenOptions- -> StdGen- -> Property-prop_newPasswordMatchesGenPassword (Positive len) opts gen- = counterexample failMsg $ property (pass == pass')- where (pass, _) = genPassword len opts gen- pass' = newPassword len opts gen- failMsg = show pass ++ " /= " ++ show pass'+ :: Positive Int -> GenOptions -> StdGen -> Property+prop_newPasswordMatchesGenPassword (Positive len) opts gen =+ counterexample failMsg $ property (pass == pass')+ where+ (pass, _) = genPassword len opts gen+ pass' = newPassword len opts gen+ failMsg = show pass ++ " /= " ++ show pass' -- |Given the same generator, newPasswords and genPasswords genereates -- the same passwords. prop_newPasswordsMatchesGenPasswords- :: Positive Int- -> Positive Int- -> GenOptions- -> StdGen- -> Property-prop_newPasswordsMatchesGenPasswords (Positive len) (Positive num) opts gen- = counterexample failMsg $ property (passes == passes')- where (passes, _) = genPasswords len num opts gen- passes' = newPasswords len num opts gen- failMsg = show passes ++ " /= " ++ show passes'+ :: Positive Int -> Positive Int -> GenOptions -> StdGen -> Property+prop_newPasswordsMatchesGenPasswords (Positive len) (Positive num) opts gen =+ counterexample failMsg $ property (passes == passes')+ where+ (passes, _) = genPasswords len num opts gen+ passes' = newPasswords len num opts gen+ failMsg = show passes ++ " /= " ++ show passes' -- |newPassphrase generates n words-prop_newPassphraseHasLen +prop_newPassphraseHasLen :: Positive Int -> Positive Int -> Positive Int@@ -140,8 +129,9 @@ -> Property prop_newPassphraseHasLen (Positive len) (Positive min) (Positive max) opts gen = counterexample failMsg $ property (length words == len)- where words = newPassphrase len min max opts gen- failMsg = show len ++ " /= length " ++ show words+ where+ words = newPassphrase len min max opts gen+ failMsg = show len ++ " /= length " ++ show words -- |newPassphrase generates words in the allowed range prop_newPassphraseWordsHaveLen@@ -151,11 +141,11 @@ -> GenOptions -> StdGen -> Bool-prop_newPassphraseWordsHaveLen- (Positive len) (Positive min) (Positive max) opts gen+prop_newPassphraseWordsHaveLen (Positive len) (Positive min) (Positive max) opts gen = all (\w -> length w >= min && length w <= max') words- where words = newPassphrase len min max' opts gen- max' = min + max+ where+ words = newPassphrase len min max' opts gen+ max' = min + max -- |Given the same generator, genPassphrase and newPassphrase generates -- the same passphrase@@ -166,10 +156,10 @@ -> GenOptions -> StdGen -> Property-prop_genPassphraseMatchesNewPassphrase - (Positive len) (Positive min) (Positive max) opts gen +prop_genPassphraseMatchesNewPassphrase (Positive len) (Positive min) (Positive max) opts gen = counterexample failMsg $ property (words == words')- where (words, _) = genPassphrase len min max' opts gen- words' = newPassphrase len min max' opts gen- max' = min + max- failMsg = show words ++ " /= " ++ show words'+ where+ (words, _) = genPassphrase len min max' opts gen+ words' = newPassphrase len min max' opts gen+ max' = min + max+ failMsg = show words ++ " /= " ++ show words'