packages feed

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 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'