elynx-tools-0.6.1.1: src/ELynx/Tools/InputOutput.hs
{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TemplateHaskell #-}
{-# LANGUAGE TypeApplications #-}
-- |
-- Module : ELynx.Tools.InputOutput
-- Copyright : (c) Dominik Schrempf 2021
-- License : GPL-3.0-or-later
--
-- Maintainer : dominik.schrempf@gmail.com
-- Stability : unstable
-- Portability : portable
--
-- Creation date: Thu Feb 14 13:30:37 2019.
--
-- Tools involving input, output, and parsing.
module ELynx.Tools.InputOutput
( -- * Execution Mode
ExecutionMode (..),
openFileWithExecutionMode,
-- * Input, output
readGZFile,
writeGZFile,
-- * Parsing
runParserOnFile,
parseFileWith,
parseIOWith,
parseFileOrIOWith,
parseStringWith,
parseByteStringWith,
)
where
import Codec.Compression.GZip
import Data.Aeson hiding (String)
import Data.Attoparsec.ByteString.Lazy hiding (Fail)
import qualified Data.ByteString.Lazy.Char8 as BL
import Data.List (isSuffixOf)
import GHC.Generics
import System.Directory
import System.IO
-- | Overwrite existing output files or fail if output files exist.
data ExecutionMode = Overwrite | Fail
deriving (Eq, Show, Generic)
instance FromJSON ExecutionMode
instance ToJSON ExecutionMode
checkFile :: ExecutionMode -> FilePath -> IO ()
checkFile Overwrite _ = return ()
checkFile Fail fp =
doesFileExist fp >>= \case
True ->
error $
"File exists: "
<> fp
<> "."
<> "\n"
<> "Please use --force to overwrite results of a previous analysis."
False -> return ()
-- | Open existing files only if 'ExecutionMode' is 'Overwrite'.
openFileWithExecutionMode :: ExecutionMode -> FilePath -> IO Handle
openFileWithExecutionMode em fp = checkFile em fp >> openFile fp WriteMode
-- | Read file. If file path ends with ".gz", assume gzipped file and decompress
-- before read.
readGZFile :: FilePath -> IO BL.ByteString
readGZFile f
| ".gz" `isSuffixOf` f = decompress <$> BL.readFile f
| otherwise = BL.readFile f
-- | Write file. If file path ends with ".gz", assume gzipped file and compress
-- before write.
writeGZFile :: ExecutionMode -> FilePath -> BL.ByteString -> IO ()
writeGZFile frc f r
| ".gz" `isSuffixOf` f = checkFile frc f >> BL.writeFile f (compress r)
| otherwise = checkFile frc f >> BL.writeFile f r
-- | Parse a possibly gzipped file.
runParserOnFile :: Parser a -> FilePath -> IO (Either String a)
runParserOnFile p f = eitherResult . parse p <$> readGZFile f
-- | Parse a possibly gzipped file and extract the result.
parseFileWith :: Parser a -> FilePath -> IO a
parseFileWith p f = parseFileOrIOWith p (Just f)
-- | Parse standard input.
parseIOWith :: Parser a -> IO a
parseIOWith p = parseFileOrIOWith p Nothing
-- | Parse a possibly gzipped file, or standard input, and extract the result.
parseFileOrIOWith :: Parser a -> Maybe FilePath -> IO a
parseFileOrIOWith p mf = do
s <- maybe BL.getContents readGZFile mf
return $! parseByteStringWith p s
-- | Parse a 'String' and extract the result.
parseStringWith :: Parser a -> String -> a
parseStringWith p x = parseByteStringWith p (BL.pack x)
-- | Parse a 'BL.ByteString' and extract the result.
parseByteStringWith :: Parser a -> BL.ByteString -> a
parseByteStringWith p x = case eitherResult $ parse p x of
Left err -> error err
Right val -> val