module System.Directory.Internal.Common where
import Prelude ()
import System.Directory.Internal.Prelude
import System.FilePath
( addTrailingPathSeparator
, hasTrailingPathSeparator
, isPathSeparator
, isRelative
, joinDrive
, joinPath
, normalise
, pathSeparator
, pathSeparators
, splitDirectories
, splitDrive
)
newtype ListT m a = ListT { unListT :: m (Maybe (a, ListT m a)) }
emptyListT :: Applicative m => ListT m a
emptyListT = ListT (pure Nothing)
maybeToListT :: Applicative m => m (Maybe a) -> ListT m a
maybeToListT m = ListT (((\ x -> (x, emptyListT)) <$>) <$> m)
listToListT :: Applicative m => [a] -> ListT m a
listToListT [] = emptyListT
listToListT (x : xs) = ListT (pure (Just (x, listToListT xs)))
liftJoinListT :: Monad m => m (ListT m a) -> ListT m a
liftJoinListT m = ListT (m >>= unListT)
listTHead :: Functor m => ListT m a -> m (Maybe a)
listTHead (ListT m) = (fst <$>) <$> m
listTToList :: Monad m => ListT m a -> m [a]
listTToList (ListT m) = do
mx <- m
case mx of
Nothing -> return []
Just (x, m') -> do
xs <- listTToList m'
return (x : xs)
andM :: Monad m => m Bool -> m Bool -> m Bool
andM mx my = do
x <- mx
if x
then my
else return x
sequenceWithIOErrors_ :: [IO ()] -> IO ()
sequenceWithIOErrors_ actions = go (Right ()) actions
where
go :: Either IOError () -> [IO ()] -> IO ()
go (Left e) [] = ioError e
go (Right ()) [] = pure ()
go s (m : ms) = s `seq` do
r <- tryIOError m
go (thenEither s r) ms
thenEither :: Either b a -> Either b a -> Either b a
thenEither x@(Left _) _ = x
thenEither _ y = y
tryIOErrorType :: (IOError -> Bool) -> IO a -> IO (Either IOError a)
tryIOErrorType check action = do
result <- tryIOError action
case result of
Left err -> if check err then pure (Left err) else throwIO err
Right val -> pure (Right val)
ignoreIOExceptions :: IO () -> IO ()
ignoreIOExceptions io = io `catchIOError` (\_ -> pure ())
specializeErrorString :: String -> (IOError -> Bool) -> IO a -> IO a
specializeErrorString str errType action = do
mx <- tryIOErrorType errType action
case mx of
Left e -> throwIO (ioeSetErrorString e str)
Right x -> pure x
ioeAddLocation :: IOError -> String -> IOError
ioeAddLocation e loc = do
ioeSetLocation e newLoc
where
newLoc = loc <> if null oldLoc then "" else ":" <> oldLoc
oldLoc = ioeGetLocation e
expandDots :: [FilePath] -> [FilePath]
expandDots = reverse . go []
where
go ys' xs' =
case xs' of
[] -> ys'
x : xs ->
case x of
"." -> go ys' xs
".." ->
case ys' of
[] -> go (x : ys') xs
".." : _ -> go (x : ys') xs
_ : ys -> go ys xs
_ -> go (x : ys') xs
normalisePathSeps :: FilePath -> FilePath
normalisePathSeps p = (\ c -> if isPathSeparator c then pathSeparator else c) <$> p
normaliseTrailingSep :: FilePath -> FilePath
normaliseTrailingSep path = do
let path' = reverse path
let (sep, path'') = span isPathSeparator path'
let addSep = if null sep then id else (pathSeparator :)
reverse (addSep path'')
emptyToCurDir :: FilePath -> FilePath
emptyToCurDir "" = "."
emptyToCurDir path = path
simplifyPosix :: FilePath -> FilePath
simplifyPosix "" = ""
simplifyPosix path = normalise path
simplifyWindows :: FilePath -> FilePath
simplifyWindows "" = ""
simplifyWindows path =
case drive' of
"\\\\?\\" -> drive' <> subpath
_ -> simplifiedPath
where
simplifiedPath = joinDrive drive' subpath'
(drive, subpath) = splitDrive path
drive' = upperDrive (normaliseTrailingSep (normalisePathSeps drive))
subpath' = appendSep . avoidEmpty . prependSep . joinPath .
stripPardirs . expandDots . skipSeps .
splitDirectories $ subpath
upperDrive d = case d of
c : ':' : s | isAlpha c && all isPathSeparator s -> toUpper c : ':' : s
_ -> d
skipSeps = filter (not . (`elem` (pure <$> pathSeparators)))
stripPardirs | pathIsAbsolute || subpathIsAbsolute = dropWhile (== "..")
| otherwise = id
prependSep | subpathIsAbsolute = (pathSeparator :)
| otherwise = id
avoidEmpty | not pathIsAbsolute
&& (null drive || hasTrailingPathSep)
= emptyToCurDir
| otherwise = id
appendSep p | hasTrailingPathSep
&& not (pathIsAbsolute && null p)
= addTrailingPathSeparator p
| otherwise = p
pathIsAbsolute = not (isRelative path)
subpathIsAbsolute = any isPathSeparator (take 1 subpath)
hasTrailingPathSep = hasTrailingPathSeparator subpath
data FileType = File
| SymbolicLink
| Directory
| DirectoryLink
deriving (Bounded, Enum, Eq, Ord, Read, Show)
fileTypeIsDirectory :: FileType -> Bool
fileTypeIsDirectory Directory = True
fileTypeIsDirectory DirectoryLink = True
fileTypeIsDirectory _ = False
fileTypeIsLink :: FileType -> Bool
fileTypeIsLink SymbolicLink = True
fileTypeIsLink DirectoryLink = True
fileTypeIsLink _ = False
data Permissions
= Permissions
{ readable :: Bool
, writable :: Bool
, executable :: Bool
, searchable :: Bool
} deriving (Eq, Ord, Read, Show)
copyFileContents :: FilePath
-> FilePath
-> IO ()
copyFileContents fromFPath toFPath =
(`ioeAddLocation` "copyFileContents") `modifyIOError` do
withBinaryFile toFPath WriteMode $ \ hTo ->
copyFileToHandle fromFPath hTo
copyFileToHandle :: FilePath
-> Handle
-> IO ()
copyFileToHandle fromFPath hTo =
(`ioeAddLocation` "copyFileToHandle") `modifyIOError` do
withBinaryFile fromFPath ReadMode $ \ hFrom ->
copyHandleData hFrom hTo
copyHandleData :: Handle
-> Handle
-> IO ()
copyHandleData hFrom hTo =
(`ioeAddLocation` "copyData") `modifyIOError` do
allocaBytes bufferSize go
where
bufferSize = 131072
go buffer = do
count <- hGetBuf hFrom buffer bufferSize
when (count > 0) $ do
hPutBuf hTo buffer count
go buffer
data XdgDirectory
= XdgData
| XdgConfig
| XdgCache
deriving (Bounded, Enum, Eq, Ord, Read, Show)
data XdgDirectoryList
= XdgDataDirs
| XdgConfigDirs
deriving (Bounded, Enum, Eq, Ord, Read, Show)