elocrypt 0.3.2 → 0.4.0
raw patch · 7 files changed
+186/−38 lines, 7 filesdep +proctestPVP ok
version bump matches the API change (PVP)
Dependencies added: proctest
API changes (from Hackage documentation)
- Data.Elocrypt: lastN :: MonadRandom m => String -> Int -> m String
+ Data.Elocrypt: lastN :: MonadRandom m => Int -> String -> m String
Files
- README.md +25/−12
- cli-test/Tests.hs +112/−0
- elocrypt.cabal +18/−2
- src/cli/Main.hs +20/−16
- src/lib/Data/Elocrypt.hs +9/−6
- test/Test/Elocrypt/Instances.hs +1/−1
- test/Test/ElocryptTest.hs +1/−1
README.md view
@@ -1,11 +1,30 @@ # Elocrypt [](https://travis-ci.org/sgillespie/elocrypt) -Elocrypt is a Haskell library that generates pronounceable, hard-to-guess passwords.. as hard as Vince Carter's knee cartilage is. Elocrypt includes a library and program.+Elocrypt generates pronounceable, easy-to-remember, hard-to-guess passwords... as hard as Vince Carter's knee cartilage is. Elocrypt includes a Haskell library and program. -## Downloading-Elocrypt sources can be found @ https://github.com/sgillespie/elocrypt+## Prerequisites+In order to build or install you will need+ * GHC (tested on 7.10 and 7.8)+ * cabal-install (tested on 1.20 and 1.22) +## Installing+Elocrypt is on [Hackage](https://hackage.haskell.org/package/elocrypt). Installation is as easy as:+```+cabal install elocrypt+```++## Running+Running elocrypt is as simple as:+```+elocrypt [length]+```++## Obtaining the source+Elocrypt sources can be found + * https://github.com/sgillespie/elocrypt+ * https://hackage.haskell.org/package/elocrypt+ ## Building In order to build or install you will need * GHC (tested on 7.10 and 7.8)@@ -21,20 +40,14 @@ cabal install ``` -## Running-Running elocrypt is as simple as...-```-elocrypt [length]-```- ## API Documentation-The full API documentation is generated by haddock+The full API documentation is on hackage @ https://hackage.haskell.org/package/elocrypt/docs. To build the documentation yourself, run ``` cabal haddock ``` ### API Examples-You can use elocrypt to generate words in any Haskell code, so long as you have installed elocrypt. Generate a word by using Elocrypt.Password.generate+You can use elocrypt to generate words in any Haskell code, so long as you have installed elocrypt. Generate a word by using Data.Elocrypt.newPassword ``` import Data.Elocrypt ...@@ -43,7 +56,7 @@ fooGen = newPassword 10 `liftM` getStdGen ``` -Alternatively, you can use Elocrypt.Password.mkPassword if you want to complete control of the random monad+Alternatively, you can use Data.Elocrypt.mkPassword if you want to complete control of the random monad ``` import Data.Elocrypt import Control.Monad.Random
+ cli-test/Tests.hs view
@@ -0,0 +1,112 @@+{-# 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` (>2) `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++-- 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.3.2+version: 0.4.0 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: v0.3.2+ tag: v0.4.0 library exposed-modules: Data.Elocrypt,@@ -58,5 +58,21 @@ tasty-th default-language: Haskell2010 hs-source-dirs: test+ main-is: Tests.hs+ type: exitcode-stdio-1.0++test-suite ui-test+ build-depends: base >= 4.7 && <4.9,+ elocrypt,+ proctest,+ MonadRandom,+ QuickCheck,+ random,+ tasty,+ tasty-quickcheck,+ tasty-th + default-language: Haskell2010+ hs-source-dirs: cli-test,+ test main-is: Tests.hs type: exitcode-stdio-1.0
src/cli/Main.hs view
@@ -2,6 +2,7 @@ import Control.Monad import Data.List+import Data.Maybe (fromMaybe) import System.Console.GetOpt import System.Environment import System.Exit@@ -10,7 +11,9 @@ import Data.Elocrypt -version = "elocrypt 0.3.2"+version = "elocrypt 0.4.0"+termLen = 80+termHeight = 10 main :: IO () main = do@@ -18,41 +21,42 @@ opts <- elocryptOpts args gen <- getStdGen - -- TODO: Refactor me- if (optHelp opts) then do+ when (optHelp opts) $ do hPutStrLn stderr usage exitSuccess- else return ()-- -- TODO: Refactor me- if (optVersion opts) then do+ when (optVersion opts) $ do hPutStrLn stderr version exitSuccess- else return () putStrLn (passwords opts gen) passwords :: RandomGen g => Options -> g -> String-passwords opts gen = concat . intersperse "\n" . map (concat . intersperse "\t") . group $ ps- where ps = take (optNumber opts) . newPasswords (optLength opts) $ gen- group (p:ps:pss:psss) = [p, ps, pss]:(group psss)- group ps = [ps]+passwords opts gen = format . group' cols $ ps+ where ps = take num . newPasswords (optLength opts) $ gen+ format = concat . intersperse "\n" . map (concat . intersperse " ")+ cols = termLen `div` (optLength opts + 2)+ num = fromMaybe (cols * termHeight) (optNumber opts) +group' :: Int -> [a] -> [[a]]+group' _ [] = []+group' i ls = g:(group' i ls')+ where (g, ls') = splitAt i ls+ data Options- = Options { optLength :: Int, -- Size of the password(s)- optNumber :: Int, -- Number of passwords to generate+ = Options { optLength :: Int, -- Size of the password(s)+ optNumber :: Maybe Int, -- Number of passwords to generate optHelp :: Bool, optVersion :: Bool } deriving (Show) defaultOptions :: Options defaultOptions = Options { optLength = 8,- optNumber = 42,+ optNumber = Nothing, optHelp = False, optVersion = False } options :: [OptDescr (Options -> Options)]-options = [Option ['n'] ["number"] (ReqArg (\n o -> o {optNumber = read n}) "NUMBER") "The number of passwords to generate (default: 42)",+options = [Option ['n'] ["number"] (ReqArg (\n o -> o {optNumber = Just (read n)}) "NUMBER") "The number of passwords to generate", Option ['h'] ["help"] (NoArg (\o -> o { optHelp = True })) "Show this help", Option ['v'] ["version"] (NoArg (\o -> o { optVersion = True })) "Show version and exit"]
src/lib/Data/Elocrypt.hs view
@@ -85,7 +85,8 @@ mkPassword :: MonadRandom m => Int -- ^ password length -> m String-mkPassword len = reverse `liftM` first2 >>= (flip lastN) (len - 2) >>= return . reverse+mkPassword len = first2 >>= reverse' >>= lastN (len - 2) >>= reverse'+ where reverse' = return . reverse -- |Plural version of mkPassword. Generate an infinite list of passwords using -- the MonadRandom m. MonadRandom is exposed here for extra control.@@ -106,9 +107,11 @@ -- * Internal --- |Generate two random characters+-- |Generate two random characters. Uses 'Elocrypt.Trigraph.trigragh'+-- to generate a weighted list. first2 :: MonadRandom m => m String-first2 = sequence . take 2 . repeat . uniform $ alphabet+first2 = fromList (map toWeight frequencies)+ where toWeight (s, w) = (s, sum w) -- |Generate a random character based on the previous two characters and -- their 'Elocrypt.Trigraph.trigraph'@@ -117,6 +120,6 @@ -- |Generate the last n characters using previous two characters -- and their 'Elocrypt.Trigraph.trigraph'-lastN :: MonadRandom m => String -> Int -> m String-lastN ls 0 = return ls-lastN ls len = next ls >>= (flip lastN) (len - 1) . (flip (:)) ls+lastN :: MonadRandom m => Int -> String -> m String+lastN 0 ls = return ls+lastN len ls = next ls >>= lastN (len - 1) . (:ls)
test/Test/Elocrypt/Instances.hs view
@@ -13,7 +13,7 @@ instance Arbitrary AlphaChar where arbitrary = Alpha `liftM` elements ['a'..'z'] -newtype GreaterThan2 a = GT2 { greaterThan2 :: a }+newtype GreaterThan2 a = GT2 { getGT2 :: a } deriving (Eq, Ord, Show) instance (Num a, Ord a, Arbitrary a) => Arbitrary (GreaterThan2 a) where
test/Test/ElocryptTest.hs view
@@ -48,7 +48,7 @@ prop_lastNShouldSkip0Weights (Alpha c1) (Alpha c2) len gen = len > 0 ==> lastNShouldSkip0Weights' lastN' where f2 = [c1, c2]- lastN' = evalRand (lastN (reverse [c1, c2]) len) gen+ lastN' = evalRand (lastN len [c2, c1]) gen lastNShouldSkip0Weights' :: String -> Property lastNShouldSkip0Weights' (p:ps:pss:psss) = isCandidate p [pss, ps] .&&.