content: {-# LANGUAGE CPP         #-}
  {-# LANGUAGE Trustworthy #-}
  
  -- | This module extends "System.Path" (which is re-exported for
  -- convenience) with thin wrappers around common IO functions
  -- and is intended to replace imports of "System.FilePath",
  --
  -- To facilitate importing this module unqualified we also re-export
  -- some definitions from "System.IO" (importing both would likely lead
  -- to name clashes).
  module System.Path.IO
    (
      -- * Wrappers
      -- ** Wrappers around "System.IO"
      withFile
    , openTempFile'
      -- ** Wrappers around "Data.ByteString"
    , readLazyByteString
    , readStrictByteString
    , writeLazyByteString
    , writeStrictByteString
    , appendLazyByteString
    , appendStrictByteString
      -- ** Wrappers around "Data.Text.IO" and "Data.Text.Lazy.IO"
      -- *** Locale-dependent encoding
    , readLazyText
    , readStrictText
    , writeLazyText
    , writeStrictText
    , appendLazyText
    , appendStrictText
      -- *** [UTF-8](https://en.wikipedia.org/wiki/UTF-8) encoding
    , readLazyTextUtf8
    , readStrictTextUtf8
    , writeLazyTextUtf8
    , writeStrictTextUtf8
    , appendLazyTextUtf8
    , appendStrictTextUtf8
      -- ** Wrappers around "System.Directory"
    , copyFile
    , createDirectory
    , createDirectoryIfMissing
    , removeDirectory
    , doesFileExist
    , doesDirectoryExist
    , getModificationTime
    , removeFile
    , getTemporaryDirectory
    , getDirectoryContents
    , getRecursiveContents
    , renameFile
    , getCurrentDirectory
  
      -- * Re-exports
      -- ** Re-exported "System.Path" module
    , module System.Path
      -- ** "System.IO" re-exports
    , IOMode(..)
    , BufferMode(..)
    , Handle
    , SeekMode(..)
    , IO.hSetBuffering
    , IO.hClose
    , IO.hFileSize
    , IO.hSeek
    ) where
  
  import           System.Path
  
  #if !MIN_VERSION_base(4,8,0)
  import           Control.Applicative      ((<$>))
  #endif
  
  import           Control.Exception        (evaluate)
  import           Control.Monad
  import           Data.Time                (UTCTime)
  import           System.IO                (BufferMode (..), Handle, IOMode (..),
                                             SeekMode (..))
  import qualified System.IO                as IO
  import           System.IO.Unsafe         (unsafeInterleaveIO)
  
  #if !MIN_VERSION_directory(1,2,0)
  import           Data.Time.Clock          (picosecondsToDiffTime)
  import           Data.Time.Clock.POSIX    (posixSecondsToUTCTime)
  import           System.Time              (ClockTime (TOD))
  #endif
  
  import qualified Data.ByteString          as BS
  import qualified Data.ByteString.Lazy     as BS.L
  import qualified Data.Text                as T
  import qualified Data.Text.Encoding       as T.E
  import qualified Data.Text.IO             as T
  import qualified Data.Text.Lazy           as T.L
  import qualified Data.Text.Lazy.Encoding  as T.L.E
  import qualified Data.Text.Lazy.IO        as T.L
  import qualified System.Directory         as Dir
  
  #if defined(__HADDOCK_VERSION__)
  import           Data.Text.Encoding.Error (UnicodeException)
  #endif
  
  {-------------------------------------------------------------------------------
    Wrappers around System.IO
  -------------------------------------------------------------------------------}
  
  -- | Wrapper around 'withFile'
  withFile :: FsRoot root => Path root -> IOMode -> (Handle -> IO r) -> IO r
  withFile path mode callback = do
      filePath <- toAbsoluteFilePath path
      IO.withFile filePath mode callback
  
  -- | Wrapper around 'openBinaryTempFileWithDefaultPermissions'
  --
  -- NOTE: The caller is responsible for cleaning up the temporary file.
  openTempFile' :: FsRoot root => Path root -> String -> IO (Path Absolute, Handle)
  openTempFile' path template = do
      filePath <- toAbsoluteFilePath path
      (tempFilePath, h) <- IO.openBinaryTempFileWithDefaultPermissions filePath template
      return (fromAbsoluteFilePath tempFilePath, h)
  
  {-------------------------------------------------------------------------------
    Wrappers around Data.ByteString.*
  -------------------------------------------------------------------------------}
  
  -- | Wrapper around lazy 'BS.L.readFile'
  readLazyByteString :: FsRoot root => Path root -> IO BS.L.ByteString
  readLazyByteString path = do
      filePath <- toAbsoluteFilePath path
      BS.L.readFile filePath
  
  -- | Wrapper around strict 'BS.readFile'
  readStrictByteString :: FsRoot root => Path root -> IO BS.ByteString
  readStrictByteString path = do
      filePath <- toAbsoluteFilePath path
      BS.readFile filePath
  
  -- | Wrapper around lazy 'BS.L.writeFile'
  writeLazyByteString :: FsRoot root => Path root -> BS.L.ByteString -> IO ()
  writeLazyByteString path bs = do
      filePath <- toAbsoluteFilePath path
      BS.L.writeFile filePath bs
  
  -- | Wrapper around strict 'BS.writeFile'
  writeStrictByteString :: FsRoot root => Path root -> BS.ByteString -> IO ()
  writeStrictByteString path bs = do
      filePath <- toAbsoluteFilePath path
      BS.writeFile filePath bs
  
  -- | Wrapper around lazy 'BS.L.appendFile'
  appendLazyByteString :: FsRoot root => Path root -> BS.L.ByteString -> IO ()
  appendLazyByteString path bs = do
      filePath <- toAbsoluteFilePath path
      BS.L.appendFile filePath bs
  
  -- | Wrapper around strict 'BS.appendFile'
  appendStrictByteString :: FsRoot root => Path root -> BS.ByteString -> IO ()
  appendStrictByteString path bs = do
      filePath <- toAbsoluteFilePath path
      BS.appendFile filePath bs
  
  {-------------------------------------------------------------------------------
    Wrappers around Data.Text.*
  -------------------------------------------------------------------------------}
  
  --------------------------------------------------------------------------------
  -- locale-dependent versions
  
  -- | Wrapper around lazy 'T.L.readFile'
  readLazyText :: FsRoot root => Path root -> IO T.L.Text
  readLazyText path = do
      filePath <- toAbsoluteFilePath path
      T.L.readFile filePath
  
  -- | Wrapper around strict 'T.readFile'
  readStrictText :: FsRoot root => Path root -> IO T.Text
  readStrictText path = do
      filePath <- toAbsoluteFilePath path
      T.readFile filePath
  
  -- | Wrapper around lazy 'T.L.writeFile'
  writeLazyText :: FsRoot root => Path root -> T.L.Text -> IO ()
  writeLazyText path bs = do
      filePath <- toAbsoluteFilePath path
      T.L.writeFile filePath bs
  
  -- | Wrapper around strict 'T.writeFile'
  writeStrictText :: FsRoot root => Path root -> T.Text -> IO ()
  writeStrictText path bs = do
      filePath <- toAbsoluteFilePath path
      T.writeFile filePath bs
  
  -- | Wrapper around lazy 'T.L.appendFile'
  appendLazyText :: FsRoot root => Path root -> T.L.Text -> IO ()
  appendLazyText path bs = do
      filePath <- toAbsoluteFilePath path
      T.L.appendFile filePath bs
  
  -- | Wrapper around strict 'T.appendFile'
  appendStrictText :: FsRoot root => Path root -> T.Text -> IO ()
  appendStrictText path bs = do
      filePath <- toAbsoluteFilePath path
      T.appendFile filePath bs
  
  --------------------------------------------------------------------------------
  -- UTF-8 versions
  
  -- | Read lazy 'Text' from a file (using UTF-8 encoding).
  --
  -- __NOTE__: Since the file is read lazily UTF-8 decoding errors are detected lazily as well. Such errors will result in an 'Data.Text.Encoding.Error.UnicodeException' being thrown within the lazy 'Text' stream.
  readLazyTextUtf8 :: FsRoot root => Path root -> IO T.L.Text
  readLazyTextUtf8 path = T.L.E.decodeUtf8 <$> readLazyByteString path
  
  -- | Read strict 'Text' from a file (using UTF-8 encoding).
  --
  -- __NOTE__: In case of UTF-8 decoding errors an 'Data.Text.Encoding.Error.UnicodeException' will be thrown.
  readStrictTextUtf8 :: FsRoot root => Path root -> IO T.Text
  readStrictTextUtf8 path = do
      bs <- readStrictByteString path
      evaluate (T.E.decodeUtf8 bs)
  
  -- | Write lazy 'Text' to a file (using UTF-8 encoding). The file is truncated to zero length before writing begins.
  writeLazyTextUtf8 :: FsRoot root => Path root -> T.L.Text -> IO ()
  writeLazyTextUtf8 path bs = do
      filePath <- toAbsoluteFilePath path
      T.L.writeFile filePath bs
  
  -- | Write strict 'Text' to a file (using UTF-8 encoding). The file is truncated to zero length before writing begins.
  writeStrictTextUtf8 :: FsRoot root => Path root -> T.Text -> IO ()
  writeStrictTextUtf8 path bs = do
      filePath <- toAbsoluteFilePath path
      T.writeFile filePath bs
  
  -- | Append lazy 'Text' to end of a file (using UTF-8 encoding).
  appendLazyTextUtf8 :: FsRoot root => Path root -> T.L.Text -> IO ()
  appendLazyTextUtf8 path bs = do
      filePath <- toAbsoluteFilePath path
      T.L.appendFile filePath bs
  
  -- | Append strict 'Text' to end of a file (using UTF-8 encoding).
  appendStrictTextUtf8 :: FsRoot root => Path root -> T.Text -> IO ()
  appendStrictTextUtf8 path bs = do
      filePath <- toAbsoluteFilePath path
      T.appendFile filePath bs
  
  {-------------------------------------------------------------------------------
    Wrappers around System.Directory
  -------------------------------------------------------------------------------}
  
  copyFile :: (FsRoot root, FsRoot root') => Path root -> Path root' -> IO ()
  copyFile src dst = do
      src' <- toAbsoluteFilePath src
      dst' <- toAbsoluteFilePath dst
      Dir.copyFile src' dst'
  
  createDirectory :: FsRoot root => Path root -> IO ()
  createDirectory path = Dir.createDirectory =<< toAbsoluteFilePath path
  
  createDirectoryIfMissing :: FsRoot root => Bool -> Path root -> IO ()
  createDirectoryIfMissing createParents path = do
      filePath <- toAbsoluteFilePath path
      Dir.createDirectoryIfMissing createParents filePath
  
  removeDirectory :: FsRoot root => Path root -> IO ()
  removeDirectory path = Dir.removeDirectory =<< toAbsoluteFilePath path
  
  doesFileExist :: FsRoot root => Path root -> IO Bool
  doesFileExist path = do
      filePath <- toAbsoluteFilePath path
      Dir.doesFileExist filePath
  
  doesDirectoryExist :: FsRoot root => Path root -> IO Bool
  doesDirectoryExist path = do
      filePath <- toAbsoluteFilePath path
      Dir.doesDirectoryExist filePath
  
  getModificationTime :: FsRoot root => Path root -> IO UTCTime
  getModificationTime path = do
      filePath <- toAbsoluteFilePath path
      toUTC <$> Dir.getModificationTime filePath
    where
  #if MIN_VERSION_directory(1,2,0)
      toUTC :: UTCTime -> UTCTime
      toUTC = id
  #else
      toUTC :: ClockTime -> UTCTime
      toUTC (TOD secs psecs) = posixSecondsToUTCTime $ realToFrac $ picosecondsToDiffTime (psecs + secs*1000000000000)
  #endif
  
  removeFile :: FsRoot root => Path root -> IO ()
  removeFile path = do
      filePath <- toAbsoluteFilePath path
      Dir.removeFile filePath
  
  getTemporaryDirectory :: IO (Path Absolute)
  getTemporaryDirectory = fromAbsoluteFilePath <$> Dir.getTemporaryDirectory
  
  -- | Return the immediate children of a directory
  --
  -- Filters out @"."@ and @".."@.
  getDirectoryContents :: FsRoot root => Path root -> IO [Path Unrooted]
  getDirectoryContents path = do
      filePath <- toAbsoluteFilePath path
      fragments' <$> Dir.getDirectoryContents filePath
    where
      fragments' :: [String] -> [Path Unrooted]
      fragments' = map fragment . filter (not . skip)
  
      skip :: String -> Bool
      skip "."  = True
      skip ".." = True
      skip _    = False
  
  -- | Recursive traverse a directory structure
  --
  -- Returns a set of paths relative to the directory specified. The list is
  -- lazily constructed, so that directories are only read when required.
  -- (This is also essential to ensure that this function does not build the
  -- entire result in memory before returning, potentially running out of heap.)
  getRecursiveContents :: FsRoot root => Path root -> IO [Path Unrooted]
  getRecursiveContents root = go emptyPath
    where
      go :: Path Unrooted -> IO [Path Unrooted]
      go subdir = unsafeInterleaveIO $ do
        entries <- getDirectoryContents (root </> subdir)
        liftM concat $ forM entries $ \entry -> do
          let path = subdir </> entry
          isDirectory <- doesDirectoryExist (root </> path)
          if isDirectory then go path
                         else return [path]
  
      emptyPath :: Path Unrooted
      emptyPath = joinFragments []
  
  renameFile :: (FsRoot root, FsRoot root')
             => Path root  -- ^ Old
             -> Path root' -- ^ New
             -> IO ()
  renameFile old new = do
      old' <- toAbsoluteFilePath old
      new' <- toAbsoluteFilePath new
      Dir.renameFile old' new'
  
  getCurrentDirectory :: IO (Path Absolute)
  getCurrentDirectory = do
      cwd <- Dir.getCurrentDirectory
      makeAbsolute $ fromFilePath cwd
  
  