packages feed

elynx-tools-0.7.0.1: src/ELynx/Tools/ELynx.hs

{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TypeApplications #-}

-- |
-- Module      :  ELynx.Tools.ELynx
-- Description :  The ELynx transformer
-- Copyright   :  2021 Dominik Schrempf
-- License     :  GPL-3.0-or-later
--
-- Maintainer  :  dominik.schrempf@gmail.com
-- Stability   :  experimental
-- Portability :  portable
--
-- Creation date: Thu Sep  2 18:55:11 2021.
module ELynx.Tools.ELynx
  ( ELynx,
    eLynxWrapper,
    out,
    outHandle,
  )
where

import Control.Monad.IO.Class
import Control.Monad.Trans.Reader hiding (local)
import Data.Aeson
import qualified Data.ByteString.Lazy.Char8 as BL
import ELynx.Tools.Environment
import ELynx.Tools.InputOutput
import ELynx.Tools.Logger
import ELynx.Tools.Options
import ELynx.Tools.Reproduction
import System.IO
import System.Random.Stateful

-- | ELynx transformer to be used with all executables.
type ELynx a = ReaderT (Environment a) IO

fixSeed :: Reproducible a => a -> IO a
fixSeed x = case getSeed x of
  (Just RandomUnset) -> do
    s <- uniformM globalStdGen :: IO Int
    return $ setSeed x (RandomSet s)
  _ -> return x

eLynxRun ::
  forall a b.
  (Eq a, Reproducible a, Reproducible b, Show a, ToJSON a) =>
  (b -> a) ->
  ELynx b () ->
  ELynx b ()
eLynxRun f worker = do
  -- Header.
  logInfoHeader (cmdName @b) (cmdDsc @b)
  mso <- reader (getSeed . localArguments)
  case mso of
    Nothing -> return ()
    Just (RandomSet s) -> logInfoS $ "Seed: random; set to " <> show s <> "."
    Just (Fixed s) -> logInfoS $ "Seed: fixed to " <> show s <> "."
    Just RandomUnset -> error "eLynxRun: Seed unset."
  -- Worker.
  worker
  -- Footer.
  e <- ask
  let g = globalArguments e
      l = localArguments e
  case (writeElynxFile g, outFileBaseName g) of
    (False, _) ->
      logInfoS "No elynx file option --- skip writing ELynx file for reproducible runs."
    (True, Nothing) ->
      logInfoS "No output file given --- skip writing ELynx file for reproducible runs."
    (True, Just bn) -> do
      logInfoS "Write ELynx reproduction file."
      liftIO $ writeReproduction bn (Arguments g (f l))
  -- Footer.
  logInfoFooter

-- | The 'ReaderT' wrapper for ELynx. Prints a header and a footer, logs to
-- 'stdout' and possibly a log file, if provided. Initializes the seed if none
-- is provided.
eLynxWrapper ::
  (Eq a, Show a, Reproducible a, Reproducible b, ToJSON a) =>
  GlobalArguments ->
  -- Local arguments.
  b ->
  -- Local arguments across all commands.
  (b -> a) ->
  ELynx b () ->
  IO ()
eLynxWrapper gArgs lArgs f worker = do
  -- 1. Fix seed.
  lArgs' <- fixSeed lArgs

  -- 2. Initialize environment.
  e <- initializeEnvironment gArgs lArgs'

  -- 3. Run.
  runReaderT (eLynxRun f worker) e

  -- 4. Close environment.
  closeEnvironment e

-- Get out file path with extension.
getOutFilePath ::
  forall a. Reproducible a => String -> ELynx a (Maybe FilePath)
getOutFilePath ext = do
  a <- ask
  let bn = outFileBaseName . globalArguments $ a
      sfxs = outSuffixes . localArguments $ a
  if ext `elem` sfxs
    then return $ (++ ext) <$> bn
    else
      error
        "getOutFilePath: out file suffix not registered; please contact maintainer."

-- | Write a result with a given name to file with given extension or standard
-- output. Supports compression.
out :: Reproducible a => String -> BL.ByteString -> String -> ELynx a ()
out name res ext = do
  mfp <- getOutFilePath ext
  case mfp of
    Nothing -> do
      logInfoS $ "Write " <> name <> " to standard output."
      liftIO $ BL.putStr res
    Just fp -> do
      logInfoS $ "Write " <> name <> " to file '" <> fp <> "'."
      em <- executionMode . globalArguments <$> ask
      liftIO $ writeGZFile em fp res

-- BUG: 'outHandle' is flawed. If '-o BASENAME' is not provided, the output
-- handle is stdout, but then, when closing the handle, stdout will be closed!
-- Big Bug.

-- | Get an output handle, does not support compression. The handle has to be
-- closed after use!
outHandle :: Reproducible a => String -> String -> ELynx a Handle
outHandle name ext = do
  mfp <- getOutFilePath ext
  case mfp of
    Nothing -> do
      logInfoS $ "Write " <> name <> " to standard output."
      return stdout
    Just fp -> do
      logInfoS $ "Write " <> name <> " to file '" <> fp <> "'."
      em <- executionMode . globalArguments <$> ask
      liftIO $ openFileWithExecutionMode em fp