{-# 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