
> module Main (main) where

> import IO
> import Maybe
> import Exception
> import Posix
> import System
> import GetOpt
> import MD5

> data Flag = Verbose | Check String | Test deriving Eq

> main :: IO()
> main = do args <- System.getArgs
>           (opts, files) <- md5sum_opts args
>           if or $ map ((==) Test) opts
>            then md5test
>            else case isCheck opts of
>                     Nothing
>                         -> if files == []
>                            then do (str, len) <- md5_stdin
>                                    putStr (md5_size (8 * len) str)
>                                        >> putStr "\n"
>                            else foldr (\x y -> md5_file x >> y)
>                                     (putStr "") files
>                     Just fn -> check fn

> md5_stdin :: IO (String, Integer)
> md5_stdin = do maybeData <- tryAllIO $ fdRead stdInput 1024
>                case maybeData of
>                    Left _ -> return ("", 0)
>                    Right (this, len)
>                        -> do (rest, r_len) <- md5_stdin
>                              return $ (this ++ rest, toInteger len + r_len)

> md5_file :: String -> IO()
> md5_file file = do maybe_content <- tryAllIO $ readFile file
>                    maybe_fs <- tryAllIO $ getFileStatus file
>                    case (maybe_content, maybe_fs) of
>                        (Right content, Right fs) ->
>                            putStr (md5_size size content)
>                                >> putStr "  "
>                                >> putStr file
>                                >> putStr "\n"
>                             where size = 8 * (toInteger $ fileSize fs)
>                        (_, Left _) -> putErr file >> putErr ": No such file or directory.\n"
>                        (Left _, _) -> do ec <- getErrorCode
>                                          if ec == permissionDenied
>                                           then putErr file >> putErr ": Permission denied\n"
>                                           else putErr "md5sum: error reading " >> putErr file >> putErr "\n"

> md5test :: IO()
> md5test = foldr f (putStr "") l
>  where f (str, hash) so_far
>         =    putStr "Doing "     >> putStr str       >> putStr "\n"
>           >> putStr "Should be " >> putStr hash      >> putStr "\n"
>           >> putStr "Got       " >> putStr (md5 str) >> putStr "\n\n"
>           >> so_far
>        l = [("", "d41d8cd98f00b204e9800998ecf8427e"),
>             ("a", "0cc175b9c0f1b6a831c399e269772661"),
>             ("abc", "900150983cd24fb0d6963f7d28e17f72"),
>             ("message digest", "f96b697d7cb7938d525a2f31aaf161d0"),
>             ("abcdefghijklmnopqrstuvwxyz",
>              "c3fcd3d76192e4007dfb496cca67e13b"),
>             ("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789",
>              "d174ab98d277d9f5a5611c2c9f419d9f"),
>             ("12345678901234567890123456789012345678901234567890123456789012345678901234567890",
>              "57edf4a22be3c955ac49da2e2107b67a")]

===== Check =====

> check :: String -> IO()
> check f = do maybe_l <- tryAllIO $ get_list f
>              case maybe_l of
>                  Left _ -> do ec <- getErrorCode
>                               if ec == permissionDenied
>                                then putErr f >> putErr ": Permission denied\n"
>                                else putErr f >> putErr ": No such file or directory\n"
>                  Right l -> foldr checkhash (putStr "") l

> checkhash :: (String, String) -> IO() -> IO()
> checkhash (h, f) so_far =
>  do maybe_content <- tryAllIO $ readFile f
>     maybe_fs <- tryAllIO $ getFileStatus f
>     case (maybe_content, maybe_fs) of
>         (Right content, Right fs) ->
>             let h' = md5_size (8 * (toInteger $ fileSize fs)) content in
>             if h == h'
>              then so_far
>              else    putErr "md5sum: MD5 check failed for '" >> putErr f
>                   >> putErr "'\n" >> so_far
>         (_, _) ->    putErr "md5sum: Can't open " >> putErr f
>                   >> putErr "\n" >> so_far

> get_list :: String -> IO [(String, String)]
> get_list f = do h <- openFile f ReadMode
>                 parse h

> parse :: Handle -> IO [(String, String)]
> parse h = do eof <- hIsEOF h
>              if eof
>               then return []
>               else do line <- hGetLine h
>                       rest <- parse h
>                       let hash = take 32 line
>                       let filename = drop 34 line
>                       if filename /= "" {- and hash is valid -}
>                        then return $ (hash, filename):rest
>                        else return rest

===== Options =====

> options :: [OptDescr Flag]
> options =
>  [Option ['c'] [] (ReqArg Check "Check") "check message digests (default is generate)",
>   Option ['v'] [] (NoArg Verbose) "verbose, print file names when checking",
>   Option ['b'] [] (NoArg Verbose) "(ignored)",
>   Option ['t'] [] (NoArg Test)    "test the code is functioning correctly"]

> md5sum_opts :: [String] -> IO ([Flag], [String])
> md5sum_opts argv =
>   case (getOpt Permute options argv) of
>      (o,n,[]  ) -> return (o,n)
>      (_,_,errs) -> fail (concat errs ++ usageInfo header options ++ footer)
>  where header = "usage: md5sum [-btv] [-c [file]] | [file...]\nGenerates or checks MD5 Message Digests"
>        footer = "The input for -c should be the list of message digests and file names\nthat is printed on stdout by this program when it generates digests."

> isCheck :: [Flag] -> Maybe String
> isCheck [] = Nothing
> isCheck (Check s:_) = Just s
> isCheck (_:os) = isCheck os

=====

> putErr :: String -> IO()
> putErr = hPutStr stderr

