{-# LANGUAGE FlexibleInstances, ScopedTypeVariables, TypeSynonymInstances #-}
module System.FilePath.Manip (
Streamable(..)
, renameWith
, modifyWith
, modifyWithBackup
, modifyInPlace
) where
import Control.Exception
import Control.Monad (liftM)
import Data.Bits ((.&.))
import System.Directory (removeFile)
import System.IO (Handle, IOMode(..), hClose, openFile)
import System.PosixCompat.Files (fileMode, getFileStatus, rename, setFileMode)
import System.PosixCompat.Temp (mkstemp)
import qualified Data.ByteString.Char8 as B
import qualified Data.ByteString.Lazy.Char8 as L
import qualified System.IO as I
renameWith :: (FilePath -> FilePath)
-> FilePath
-> IO ()
renameWith :: (FilePath -> FilePath) -> FilePath -> IO ()
renameWith FilePath -> FilePath
f FilePath
path = FilePath -> FilePath -> IO ()
rename FilePath
path (FilePath -> FilePath
f FilePath
path)
class Streamable a where
readAll :: Handle -> IO a
writeAll :: Handle -> a -> IO ()
instance Streamable B.ByteString where
readAll :: Handle -> IO ByteString
readAll = Handle -> IO ByteString
B.hGetContents
writeAll :: Handle -> ByteString -> IO ()
writeAll = Handle -> ByteString -> IO ()
B.hPut
instance Streamable L.ByteString where
readAll :: Handle -> IO ByteString
readAll = Handle -> IO ByteString
L.hGetContents
writeAll :: Handle -> ByteString -> IO ()
writeAll = Handle -> ByteString -> IO ()
L.hPut
instance Streamable String where
readAll :: Handle -> IO FilePath
readAll = Handle -> IO FilePath
I.hGetContents
writeAll :: Handle -> FilePath -> IO ()
writeAll = Handle -> FilePath -> IO ()
I.hPutStr
modifyInPlace :: Streamable a => (a -> a)
-> FilePath
-> IO ()
modifyInPlace :: forall a. Streamable a => (a -> a) -> FilePath -> IO ()
modifyInPlace = (FilePath -> FilePath -> IO ()) -> (a -> a) -> FilePath -> IO ()
forall a.
Streamable a =>
(FilePath -> FilePath -> IO ()) -> (a -> a) -> FilePath -> IO ()
modifyWith ((FilePath -> FilePath -> IO ()) -> FilePath -> FilePath -> IO ()
forall a b c. (a -> b -> c) -> b -> a -> c
flip FilePath -> FilePath -> IO ()
rename)
modifyWithBackup :: Streamable a =>
(FilePath -> FilePath)
-> (a -> a)
-> FilePath
-> IO ()
modifyWithBackup :: forall a.
Streamable a =>
(FilePath -> FilePath) -> (a -> a) -> FilePath -> IO ()
modifyWithBackup FilePath -> FilePath
f = (FilePath -> FilePath -> IO ()) -> (a -> a) -> FilePath -> IO ()
forall a.
Streamable a =>
(FilePath -> FilePath -> IO ()) -> (a -> a) -> FilePath -> IO ()
modifyWith FilePath -> FilePath -> IO ()
backup
where backup :: FilePath -> FilePath -> IO ()
backup FilePath
path FilePath
tmpPath = (FilePath -> FilePath) -> FilePath -> IO ()
renameWith FilePath -> FilePath
f FilePath
path IO () -> IO () -> IO ()
forall a b. IO a -> IO b -> IO b
forall (m :: * -> *) a b. Monad m => m a -> m b -> m b
>> FilePath -> FilePath -> IO ()
rename FilePath
tmpPath FilePath
path
modifyWith :: Streamable a =>
(FilePath -> FilePath -> IO ())
-> (a -> a)
-> FilePath
-> IO ()
modifyWith :: forall a.
Streamable a =>
(FilePath -> FilePath -> IO ()) -> (a -> a) -> FilePath -> IO ()
modifyWith FilePath -> FilePath -> IO ()
after a -> a
transform FilePath
path =
IO Handle -> (Handle -> IO ()) -> (Handle -> IO ()) -> IO ()
forall a b c. IO a -> (a -> IO b) -> (a -> IO c) -> IO c
bracket (FilePath -> IOMode -> IO Handle
openFile FilePath
path IOMode
ReadMode) Handle -> IO ()
hClose ((Handle -> IO ()) -> IO ()) -> (Handle -> IO ()) -> IO ()
forall a b. (a -> b) -> a -> b
$ \Handle
ih -> do
(tmpPath, oh) <- FilePath -> IO (FilePath, Handle)
mkstemp (FilePath
path FilePath -> FilePath -> FilePath
forall a. [a] -> [a] -> [a]
++ FilePath
"XXXXXX")
let ignore = () -> IO ()
forall a. a -> IO a
forall (m :: * -> *) a. Monad m => a -> m a
return ()
nukeTmp = (IOException -> IO ()) -> IO () -> IO ()
forall e a. Exception e => (e -> IO a) -> IO a -> IO a
handle (\(IOException
_::IOException) -> IO ()
ignore) (FilePath -> IO ()
removeFile FilePath
tmpPath)
handle (\(IOException
e::IOException) -> IO ()
nukeTmp IO () -> IO () -> IO ()
forall a b. IO a -> IO b -> IO b
forall (m :: * -> *) a b. Monad m => m a -> m b -> m b
>> IOException -> IO ()
forall a e. (HasCallStack, Exception e) => e -> a
throw IOException
e) $ do
bracket_ ignore (hClose oh) $
readAll ih >>= return . transform >>= writeAll oh
handle (\(IOException
_::IOException) -> IO ()
nukeTmp) $ do
mode <- fileMode `liftM` getFileStatus path
setFileMode tmpPath (mode .&. 0777)
after path tmpPath