elocrypt 0.4.1 → 0.6.0
raw patch · 6 files changed
+212/−181 lines, 6 filesdep ~basePVP ok
version bump matches the API change (PVP)
Dependency ranges changed: base
API changes (from Hackage documentation)
+ Data.Elocrypt: mkPassphrase :: Int -> IO [String]
- Data.Elocrypt: genPassword :: RandomGen g => Int -> g -> (String, g)
+ Data.Elocrypt: genPassword :: RandomGen g => Int -> Bool -> g -> (String, g)
- Data.Elocrypt: genPasswords :: RandomGen g => Int -> g -> ([String], g)
+ Data.Elocrypt: genPasswords :: RandomGen g => Int -> Bool -> g -> ([String], g)
- Data.Elocrypt: mkPassword :: MonadRandom m => Int -> m String
+ Data.Elocrypt: mkPassword :: MonadRandom m => Int -> Bool -> m String
- Data.Elocrypt: mkPasswords :: MonadRandom m => Int -> m [String]
+ Data.Elocrypt: mkPasswords :: MonadRandom m => Int -> Bool -> m [String]
- Data.Elocrypt: newPassword :: RandomGen g => Int -> g -> String
+ Data.Elocrypt: newPassword :: RandomGen g => Int -> Bool -> g -> String
- Data.Elocrypt: newPasswords :: RandomGen g => Int -> g -> [String]
+ Data.Elocrypt: newPasswords :: RandomGen g => Int -> Bool -> g -> [String]
Files
- cli-test/CliTests.hs +130/−0
- cli-test/Tests.hs +0/−130
- elocrypt.cabal +11/−7
- src/cli/Main.hs +2/−2
- src/lib/Data/Elocrypt.hs +48/−24
- test/Test/ElocryptTest.hs +21/−18
+ cli-test/CliTests.hs view
@@ -0,0 +1,130 @@+{-# LANGUAGE TemplateHaskell #-}+module Main where++import Data.Functor+import Data.List+import Data.Maybe+import Control.Monad+import Text.Printf++import Test.Proctest+import Test.Proctest.Assertions+import Test.QuickCheck+import Test.QuickCheck.Property+import Test.Tasty hiding (Timeout)+import Test.Tasty.QuickCheck (QuickCheckTests(..), testProperty)+import Test.Tasty.TH++import Test.Elocrypt.Instances++main :: IO ()+main = defaultMain (options tests)++options :: TestTree -> TestTree+options = localOption (QuickCheckTests 10)++tests :: TestTree+tests = testGroup "CLI Tests" [testGroup']++testGroup' = $(testGroupGenerator)++newtype CliArgs = CliArgs { getArgs :: [String] }+ deriving Eq++instance Show CliArgs where+ show = concat . intersperse " " . getArgs++instance Arbitrary CliArgs where+ arbitrary = do+ len <- arbitrary `suchThat` (>0) `suchThat` (<=40) :: Gen Int+ num <- arbitrary `suchThat` (>2) `suchThat` (<=20) :: Gen Int+ args <- sublistOf ["-n %d" `printf` num,+ show len]+ return (CliArgs args)+ +prop_shouldPrintPasswordsWithLength :: CliArgs -> Property+prop_shouldPrintPasswordsWithLength (CliArgs args)+ = isJust (getPosParam args) ==>+ ioProperty $ do+ (in', out', err', p) <- run' "dist/build/elocrypt/elocrypt" args+ response <- readHandle out'++ let len = read . fromJust . getPosParam $ args+ words' = words response++ return (all ((==) len . length) words')++prop_shouldPrintNumberPasswords :: CliArgs -> Property+prop_shouldPrintNumberPasswords (CliArgs args)+ = isJust (getArg "-n" args) ==>+ ioProperty $ do+ (in', out', err', p) <- run' "dist/build/elocrypt/elocrypt" args+ response <- readHandle out'++ let number = read . fromJust . getArg "-n" $ args+ words' = words response+ + return (number == length words')++prop_shouldPrintMultPasswordsPerLine :: CliArgs -> Property+prop_shouldPrintMultPasswordsPerLine (CliArgs args)+ = (read . fromMaybe "8" . getPosParam $ args) <= 38 ==>+ ioProperty $ do+ (in', out', err', p) <- run' "dist/build/elocrypt/elocrypt" args+ response <- readHandle out'+ return . all (>1) . tail . reverse . map length . map words . lines $ response++prop_shouldPrintDefaultMultPasswordsPerLine :: CliArgs -> Property+prop_shouldPrintDefaultMultPasswordsPerLine (CliArgs args)+ = isNothing (getArg "-n" args) ==>+ (read . fromMaybe "8" . getPosParam $ args) <= 38 ==>+ ioProperty $ do+ (in', out', err', p) <- run' "dist/build/elocrypt/elocrypt" args+ response <- readHandle out'+ return . all (>1) . tail . reverse . map length . map words . lines $ response++prop_shouldNotPrintZeroPasswords :: CliArgs -> Property+prop_shouldNotPrintZeroPasswords (CliArgs args)+ = isNothing (getPosParam args) ==>+ ioProperty $ do+ let args' = args ++ ["0"]+ + (in', out', err', p) <- run' "dist/build/elocrypt/elocrypt" args'+ response <- readHandle out'+ return (response == "")++prop_shouldPrintLongPasswords :: GreaterThan79 Int -> Property+prop_shouldPrintLongPasswords (GT79 a)+ = ioProperty $ do+ (in', out', err', p) <- run' "dist/build/elocrypt/elocrypt" [show a]+ response <- readHandle out'+ return . all (==1) . map length . map words . lines $ response+++-- Utility functions+run' :: FilePath -> [String] -> IO (Handle, Handle, Handle, ProcessHandle)+run' exe args = do+ res@(in', out', err', p) <- run exe args+ sleep'+ assertExitedSuccess (seconds 2) p+ return res++readHandle :: Handle -> IO String+readHandle = (<$>) asUtf8Str . waitOutput (seconds 2) 5000++assertExitedSuccess :: Timeout -> ProcessHandle -> IO Bool+assertExitedSuccess t = liftM (== ExitSuccess) . assertExitedTimeout t++assertExitedFailure :: Timeout -> ProcessHandle -> IO Bool+assertExitedFailure t = liftM not . assertExitedSuccess t++sleep' :: IO ()+sleep' = sleep (seconds 0.0001)++getArg :: String -> [String] -> Maybe String+getArg prefix args = (tail . dropWhile (not . elem')) `liftM` arg+ where arg = find (isPrefixOf prefix) args+ elem' = (flip elem) [' ', '=']++getPosParam :: [String] -> Maybe String+getPosParam = find $ (/= '-') . head
− cli-test/Tests.hs
@@ -1,130 +0,0 @@-{-# LANGUAGE TemplateHaskell #-}-module Main where--import Data.Functor-import Data.List-import Data.Maybe-import Control.Monad-import Text.Printf--import Test.Proctest-import Test.Proctest.Assertions-import Test.QuickCheck-import Test.QuickCheck.Property-import Test.Tasty hiding (Timeout)-import Test.Tasty.QuickCheck (QuickCheckTests(..), testProperty)-import Test.Tasty.TH--import Test.Elocrypt.Instances--main :: IO ()-main = defaultMain (options tests)--options :: TestTree -> TestTree-options = localOption (QuickCheckTests 10)--tests :: TestTree-tests = testGroup "CLI Tests" [testGroup']--testGroup' = $(testGroupGenerator)--newtype CliArgs = CliArgs { getArgs :: [String] }- deriving Eq--instance Show CliArgs where- show = concat . intersperse " " . getArgs--instance Arbitrary CliArgs where- arbitrary = do- len <- arbitrary `suchThat` (>0) `suchThat` (<=40) :: Gen Int- num <- arbitrary `suchThat` (>2) `suchThat` (<=20) :: Gen Int- args <- sublistOf ["-n %d" `printf` num,- show len]- return (CliArgs args)- -prop_shouldPrintPasswordsWithLength :: CliArgs -> Property-prop_shouldPrintPasswordsWithLength (CliArgs args)- = isJust (getPosParam args) ==>- ioProperty $ do- (in', out', err', p) <- run' "dist/build/elocrypt/elocrypt" args- response <- readHandle out'-- let len = read . fromJust . getPosParam $ args- words' = words response-- return (all ((==) len . length) words')--prop_shouldPrintNumberPasswords :: CliArgs -> Property-prop_shouldPrintNumberPasswords (CliArgs args)- = isJust (getArg "-n" args) ==>- ioProperty $ do- (in', out', err', p) <- run' "dist/build/elocrypt/elocrypt" args- response <- readHandle out'-- let number = read . fromJust . getArg "-n" $ args- words' = words response- - return (number == length words')--prop_shouldPrintMultPasswordsPerLine :: CliArgs -> Property-prop_shouldPrintMultPasswordsPerLine (CliArgs args)- = (read . fromMaybe "8" . getPosParam $ args) <= 38 ==>- ioProperty $ do- (in', out', err', p) <- run' "dist/build/elocrypt/elocrypt" args- response <- readHandle out'- return . all (>1) . tail . reverse . map length . map words . lines $ response--prop_shouldPrintDefaultMultPasswordsPerLine :: CliArgs -> Property-prop_shouldPrintDefaultMultPasswordsPerLine (CliArgs args)- = isNothing (getArg "-n" args) ==>- (read . fromMaybe "8" . getPosParam $ args) <= 38 ==>- ioProperty $ do- (in', out', err', p) <- run' "dist/build/elocrypt/elocrypt" args- response <- readHandle out'- return . all (>1) . tail . reverse . map length . map words . lines $ response--prop_shouldNotPrintZeroPasswords :: CliArgs -> Property-prop_shouldNotPrintZeroPasswords (CliArgs args)- = isNothing (getPosParam args) ==>- ioProperty $ do- let args' = args ++ ["0"]- - (in', out', err', p) <- run' "dist/build/elocrypt/elocrypt" args'- response <- readHandle out'- return (response == "")--prop_shouldPrintLongPasswords :: GreaterThan79 Int -> Property-prop_shouldPrintLongPasswords (GT79 a)- = ioProperty $ do- (in', out', err', p) <- run' "dist/build/elocrypt/elocrypt" [show a]- response <- readHandle out'- return . all (==1) . map length . map words . lines $ response----- Utility functions-run' :: FilePath -> [String] -> IO (Handle, Handle, Handle, ProcessHandle)-run' exe args = do- res@(in', out', err', p) <- run exe args- sleep'- assertExitedSuccess (seconds 2) p- return res--readHandle :: Handle -> IO String-readHandle = (<$>) asUtf8Str . waitOutput (seconds 2) 5000--assertExitedSuccess :: Timeout -> ProcessHandle -> IO Bool-assertExitedSuccess t = liftM (== ExitSuccess) . assertExitedTimeout t--assertExitedFailure :: Timeout -> ProcessHandle -> IO Bool-assertExitedFailure t = liftM not . assertExitedSuccess t--sleep' :: IO ()-sleep' = sleep (seconds 0.0001)--getArg :: String -> [String] -> Maybe String-getArg prefix args = (tail . dropWhile (not . elem')) `liftM` arg- where arg = find (isPrefixOf prefix) args- elem' = (flip elem) [' ', '=']--getPosParam :: [String] -> Maybe String-getPosParam = find $ (/= '-') . head
elocrypt.cabal view
@@ -1,5 +1,5 @@ name: elocrypt-version: 0.4.1+version: 0.6.0 synopsis: Generate easy-to-remember, hard-to-guess passwords homepage: https://www.github.com/sgillespie/elocrypt license: OtherLicense@@ -26,21 +26,21 @@ source-repository this type: git location: https://github.com/sgillespie/elocrypt.git- tag: v0.4.1+ tag: v0.6.0 library exposed-modules: Data.Elocrypt, Data.Elocrypt.Trigraph -- other-modules: -- other-extensions: - build-depends: base >=4.7 && <4.9,+ build-depends: base >=4.7 && <5, MonadRandom, random hs-source-dirs: src/lib default-language: Haskell2010 executable elocrypt- build-depends: base >= 4.7 && <4.9,+ build-depends: base >= 4.7 && <5, elocrypt, random default-language: Haskell2010@@ -48,7 +48,7 @@ main-is: Main.hs test-suite test- build-depends: base >= 4.7 && <4.9,+ build-depends: base >= 4.7 && <5, elocrypt, MonadRandom, QuickCheck,@@ -59,10 +59,13 @@ default-language: Haskell2010 hs-source-dirs: test main-is: Tests.hs+ other-modules: Test.Elocrypt.Instances,+ Test.Elocrypt.TrigraphTest,+ Test.ElocryptTest type: exitcode-stdio-1.0 test-suite ui-test- build-depends: base >= 4.7 && <4.9,+ build-depends: base >= 4.7 && <5, elocrypt, proctest, MonadRandom,@@ -74,5 +77,6 @@ default-language: Haskell2010 hs-source-dirs: cli-test, test- main-is: Tests.hs+ main-is: CliTests.hs+ other-modules: Test.Elocrypt.Instances type: exitcode-stdio-1.0
src/cli/Main.hs view
@@ -11,7 +11,7 @@ import Data.Elocrypt -version = "elocrypt 0.4.1"+version = "elocrypt 0.6.0" termLen = 80 termHeight = 10 @@ -35,7 +35,7 @@ passwords :: RandomGen g => Options -> g -> String passwords opts gen = format . group' cols $ ps- where ps = take num . newPasswords (optLength opts) $ gen+ where ps = take num . newPasswords (optLength opts) False $ gen -- TODO[sgillespie]: Add caps format = concat . intersperse "\n" . map (concat . intersperse " ") cols = if optLength opts <= termLen - 2 then termLen `div` (optLength opts + 2)
src/lib/Data/Elocrypt.hs view
@@ -14,9 +14,14 @@ import Control.Monad import Control.Monad.Random hiding (next)+import Data.Bool import Data.Maybe import System.Random hiding (next) +-- |Generate a finite number of words of random length (between 3 and 9 chars).+mkPassphrase :: Int -> IO [String]+mkPassphrase n = sequence $ replicate n $ randomRIO (3, 9) >>= flip mkPassword False+ -- * Random password generators -- |Generate a password using the generator g, returning the result and the@@ -25,13 +30,14 @@ -- @ -- -- Generate a password of length 10 using the system generator -- myGenPassword :: IO (String, StdGen)--- myGenPassword = genPassword 10 \`liftM\` getStdGen+-- myGenPassword = genPassword 10 True \`liftM\` getStdGen -- @ genPassword :: RandomGen g- => Int -- ^ password length- -> g -- ^ random generator+ => Int -- ^ password length+ -> Bool -- ^ include capitals?+ -> g -- ^ random generator -> (String, g)-genPassword = runRand . mkPassword+genPassword len = runRand . mkPassword len -- |Plural version of genPassword. Generates an infinite list of passwords -- using the generator g, returning the result and the updated generator.@@ -39,26 +45,28 @@ -- @ -- -- Generate 10 passwords of length 10 using the system generator -- myGenPasswords :: IO ([String], StdGen)--- myGenPasswords = (\(ls, g) -> (take 10 ls, g) `liftM` genPasswords 10 `liftM` getStdGen+-- myGenPasswords = (\(ls, g) -> (take 10 ls, g) `liftM` genPasswords 10 True `liftM` getStdGen -- @ genPasswords :: RandomGen g- => Int -- ^ password length- -> g -- ^ random generator+ => Int -- ^ password length+ -> Bool -- ^ include capitals?+ -> g -- ^ random generator -> ([String], g)-genPasswords = runRand . mkPasswords+genPasswords len = runRand . mkPasswords len -- |Generate a password using the generator g, returning the result. -- -- @ -- -- Generate a password of length 10 using the system generator -- myNewPassword :: IO String--- myNewPassword = newPassword 10 \`liftM\` getStdGen+-- myNewPassword = newPassword 10 True \`liftM\` getStdGen -- @ newPassword :: RandomGen g- => Int -- ^ password length- -> g -- ^ random generator+ => Int -- ^ password length+ -> Bool -- ^ include capitals?+ -> g -- ^ random generator -> String-newPassword = evalRand . mkPassword+newPassword len = evalRand . mkPassword len -- |Plural version of newPassword. Generates an infinite list of passwords -- using the generator g, returning the result@@ -66,13 +74,14 @@ -- @ -- -- Generate 10 passwords of length 10 using the system generator -- myNewPasswords :: IO [String]--- myNewPasswords = (take 10 . genPasswords 10) `liftM` getStdGen+-- myNewPasswords = (take 10 . genPasswords 10 True) `liftM` getStdGen -- @ newPasswords :: RandomGen g- => Int -- ^ password length- -> g -- ^ random generator+ => Int -- ^ password length+ -> Bool -- ^ include capitals?+ -> g -- ^ random generator -> [String]-newPasswords = evalRand . mkPasswords+newPasswords len = evalRand . mkPasswords len -- |Generate a password using the MonadRandom m. MonadRandom is exposed here -- for extra control.@@ -80,12 +89,13 @@ -- @ -- -- Generate a password of length 10 using the system generator -- myPasswords :: IO String--- myPasswords = evalRand (mkPassword 10) \`liftM\` getStdGen+-- myPasswords = evalRand (mkPassword 10 True) \`liftM\` getStdGen -- @ mkPassword :: MonadRandom m- => Int -- ^ password length+ => Int -- ^ password length+ -> Bool -- ^ include capitals? -> m String-mkPassword len = do+mkPassword len caps = do f2 <- reverse `liftM` first2 if len > 2 then reverse `liftM` lastN (len - 2) f2@@ -97,13 +107,14 @@ -- @ -- -- Generate an infinite list of passwords of length 10 using the system generator -- myMkPasswords :: IO [String]--- myMkPasswords = evalRand (mkPasswords 10) \`liftM\` getStdGen+-- myMkPasswords = evalRand (mkPasswords 10 True) \`liftM\` getStdGen -- @ mkPasswords :: MonadRandom m- => Int -- ^ password length+ => Int -- ^ password length+ -> Bool -- ^ include capitals? -> m [String]-mkPasswords = sequence . repeat . mkPassword- +mkPasswords len = sequence . repeat . mkPassword len+ -- |The alphabet we sample random values from alphabet :: [Char] alphabet = ['a'..'z']@@ -119,10 +130,23 @@ -- |Generate a random character based on the previous two characters and -- their 'Elocrypt.Trigraph.trigraph' next :: MonadRandom m => String -> m Char-next (x:xs:_) = fromList . zip ['a'..'z'] . fromJust . findFrequency $ [xs,x]+next (x:xs:_) = fromList+ . zip ['a'..'z']+ . defaultFreqs+ . fromJust+ . findFrequency $ [xs, x] + -- Fix frequencies if they are all 0, since MonadRandom prohibits this.+ -- Use all 1s in this case to give every item an equal weight.+ where defaultFreqs :: [Rational] -> [Rational]+ defaultFreqs f = bool (replicate 26 1) f (any (>0) f)++-- This shouldn't ever happen+next _ = undefined+ -- |Generate the last n characters using previous two characters -- and their 'Elocrypt.Trigraph.trigraph' lastN :: MonadRandom m => Int -> String -> m String lastN 0 ls = return ls lastN len ls = next ls >>= lastN (len - 1) . (:ls)+
test/Test/ElocryptTest.hs view
@@ -6,6 +6,7 @@ import Data.Maybe import System.Random hiding (next) import Test.QuickCheck+import Test.Tasty import Test.Tasty.QuickCheck (testProperty) import Test.Tasty.TH @@ -13,33 +14,35 @@ import Data.Elocrypt.Trigraph import Test.Elocrypt.Instances +tests :: TestTree tests = $(testGroupGenerator) -prop_genPasswordShouldBeUnique :: GreaterThan2 Int -> StdGen -> Bool-prop_genPasswordShouldBeUnique (GT2 len) gen- = p /= fst (genPassword len gen')- where (p, gen') = genPassword len gen+prop_genPasswordShouldBeUnique :: GreaterThan2 Int -> Bool -> StdGen -> Bool+prop_genPasswordShouldBeUnique (GT2 len) caps gen+ = p /= fst (genPassword len caps gen')+ where (p, gen') = genPassword len caps gen -prop_genPasswordsShouldBeUnique :: GreaterThan2 Int -> StdGen -> Bool-prop_genPasswordsShouldBeUnique (GT2 len) gen+prop_genPasswordsShouldBeUnique :: GreaterThan2 Int -> Bool -> StdGen -> Bool+prop_genPasswordsShouldBeUnique (GT2 len) caps gen = p /= ps- where (p:ps:_) = fst (genPasswords len gen)+ where (p:ps:_) = fst (genPasswords len caps gen) -prop_newPasswordShouldBeLength :: Int -> StdGen -> Property-prop_newPasswordShouldBeLength len gen = len > 0 ==> length (newPassword len gen) == len+prop_newPasswordShouldBeLength :: Int -> Bool -> StdGen -> Property+prop_newPasswordShouldBeLength len caps gen = len > 0 ==>+ length (newPassword len caps gen) == len -prop_newPasswordShouldConsistOfAlphabet :: Int -> StdGen -> Bool-prop_newPasswordShouldConsistOfAlphabet len gen- = all ((flip elem) alphabet) (newPassword len gen)+prop_newPasswordShouldConsistOfAlphabet :: Int -> Bool -> StdGen -> Bool+prop_newPasswordShouldConsistOfAlphabet len caps gen+ = all ((flip elem) alphabet) (newPassword len caps gen) -prop_newPasswordsShouldBeUnique :: GreaterThan2 Int -> StdGen -> Bool-prop_newPasswordsShouldBeUnique (GT2 len) gen+prop_newPasswordsShouldBeUnique :: GreaterThan2 Int -> Bool -> StdGen -> Bool+prop_newPasswordsShouldBeUnique (GT2 len) caps gen = p /= ps- where (p:ps:_) = newPasswords len gen+ where (p:ps:_) = newPasswords len caps gen -prop_newPasswordShouldHaveLen :: Int -> StdGen -> Property-prop_newPasswordShouldHaveLen len gen- = len >= 0 ==> length (newPassword len gen) == len+prop_newPasswordShouldHaveLen :: Int -> Bool -> StdGen -> Property+prop_newPasswordShouldHaveLen len caps gen+ = len >= 0 ==> length (newPassword len caps gen) == len prop_first2ShouldHaveLength2 :: StdGen -> Bool prop_first2ShouldHaveLength2 g = length (evalRand first2 g) == 2