raaz-0.2.0: bin/Command/Checksum.hs
{-# LANGUAGE GADTs #-}
{-# LANGUAGE RankNTypes #-}
{-# LANGUAGE RecordWildCards #-}
{-# LANGUAGE ConstraintKinds #-}
{-# LANGUAGE StandaloneDeriving #-}
{-# LANGUAGE CPP #-}
module Command.Checksum ( checksum ) where
import Control.Monad
import Data.Char ( toLower )
import Data.Monoid
import Data.String
import Options.Applicative
import Raaz
import System.Exit
import System.IO
------------------------- Supported checksum algorithm -------------------------------
-- You can add new hash algorithms by adding a constructor for the
-- GADT, Algorithm h. You should also enable the command by adding a mkCmd line in the checksum
checksum :: Parser (IO ())
#if MIN_VERSION_optparse_applicative(0,13,0)
checksum = subparser $ commandGroup "Checksum Computation"
#else
checksum = subparser $ mempty
#endif
<> mkCmd Blake2b
<> mkCmd Blake2s
<> mkCmd Sha512
<> mkCmd Sha256
-- Add the mkCmd here and also add an appropriate
-- constructor for the Algorithm type. If you add a hash
-- Foo then the associated command will be foosum.
-- | The hash algorithm to use.
data Algorithm h where
Blake2b :: Algorithm BLAKE2b
Blake2s :: Algorithm BLAKE2s
Sha256 :: Algorithm SHA256
Sha512 :: Algorithm SHA512
-- Add new hash algorithm here.
deriving instance Show (Algorithm h)
-- Add a mkCmd here for the new hash algorithm
mkCmd :: Digest h => Algorithm h -> Mod CommandFields (IO ())
mkCmd algo = command cmd inf
where inf = info (helper <*> opts) $ fullDesc <> hdr <> desc
opts = run algo <$> optParse
algoname = map toLower $ show algo
cmd = algoname ++ "sum"
hdr = header $ unwords [ "raaz", cmd, "- File checksums using", algoname]
desc = progDesc $ "Compute or verify the file checksums using " ++ algoname
------------------------ Types and constriants -------------------------------------
-- | This constraint class consolidates the constraints on the checksum algorithms.
type Digest h = (Hash h, Recommendation h, Show h, IsString h)
data Option = Option { checkDigest :: Bool -- ^ true if we need verification.
, reportOkey :: Bool -- ^ whether to print success
, reportFailure :: Bool -- ^ whether to print failure
, inputFiles :: [FilePath]
}
--------------------- The checksum type ----------------------------------------
data Checksum h = Checksum {filePath :: FilePath, fileDigest :: h}
instance Show h => Show (Checksum h) where
show (Checksum{..}) = show fileDigest ++ " " ++ filePath
parse :: Digest h => Algorithm h -> String -> Checksum h
parse algo inp = Checksum { filePath = drop 2 rest
, fileDigest = parseDigest algo digest
}
where parseDigest :: Digest h => Algorithm h -> String -> h
parseDigest _ = fromString
(digest, rest) = break (==' ') inp -- break at the space.
-- | Parse the lines into checksum.
parseMany :: Digest h => Algorithm h -> String -> [Checksum h]
parseMany algo = map (parse algo) . lines
---------- The main combinators that does the actual work -------------
-- | The workhorse for this command.
run :: Digest h => Algorithm h -> Option -> IO ()
run algo opt@(Option{..})
| checkDigest = runVerify algo opt
| otherwise = runCompute algo inputFiles
--------------------------- Compute mode ---------------------------------------
runCompute :: Digest h
=> Algorithm h -- Algorithm to use
-> [FilePath] -- files for which checksums need to be computed.
-> IO ()
runCompute algo files
| null files = computeStdin algo >>= print -- No files means compute it for stdin.
| otherwise = mapM_ printToken files -- Print the token for each file.
where printToken = compute algo >=> print
-- | Compute the checksum of a file.
compute :: Digest h
=> Algorithm h -- ^ The hashing algorithm to use.
-> FilePath -- ^ The file to compute the token for.
-> IO (Checksum h)
compute _ fp = Checksum fp <$> hashFile fp
-- | Compute the checksum of standard input
computeStdin :: Digest h => Algorithm h -> IO (Checksum h)
computeStdin _ = Checksum "-" <$> hashSource stdin
----------------------------------- Verify Mode ---------------------------------------
runVerify :: Digest h => Algorithm h -> Option -> IO ()
runVerify algo opt@(Option{..}) = do
nFails <- if null inputFiles then getContents >>= verifyLines
else sum <$> mapM verifyFile inputFiles
when reportFailure $ putStrLn $ show nFails ++ " failures."
when (nFails > 0) exitFailure
where
verifyLines = verifyList opt . parseMany algo
verifyFile fp = withFile fp ReadMode $ hGetContents >=> verifyLines
verify :: Digest h
=> Option
-> Checksum h
-> IO Bool
verify (Option{..}) (Checksum{..}) = do
digest <- hashFile filePath
let result = digest == fileDigest
okey = when reportOkey $ putStrLn $ filePath ++ ": OK"
failed = when reportFailure $ putStrLn $ filePath ++ ": FAILED"
in do if result then okey else failed
return result
verifyList :: Digest h
=> Option
-> [Checksum h]
-> IO Int
verifyList opt = fmap countFailures . mapM (verify opt)
where countFailures = Prelude.length . filter not
----------------------------- Option parsers ----------------------------------
optParse :: Parser Option
optParse = verbosityOpt <*> (Option <$> checkOpt <*> pure True <*> pure True <*> files)
where checkOpt = switch
$ long "check"
<> short 'c'
<> help "Verify the input checksums instead of compute"
files = many $ argument str $ metavar "FILE.."
verbosityOpt :: Parser (Option -> Option)
verbosityOpt = (.) <$> statusOpt <*> quietOpt
where statusOnly opt = opt { reportOkey = False, reportFailure = False }
quietMode opt = opt { reportOkey = False }
statusOpt = flag id statusOnly
$ short 's'
<> long "status"
<> help "Do not print anything, only return the verification status"
quietOpt = flag id quietMode
$ short 'q'
<> long "quite"
<> help "Do not print OK, print only failures"