{-# LANGUAGE MultiParamTypeClasses, FunctionalDependencies, FlexibleInstances #-} {- | Module : System.IO.Classes Copyright : (c) Andrey Mulik 2020 License : BSD-style Maintainer : work.a.mulik@gmail.com Portability : portable @System.IO.Classes@ provides generalized path and file classes. -} module System.IO.Classes ( -- * Export module System.IO.Handle, module System.IO.Error, -- * Generalized path IsFilePath (..), -- * Generalized file IsFile (..), getContents, putContents, withFile, readFile, writeFile, appendFile, -- ** Text IO IsTextFile (..), getLine, putStr, putStrLn, gets, puts ) where import Prelude () import SDP.SafePrelude import Data.FilePath import qualified System.IO as IO import System.IO.Handle import System.IO.Error import Control.Exception default () -------------------------------------------------------------------------------- -- | 'IsFilePath' is type class of file path (data destination) representations. class IsFilePath path where {- | @hOpen path mode@ open new text handle with block buffering and given @mode@ using @path@ identifier. This operation may fail with: * @isAlreadyInUseError@ if the file is already open and cannot be reopened * @isDoesNotExistError@ if the file does not exist * @isPermissionError@ if the user doesn't have permission to open the file -} hOpen :: (MonadIO io) => IOMode -> path -> io Handle hOpen mode path = hOpenWith mode path (BlockBuffering Nothing) False {- | @hOpenWith path mode buf bin@ open new handle with buffering mode @buf@, binary mode @bin@ and 'IOMode' @mode@ using @path@ identifier. This operation may fail with: * 'isAlreadyInUseError' if the file is already open and cannot be reopened * 'isDoesNotExistError' if the file does not exist * 'isPermissionError' if the user doesn't have permission to open the file -} hOpenWith :: (MonadIO io) => IOMode -> path -> BufferMode -> Bool -> io Handle -- | Open new temp file 'Handle' by given source identifier. hOpenTemp :: (MonadIO io) => path -> io (path, Handle) -------------------------------------------------------------------------------- {- | 'IsFile' is a type class that represents the contents of a file. 'IsFile' provides only basic read and write operations. It doesn't allow, for example, changing the 'IOMode' or handle the concatenation of the file contents with the appendable information. -} class IsFile file where {- | @hGetContents hdl@ reads the contents of the file. Reading may be both lazily (in this case @hdl@ should be semi-closed until the file end is reached) or strictly. After reading the file, @hdl@ should be closed. Once a semi-closed handle becomes closed, the contents becomes fixed. The contents of this final value is only partially specified: it will contain at least all the items of the stream that were evaluated prior to the handle becoming closed. This operation may fail with: * isEOFError if the end of file has been reached. -} hGetContents :: (MonadIO io) => Handle -> io file {- | @hPutContents hdl file@ writes the @file@ contents to @hdl@. This operation may fail with: * 'isFullError' if the device is full; or * 'isPermissionError' if another system resource limit would be exceeded. If 'hPutContents' changes the recording mode (buffering, binary/text), it should return the original Handle settings. -} hPutContents :: (MonadIO io) => Handle -> file -> io () -- | Just 'hGetContents' 'stdin'. getContents :: (MonadIO io, IsFile file) => io file getContents = hGetContents stdin -- | Just 'hPutContents' 'stdout'. putContents :: (MonadIO io, IsFile file) => file -> io () putContents = hPutContents stdout -------------------------------------------------------------------------------- {- | @withFile path mode act@ opens a file using 'hOpen' and passes the resulting handle to the computation @act@. The handle will be closed on exit from @withFile@, whether by normal termination or by raising an exception. If closing the handle raises an exception, then this exception will be raised by withFile rather than any exception raised by act. -} withFile :: (MonadIO io, IsFilePath path) => path -> IOMode -> (Handle -> IO a) -> io a withFile path mode = liftIO . bracket (hOpen mode path) hClose {- | The 'readFile' function reads a file and returns its contents. The specifics of reading a file (laziness/strictness, possible exceptions) depend on the type of resource and the 'hGetContents' implementation. -} readFile :: (MonadIO io, IsFilePath p, IsFile f) => p -> io f readFile = hOpen ReadMode >=> hGetContents -- | @writeFile path file@ function writes the @file@ value, to the @path@. writeFile :: (MonadIO io, IsFilePath p, IsFile f) => p -> f -> io () writeFile path file = withFile path WriteMode (`hPutContents` file) -- | @appendFile path file@ appends the @file@ value, to the @path@. appendFile :: (MonadIO io, IsFilePath p, IsFile f) => p -> f -> io () appendFile path file = withFile path AppendMode (`hPutContents` file) -------------------------------------------------------------------------------- -- | 'IsTextFile' is a type class of text file representations. class (IsFile text) => IsTextFile text where -- | Read one text line from handle. hGetLine :: (MonadIO io) => Handle -> io text -- | Put text to handle. hPutStr :: (MonadIO io) => Handle -> text -> io () -- | Put text line to handle. hPutStrLn :: (MonadIO io) => Handle -> text -> io () hPutStrLn hdl text = do hPutStr hdl text; hPutChar hdl '\n' -------------------------------------------------------------------------------- -- | Same as @hGetLine stdin@. getLine :: (MonadIO io, IsTextFile text) => io text getLine = hGetLine stdin -- | Same as @hPutStr stdout@. putStr :: (MonadIO io, IsTextFile text) => text -> io () putStr = hPutStr stdout -- | Same as @hPutStrLn stdout@. putStrLn :: (MonadIO io, IsTextFile text) => text -> io () putStrLn = hPutStrLn stdout -- | Short version of 'getLine'. gets :: (MonadIO io, IsTextFile text) => io text gets = getLine -- | Short version of 'putStrLn'. puts :: (MonadIO io, IsTextFile text) => text -> io () puts = putStrLn -------------------------------------------------------------------------------- instance IsFilePath FilePath where hOpen = liftIO ... flip IO.openFile hOpenWith mode path buf bin = do h <- hOpen mode path hSetBuffering h buf hSetBinaryMode h bin return h hOpenTemp (dir :/ name) = liftIO $ first (dir :/) <$> IO.openTempFile dir name instance IsFile String where hGetContents = liftIO . IO.hGetContents hPutContents = liftIO ... IO.hPutStr instance IsTextFile String where hPutStrLn = liftIO ... IO.hPutStrLn hGetLine = liftIO . IO.hGetLine hPutStr = liftIO ... IO.hPutStr