{- |
   Module      : Streaming.With
   Description : with/bracket-style idioms for use with streaming
   Copyright   : Ivan Lazar Miljenovic
   License     : MIT
   Maintainer  : Ivan.Miljenovic@gmail.com



 -}
module Streaming.With
  ( -- * File-handling
    withFile
  , withBinaryFile
    -- ** Common file-handling cases
  , writeBinaryFile
  , appendBinaryFile
  , withBinaryFileContents
    -- ** Temporary files
  , withSystemTempFile
  , withTempFile
    -- *** Re-exports
    -- $tempreexports
  , withSystemTempDirectory
  , withTempDirectory
    -- * Re-exports
    -- $reexports
  , MonadMask
  , bracket
  ) where

import           Data.ByteString.Streaming (ByteString)
import qualified Data.ByteString.Streaming as B

import           Control.Monad.Catch    (MonadMask, bracket)
import           Control.Monad.IO.Class (MonadIO, liftIO)
import           System.IO              (Handle, IOMode(..), hClose,
                                         openBinaryFile, openFile)
import           System.IO.Temp         (withSystemTempDirectory,
                                         withTempDirectory)
import qualified System.IO.Temp         as T

--------------------------------------------------------------------------------

-- | A lifted variant of 'System.IO.withFile'.
--
--   You almost definitely don't want to use this; instead, use
--   'withBinaryFile' in conjunction with "Data.ByteString.Streaming".
withFile :: (MonadMask m, MonadIO m) => FilePath -> IOMode -> (Handle -> m r) -> m r
withFile fp md = bracket (liftIO (openFile fp md)) (liftIO . hClose)

-- | A lifted variant of 'System.IO.withBinaryFile'.
withBinaryFile :: (MonadMask m, MonadIO m) => FilePath -> IOMode -> (Handle -> m r) -> m r
withBinaryFile fp md = bracket (liftIO (openBinaryFile fp md)) (liftIO . hClose)

-- | Write to the specified file.
writeBinaryFile :: (MonadMask m, MonadIO m) => FilePath -> ByteString m r -> m r
writeBinaryFile fp = withBinaryFile fp WriteMode . flip B.hPut

-- | Append to the specified file.
appendBinaryFile :: (MonadMask m, MonadIO m) => FilePath -> ByteString m r -> m r
appendBinaryFile fp = withBinaryFile fp AppendMode . flip B.hPut

-- | Apply a function to the contents of the file.
--
--   Note that a different monadic stack is allowed for the
--   'ByteString' input, as long as it later gets resolved to the
--   required output type (e.g. remove transformer).
withBinaryFileContents :: (MonadMask m, MonadIO m, MonadIO n) => FilePath
                          -> (ByteString n () -> m r) -> m r
withBinaryFileContents fp f = withBinaryFile fp ReadMode (f . B.hGetContents)

--------------------------------------------------------------------------------

-- | /This is 'T.withSystemTempFile' from the @temporary@ package with
--   the continuation re-structured to only take one argument./
--
--   Create and use a temporary file in the system standard temporary
--   directory.
--
--   Behaves exactly the same as 'withTempFile', except that the
--   parent temporary directory will be that returned by
--   'T.getCanonicalTemporaryDirectory'.
--
--   @since 0.1.1.0
withSystemTempFile :: (MonadIO m, MonadMask m)
                   => String -- ^ File name template.  See 'T.openTempFile'
                   -> ((FilePath, Handle) -> m r)
                   -> m r
withSystemTempFile template = T.withSystemTempFile template . curry

-- | /This is 'T.withTempFile' from the @temporary@ package with
--   the continuation re-structured to only take one argument./
--
--   Use a temporary filename that doesn't already exist.
--
--   Creates a new temporary file inside the given directory, making
--   use of the template. The temp file is deleted after use. For
--   example:
--
--   > withTempFile "src" "sdist." $ \(tmpFile, hFile) -> ...
--
--   The @tmpFile@ will be file in the given directory, e.g.
--   @src/sdist.342@.
--
--   @since 0.1.1.0
withTempFile :: (MonadIO m, MonadMask m)
             => FilePath -- ^ Temp dir to create the file in
             -> String   -- ^ File name template.  See
                         --   'T.openTempFile'.
             -> ((FilePath, Handle) -> m r)
             -> m r
withTempFile dir template = T.withTempFile dir template . curry

{- $tempreexports

These functions are re-exported from the
<http://hackage.haskell.org/package/temporary temporary> package as-is
as their structure already matches those found here.

@since 0.1.1.0

-}

--------------------------------------------------------------------------------

{- $reexports

These may assist in writing your own bracket-style functions.

Note that not everything is re-exported: for example, 'Handle' isn't
re-exported for use with 'withFile' as it's unlikely that you will
write another wrapper around it, and furthermore it wouldn't be a
common enough extension to warrant it.

-}