elynx-0.3.0: app/Main.hs
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TypeApplications #-}
-- |
-- Module : Main
-- Description : Validate elynx file
-- Copyright : (c) Dominik Schrempf 2020
-- License : GPL-3.0-or-later
--
-- Maintainer : dominik.schrempf@gmail.com
-- Stability : unstable
-- Portability : portable
--
-- Creation date: Wed Apr 22 21:08:25 2020.
module Main
( main,
)
where
import Control.Monad
import Data.Aeson
import qualified Data.Aeson.Types as J
import qualified Data.ByteString.Char8 as B
import Data.Maybe
import Data.Version (Version)
import ELynx.Tools
import Options
import Options.Applicative
import qualified SLynx.Options as S
import SLynx.SLynx (slynx)
import System.Environment
( withArgs,
withProgName,
)
import qualified TLynx.Options as T
import TLynx.TLynx (tlynx)
parseProgName :: Value -> J.Parser String
parseProgName = withObject "progName" $ \o -> o .: "progName"
-- TODO: Declaring program names here should not be necessary.
parseAllR :: String -> Value -> J.Parser AllReproductions
parseAllR "slynx" v =
S <$> (parseJSON v :: J.Parser (Reproduction (Arguments S.CommandArguments)))
parseAllR "tlynx" v =
T <$> (parseJSON v :: J.Parser (Reproduction (Arguments T.CommandArguments)))
parseAllR p _ =
let err =
unlines
[ "Could not parse program name:",
p,
"Do you use the correct ELynx version?"
]
in error err
parse :: Show a => [String] -> Parser a -> a
parse s p =
fromMaybe
(error $ "parse: could not parse command line arguments: " ++ show s)
(getParseResult res)
where
res = execParserPure defaultPrefs (info p briefDesc) s
-- Does the command line fit the saved arguments?
checkArgs ::
forall a.
(Eq a, Show a, Reproducible a) =>
Reproduction a ->
IO (Either String ())
checkArgs s = do
let r = reproducible s
p = parser @a
as = argsStr s
res = parse as p
return $
if res /= reproducible s
then
Left $
unlines
["Command line string and command arguments do not fit:", show as, show r]
else Right ()
-- Does the file match the base 16 checksum?
checkFile :: FilePath -> B.ByteString -> IO (Either String ())
checkFile fp h = do
h' <- hashFile fp
return $
if h' == h
then Right ()
else
Left $
unlines
[ "SHA256 sum does not match for a file:",
fp ++ " has check sum " ++ B.unpack h',
"Stored check sum is " ++ B.unpack h
]
checkVersion :: Version -> Either String ()
checkVersion v =
if v == version
then Right ()
else
Left $
unlines
[ "Versions differ:",
"Version in ELynx reproduction file: " ++ show v,
"Version of current executable: " ++ show version
]
checkHash :: Reproducible a => Reproduction a -> Either String ()
checkHash r =
if h == h'
then Right ()
else
Left $
unlines
[ "ELynx reproduction file has been changed:",
"Hash saved in file: " ++ show h,
"Hash calculated from file: " ++ show h'
]
where
h = rHash r
h' = Just $ getReproductionHash r
-- Check if command line arguments and files check sums are matching.
validate ::
(Eq a, Show a, Reproducible a) => Reproduction a -> IO (Either String ())
validate s = do
chA <- checkArgs s
let chV = checkVersion (rVersion s)
chH = checkHash s
chFs <- zipWithM checkFile (files s) (map B.pack $ checkSums s)
return $ sequence_ (chA : chV : chH : chFs)
validateAllReproductions :: AllReproductions -> IO (Either String ())
validateAllReproductions (S x) = validate x
validateAllReproductions (T x) = validate x
getAllR :: FilePath -> IO AllReproductions
getAllR fp = do
eELynx <- eitherDecodeFileStrict fp :: IO (Either String Value)
elynx <- case eELynx of
Left err -> do
putStrLn "Failed decoding the ELynx reproduction file."
putStrLn "The following error occurred:"
error err
Right val -> return val
prog <- case J.parseEither parseProgName elynx of
Left err -> do
putStrLn "Failed getting command hash from ELynx reproduction file."
putStrLn "The following error occurred:"
error err
Right prog -> return prog
case J.parseEither (parseAllR prog) elynx of
Left err -> do
putStrLn "Failed parsing the ELynx reproduction file."
putStrLn "The following error occurred:"
error err
Right repr -> return repr
-- Read and validate ELynx reproduction file. Check consistency of arguments and
-- input files.
runValidate :: ValidateArguments -> IO ()
runValidate a = do
let fp = vElynxFile a
repr <- getAllR fp
val <- validateAllReproductions repr
case val of
Left err -> do
putStrLn "Failed validating the ELynx reproduction file."
putStrLn "The following error occurred:"
error err
Right () -> putStrLn "Validation successful!"
runRedo :: RedoArguments -> IO ()
runRedo a = do
let fp = rElynxFile a
let f = rForce a
when (f == Force False) $ do
putStrLn "Validate ELynx reproduction file before reanalysis."
putStrLn "Use the --force (-f) option to skip this test."
runValidate (ValidateArguments fp)
repr <- getAllR fp
let as = getArgs repr
as' <-
if "-f" `notElem` as && "--force" `notElem` as
then do
putStrLn
"Force option required to redo analysis. Add -f (force) to arguments."
return $ "-f" : as
else return as
withProgName (getProgName repr) $ withArgs as' $ redo repr
setForce :: Arguments a -> Arguments a
setForce (Arguments g l) = Arguments g {forceReanalysis = Force True} l
redo :: AllReproductions -> IO ()
redo (S x) = slynx $ setForce $ reproducible x
redo (T x) = tlynx $ setForce $ reproducible x
main :: IO ()
main = do
g <- execParser commandArguments
case g of
Validate a -> runValidate a
Redo a -> runRedo a