From 3e6d93182a9b95c8c371f64eae392b354cbc40ce Mon Sep 17 00:00:00 2001 From: Julian Ospald Date: Wed, 11 Apr 2018 00:44:47 +0200 Subject: [PATCH] Abstract over Path more properly We don't expect "Path Abs" everywhere anymore. The functions have been made to be more generic. A user can still pass absolute paths, so we don't lose any safety. However, some function implementations may be more tricky. --- src/HPath/IO.hs | 208 ++++++++++++++++++++----------------- src/HPath/IO.hs-boot | 3 +- src/HPath/IO/Errors.hs | 92 ++++++++-------- test/HPath/IO/ToAbsSpec.hs | 27 +++++ 4 files changed, 191 insertions(+), 139 deletions(-) create mode 100644 test/HPath/IO/ToAbsSpec.hs diff --git a/src/HPath/IO.hs b/src/HPath/IO.hs index 429e939..e883e3b 100644 --- a/src/HPath/IO.hs +++ b/src/HPath/IO.hs @@ -8,8 +8,11 @@ -- Portability : portable -- -- This module provides high-level IO related file operations like --- copy, delete, move and so on. It only operates on /Path Abs/ which --- guarantees us well-typed paths which are absolute. +-- copy, delete, move and so on. It only operates on /Path x/ which +-- guarantees us well-typed paths. Passing in /Path Abs/ to any +-- of these functions generally increases safety. Passing /Path Rel/ +-- may trigger looking up the current directory via `getcwd` in some +-- cases where it cannot be avoided. -- -- Some functions are just path-safe wrappers around -- unix functions, others have stricter exception handling @@ -75,6 +78,7 @@ module HPath.IO , getFileType -- * Others , canonicalizePath + , toAbs ) where @@ -184,6 +188,7 @@ import System.Posix.ByteString import System.Posix.Directory.ByteString ( createDirectory + , getWorkingDirectory , removeDirectory ) import System.Posix.Directory.Traversals @@ -326,9 +331,9 @@ data CopyMode = Strict -- ^ fail if any target exists -- Throws in `Strict` CopyMode only: -- -- - `AlreadyExists` if destination already exists -copyDirRecursive :: Path Abs -- ^ source dir - -> Path Abs -- ^ destination (parent dirs - -- are not automatically created) +copyDirRecursive :: Path b1 -- ^ source dir + -> Path b2 -- ^ destination (parent dirs + -- are not automatically created) -> CopyMode -> RecursiveErrorMode -> IO () @@ -344,27 +349,27 @@ copyDirRecursive fromp destdirp cm rm (throwIO . RecursiveFailure $ collectedExceptions) where go :: IORef [(RecursiveFailureHint, IOException)] - -> Path Abs -> Path Abs -> IO () - go ce fromp' destdirp' = do + -> Path b1 -> Path b2 -> IO () + go ce fromp'@(MkPath fromBS) destdirp'@(MkPath destdirpBS) = do -- NOTE: order is important here, so we don't get empty directories -- on failure -- get the contents of the source dir - contents <- handleIOE (ReadContentsFailed fromp' destdirp') ce [] $ do + contents <- handleIOE (ReadContentsFailed fromBS destdirpBS) ce [] $ do contents <- getDirsFiles fromp' -- create the destination dir and -- only return contents if we succeed - handleIOE (CreateDirFailed fromp' destdirp') ce [] $ do - fmode' <- PF.fileMode <$> PF.getSymbolicLinkStatus (fromAbs fromp') + handleIOE (CreateDirFailed fromBS destdirpBS) ce [] $ do + fmode' <- PF.fileMode <$> PF.getSymbolicLinkStatus fromBS case cm of - Strict -> createDirectory (fromAbs destdirp') fmode' - Overwrite -> catchIOError (createDirectory (fromAbs destdirp') + Strict -> createDirectory destdirpBS fmode' + Overwrite -> catchIOError (createDirectory destdirpBS fmode') $ \e -> case ioeGetErrorType e of - AlreadyExists -> setFileMode (fromAbs destdirp') + AlreadyExists -> setFileMode destdirpBS fmode' _ -> ioError e return contents @@ -378,10 +383,10 @@ copyDirRecursive fromp destdirp cm rm ftype <- getFileType f newdest <- (destdirp' ) <$> basename f case ftype of - SymbolicLink -> handleIOE (RecreateSymlinkFailed f newdest) ce () + SymbolicLink -> handleIOE (RecreateSymlinkFailed (toFilePath f) (toFilePath newdest)) ce () $ recreateSymlink f newdest cm Directory -> go ce f newdest - RegularFile -> handleIOE (CopyFileFailed f newdest) ce () + RegularFile -> handleIOE (CopyFileFailed (toFilePath f) (toFilePath newdest)) ce () $ copyFile f newdest cm _ -> return () @@ -422,23 +427,23 @@ copyDirRecursive fromp destdirp cm rm -- - `UnsatisfiedConstraints` if destination file is non-empty directory -- -- Note: calls `symlink` -recreateSymlink :: Path Abs -- ^ the old symlink file - -> Path Abs -- ^ destination file +recreateSymlink :: Path b1 -- ^ the old symlink file + -> Path b2 -- ^ destination file -> CopyMode -> IO () -recreateSymlink symsource newsym cm +recreateSymlink symsource@(MkPath symsourceBS) newsym@(MkPath newsymBS) cm = do throwSameFile symsource newsym - sympoint <- readSymbolicLink (fromAbs symsource) + sympoint <- readSymbolicLink symsourceBS case cm of Strict -> return () Overwrite -> do - writable <- isWritable (dirname newsym) + writable <- toAbs newsym >>= isWritable isfile <- doesFileExist newsym isdir <- doesDirectoryExist newsym when (writable && isfile) (deleteFile newsym) when (writable && isdir) (deleteDir newsym) - createSymbolicLink sympoint (fromAbs newsym) + createSymbolicLink sympoint newsymBS -- |Copies the given regular file to the given destination. @@ -476,8 +481,8 @@ recreateSymlink symsource newsym cm -- - `AlreadyExists` if destination already exists -- -- Note: calls `sendfile` and possibly `read`/`write` as fallback -copyFile :: Path Abs -- ^ source file - -> Path Abs -- ^ destination file +copyFile :: Path b1 -- ^ source file + -> Path b2 -- ^ destination file -> CopyMode -> IO () copyFile from to cm = do @@ -496,8 +501,8 @@ copyFile from to cm = do -- figure out if we can still copy by deleting it first PermissionDenied -> do exists <- doesFileExist to - writable <- isWritable (dirname to) - if exists && writable + writable <- toAbs to >>= isWritable + if (exists && writable) then deleteFile to >> copyFile from to Strict else ioError e _ -> ioError e @@ -505,18 +510,17 @@ copyFile from to cm = do _copyFile :: [SPDF.Flags] -> [SPDF.Flags] - -> Path Abs -- ^ source file - -> Path Abs -- ^ destination file + -> Path b1 -- ^ source file + -> Path b2 -- ^ destination file -> IO () -_copyFile sflags dflags from to +_copyFile sflags dflags (MkPath fromBS) to@(MkPath toBS) = -- from sendfile(2) manpage: -- Applications may wish to fall back to read(2)/write(2) in -- the case where sendfile() fails with EINVAL or ENOSYS. - withAbsPath to $ \to' -> withAbsPath from $ \from' -> - catchErrno [eINVAL, eNOSYS] - (sendFileCopy from' to') - (void $ readWriteCopy from' to') + catchErrno [eINVAL, eNOSYS] + (sendFileCopy fromBS toBS) + (void $ readWriteCopy fromBS toBS) where copyWith copyAction source dest = bracket (openFd source SPI.ReadOnly sflags Nothing) @@ -561,8 +565,8 @@ _copyFile sflags dflags from to -- -- * examines filetypes explicitly -- * calls `copyDirRecursive` for directories -easyCopy :: Path Abs - -> Path Abs +easyCopy :: Path b1 + -> Path b2 -> CopyMode -> RecursiveErrorMode -> IO () @@ -591,8 +595,8 @@ easyCopy from to cm rm = do -- - `InappropriateType` for wrong file type (directory) -- - `NoSuchThing` if the file does not exist -- - `PermissionDenied` if the directory cannot be read -deleteFile :: Path Abs -> IO () -deleteFile p = withAbsPath p removeLink +deleteFile :: Path b -> IO () +deleteFile (MkPath p) = removeLink p -- |Deletes the given directory, which must be empty, never symlinks. @@ -606,8 +610,8 @@ deleteFile p = withAbsPath p removeLink -- - `PermissionDenied` if we can't open or write to parent directory -- -- Notes: calls `rmdir` -deleteDir :: Path Abs -> IO () -deleteDir p = withAbsPath p removeDirectory +deleteDir :: Path b -> IO () +deleteDir (MkPath p) = removeDirectory p -- |Deletes the given directory recursively. Does not follow symbolic @@ -629,7 +633,7 @@ deleteDir p = withAbsPath p removeDirectory -- - `InappropriateType` for wrong file type (regular file) -- - `NoSuchThing` if directory does not exist -- - `PermissionDenied` if we can't open or write to parent directory -deleteDirRecursive :: Path Abs -> IO () +deleteDirRecursive :: Path b -> IO () deleteDirRecursive p = catchErrno [eNOTEMPTY, eEXIST] (deleteDir p) @@ -654,7 +658,7 @@ deleteDirRecursive p = -- -- * examines filetypes explicitly -- * calls `deleteDirRecursive` for directories -easyDelete :: Path Abs -> IO () +easyDelete :: Path b -> IO () easyDelete p = do ftype <- getFileType p case ftype of @@ -673,21 +677,18 @@ easyDelete p = do -- |Opens a file appropriately by invoking xdg-open. The file type -- is not checked. This forks a process. -openFile :: Path Abs +openFile :: Path b -> IO ProcessID -openFile p = - withAbsPath p $ \fp -> - SPP.forkProcess $ SPP.executeFile "xdg-open" True [fp] Nothing +openFile (MkPath fp) = + SPP.forkProcess $ SPP.executeFile "xdg-open" True [fp] Nothing -- |Executes a program with the given arguments. This forks a process. -executeFile :: Path Abs -- ^ program +executeFile :: Path b -- ^ program -> [ByteString] -- ^ arguments -> IO ProcessID -executeFile fp args - = withAbsPath fp $ \fpb -> - SPP.forkProcess - $ SPP.executeFile fpb True args Nothing +executeFile (MkPath fp) args = + SPP.forkProcess $ SPP.executeFile fp True args Nothing @@ -706,9 +707,9 @@ executeFile fp args -- - `AlreadyExists` if destination already exists -- - `NoSuchThing` if any of the parent components of the path -- do not exist -createRegularFile :: FileMode -> Path Abs -> IO () -createRegularFile fm dest = - bracket (SPI.openFd (fromAbs dest) SPI.WriteOnly (Just fm) +createRegularFile :: FileMode -> Path b -> IO () +createRegularFile fm (MkPath destBS) = + bracket (SPI.openFd destBS SPI.WriteOnly (Just fm) (SPI.defaultFileFlags { exclusive = True })) SPI.closeFd (\_ -> return ()) @@ -722,8 +723,8 @@ createRegularFile fm dest = -- - `AlreadyExists` if destination already exists -- - `NoSuchThing` if any of the parent components of the path -- do not exist -createDir :: FileMode -> Path Abs -> IO () -createDir fm dest = createDirectory (fromAbs dest) fm +createDir :: FileMode -> Path b -> IO () +createDir fm (MkPath destBS) = createDirectory destBS fm -- |Create an empty directory at the given directory with the given filename. @@ -744,15 +745,19 @@ createDir fm dest = createDirectory (fromAbs dest) fm -- exist and cannot be written to -- - `AlreadyExists` if destination already exists and -- is not a directory -createDirRecursive :: FileMode -> Path Abs -> IO () -createDirRecursive fm dest = - catchIOError (createDirectory (fromAbs dest) fm) $ \e -> do - errno <- getErrno - case errno of - en | en == eEXIST -> unlessM (doesDirectoryExist dest) (ioError e) - | en == eNOENT -> createDirRecursive fm (dirname dest) - >> createDirectory (fromAbs dest) fm - | otherwise -> ioError e +createDirRecursive :: FileMode -> Path b -> IO () +createDirRecursive fm p = + toAbs p >>= go + where + go :: Path Abs -> IO () + go dest@(MkPath destBS) = do + catchIOError (createDirectory destBS fm) $ \e -> do + errno <- getErrno + case errno of + en | en == eEXIST -> unlessM (doesDirectoryExist dest) (ioError e) + | en == eNOENT -> createDirRecursive fm (dirname dest) + >> createDirectory destBS fm + | otherwise -> ioError e -- |Create a symlink. @@ -765,11 +770,11 @@ createDirRecursive fm dest = -- do not exist -- -- Note: calls `symlink` -createSymlink :: Path Abs -- ^ destination file +createSymlink :: Path b -- ^ destination file -> ByteString -- ^ path the symlink points to -> IO () -createSymlink dest sympoint - = createSymbolicLink sympoint (fromAbs dest) +createSymlink (MkPath destBS) sympoint + = createSymbolicLink sympoint destBS @@ -799,12 +804,12 @@ createSymlink dest sympoint -- (`HPathIOException`) -- -- Note: calls `rename` (but does not allow to rename over existing files) -renameFile :: Path Abs -> Path Abs -> IO () -renameFile fromf tof = do +renameFile :: Path b1 -> Path b2 -> IO () +renameFile fromf@(MkPath fromfBS) tof@(MkPath tofBS) = do throwSameFile fromf tof throwFileDoesExist tof throwDirDoesExist tof - rename (fromAbs fromf) (fromAbs tof) + rename fromfBS tofBS -- |Move a file. This also works across devices by copy-delete fallback. @@ -836,8 +841,8 @@ renameFile fromf tof = do -- - `AlreadyExists` if destination already exists -- -- Note: calls `rename` (but does not allow to rename over existing files) -moveFile :: Path Abs -- ^ file to move - -> Path Abs -- ^ destination +moveFile :: Path b1 -- ^ file to move + -> Path b2 -- ^ destination -> CopyMode -> IO () moveFile from to cm = do @@ -848,7 +853,7 @@ moveFile from to cm = do easyDelete from Overwrite -> do ft <- getFileType from - writable <- isWritable $ dirname to + writable <- toAbs to >>= isWritable case ft of RegularFile -> do exists <- doesFileExist to @@ -891,8 +896,8 @@ moveFile from to cm = do -- - `PermissionDenied` if we cannot read the file or the directory -- containting it -- - `NoSuchThing` if the file does not exist -readFile :: Path Abs -> IO ByteString -readFile p = withAbsPath p $ \fp -> +readFile :: Path b -> IO ByteString +readFile (MkPath fp) = bracket (openFd fp SPI.ReadOnly [] Nothing) (SPI.closeFd) $ \fd -> do stat <- PF.getFdStatus fd let fsize = PF.fileSize stat @@ -913,8 +918,8 @@ readFile p = withAbsPath p $ \fp -> -- - `PermissionDenied` if we cannot read the file or the directory -- containting it -- - `NoSuchThing` if the file does not exist -readFileEOF :: Path Abs -> IO L.ByteString -readFileEOF p = withAbsPath p $ \fp -> +readFileEOF :: Path b -> IO L.ByteString +readFileEOF (MkPath fp) = bracket (openFd fp SPI.ReadOnly [] Nothing) (SPI.closeFd) $ \fd -> allocaBytes (fromIntegral bufSize) $ \buf -> read' fd buf mempty where @@ -948,8 +953,8 @@ readFileEOF p = withAbsPath p $ \fp -> -- - `PermissionDenied` if we cannot read the file or the directory -- containting it -- - `NoSuchThing` if the file does not exist -writeFile :: Path Abs -> ByteString -> IO () -writeFile p bs = withAbsPath p $ \fp -> +writeFile :: Path b -> ByteString -> IO () +writeFile (MkPath fp) bs = bracket (openFd fp SPI.WriteOnly [SPDF.oTrunc] Nothing) (SPI.closeFd) $ \fd -> void $ SPB.fdWrite fd bs @@ -963,8 +968,8 @@ writeFile p bs = withAbsPath p $ \fp -> -- - `PermissionDenied` if we cannot read the file or the directory -- containting it -- - `NoSuchThing` if the file does not exist -appendFile :: Path Abs -> ByteString -> IO () -appendFile p bs = withAbsPath p $ \fp -> +appendFile :: Path b -> ByteString -> IO () +appendFile (MkPath fp) bs = bracket (openFd fp SPI.WriteOnly [SPDF.oAppend] Nothing) (SPI.closeFd) $ \fd -> void $ SPB.fdWrite fd bs @@ -1015,15 +1020,14 @@ newDirPerms -- - `InappropriateType` if file type is wrong (symlink to file) -- - `InappropriateType` if file type is wrong (symlink to dir) -- - `PermissionDenied` if directory cannot be opened -getDirsFiles :: Path Abs -- ^ dir to read - -> IO [Path Abs] -getDirsFiles p = - withAbsPath p $ \fp -> do - fd <- openFd fp SPI.ReadOnly [SPDF.oNofollow] Nothing - return - . catMaybes - . fmap (\x -> () p <$> (parseMaybe . snd $ x)) - =<< getDirectoryContents' fd +getDirsFiles :: Path b -- ^ dir to read + -> IO [Path b] +getDirsFiles p@(MkPath fp) = do + fd <- openFd fp SPI.ReadOnly [SPDF.oNofollow] Nothing + return + . catMaybes + . fmap (\x -> () p <$> (parseMaybe . snd $ x)) + =<< getDirectoryContents' fd where parseMaybe :: ByteString -> Maybe (Path Fn) parseMaybe = parseFn @@ -1043,9 +1047,9 @@ getDirsFiles p = -- -- - `NoSuchThing` if the file does not exist -- - `PermissionDenied` if any part of the path is not accessible -getFileType :: Path Abs -> IO FileType -getFileType p = do - fs <- PF.getSymbolicLinkStatus (fromAbs p) +getFileType :: Path b -> IO FileType +getFileType (MkPath fp) = do + fs <- PF.getSymbolicLinkStatus fp decide fs where decide fs @@ -1066,13 +1070,29 @@ getFileType p = do --- |Applies `realpath` on the given absolute path. +-- |Applies `realpath` on the given path. -- -- Throws: -- -- - `NoSuchThing` if the file at the given path does not exist -- - `NoSuchThing` if the symlink is broken -canonicalizePath :: Path Abs -> IO (Path Abs) +canonicalizePath :: Path b -> IO (Path Abs) canonicalizePath (MkPath l) = do nl <- SPDT.realpath l return $ MkPath nl + + +-- |Converts any path to an absolute path. +-- This is done in the following way: +-- +-- - if the path is already an absolute one, just return it +-- - if it's a relative path, prepend the current directory to it +toAbs :: Path b -> IO (Path Abs) +toAbs (MkPath bs) = do + let mabs = parseAbs bs :: Maybe (Path Abs) + case mabs of + Just a -> return a + Nothing -> do + cwd <- getWorkingDirectory >>= parseAbs + rel <- parseRel bs -- we know it must be relative now + return $ cwd rel diff --git a/src/HPath/IO.hs-boot b/src/HPath/IO.hs-boot index bee2a28..d16bf75 100644 --- a/src/HPath/IO.hs-boot +++ b/src/HPath/IO.hs-boot @@ -3,5 +3,6 @@ module HPath.IO where import HPath -canonicalizePath :: Path Abs -> IO (Path Abs) +canonicalizePath :: Path b -> IO (Path Abs) +toAbs :: Path b -> IO (Path Abs) diff --git a/src/HPath/IO/Errors.hs b/src/HPath/IO/Errors.hs index ae151db..3e87c1c 100644 --- a/src/HPath/IO/Errors.hs +++ b/src/HPath/IO/Errors.hs @@ -84,9 +84,14 @@ import GHC.IO.Exception IOErrorType ) import HPath +import HPath.Internal + ( + Path(..) + ) import {-# SOURCE #-} HPath.IO ( canonicalizePath + , toAbs ) import System.IO.Error ( @@ -119,10 +124,10 @@ data HPathIOException = SameFile ByteString ByteString -- -- The first argument to the data constructor is always the -- source and the second the destination. -data RecursiveFailureHint = ReadContentsFailed (Path Abs) (Path Abs) - | CreateDirFailed (Path Abs) (Path Abs) - | CopyFileFailed (Path Abs) (Path Abs) - | RecreateSymlinkFailed (Path Abs) (Path Abs) +data RecursiveFailureHint = ReadContentsFailed ByteString ByteString + | CreateDirFailed ByteString ByteString + | CopyFileFailed ByteString ByteString + | RecreateSymlinkFailed ByteString ByteString deriving (Eq, Show) @@ -169,51 +174,50 @@ isRecreateSymlinkFailed _ = False -- |Throws `AlreadyExists` `IOError` if file exists. -throwFileDoesExist :: Path Abs -> IO () -throwFileDoesExist fp = +throwFileDoesExist :: Path b -> IO () +throwFileDoesExist fp@(MkPath bs) = whenM (doesFileExist fp) (ioError . mkIOError alreadyExistsErrorType "File already exists" Nothing - $ (Just (toString $ fromAbs fp)) + $ (Just (toString $ bs)) ) -- |Throws `AlreadyExists` `IOError` if directory exists. -throwDirDoesExist :: Path Abs -> IO () -throwDirDoesExist fp = +throwDirDoesExist :: Path b -> IO () +throwDirDoesExist fp@(MkPath bs) = whenM (doesDirectoryExist fp) (ioError . mkIOError alreadyExistsErrorType "Directory already exists" Nothing - $ (Just (toString $ fromAbs fp)) + $ (Just (toString $ bs)) ) -- |Uses `isSameFile` and throws `SameFile` if it returns True. -throwSameFile :: Path Abs - -> Path Abs +throwSameFile :: Path b1 + -> Path b2 -> IO () -throwSameFile fp1 fp2 = +throwSameFile fp1@(MkPath bs1) fp2@(MkPath bs2) = whenM (sameFile fp1 fp2) - (throwIO $ SameFile (fromAbs fp1) (fromAbs fp2)) + (throwIO $ SameFile bs1 bs2) -- |Check if the files are the same by examining device and file id. -- This follows symbolic links. -sameFile :: Path Abs -> Path Abs -> IO Bool -sameFile fp1 fp2 = - withAbsPath fp1 $ \fp1' -> withAbsPath fp2 $ \fp2' -> - handleIOError (\_ -> return False) $ do - fs1 <- getFileStatus fp1' - fs2 <- getFileStatus fp2' +sameFile :: Path b1 -> Path b2 -> IO Bool +sameFile (MkPath fp1) (MkPath fp2) = + handleIOError (\_ -> return False) $ do + fs1 <- getFileStatus fp1 + fs2 <- getFileStatus fp2 - if ((PF.deviceID fs1, PF.fileID fs1) == - (PF.deviceID fs2, PF.fileID fs2)) - then return True - else return False + if ((PF.deviceID fs1, PF.fileID fs1) == + (PF.deviceID fs2, PF.fileID fs2)) + then return True + else return False -- TODO: make this more robust when destination does not exist @@ -221,54 +225,54 @@ sameFile fp1 fp2 = -- within the source directory by comparing the device+file ID of the -- source directory with all device+file IDs of the parent directories -- of the destination. -throwDestinationInSource :: Path Abs -- ^ source dir - -> Path Abs -- ^ full destination, @dirname dest@ - -- must exist +throwDestinationInSource :: Path b1 -- ^ source dir + -> Path b2 -- ^ full destination, @dirname dest@ + -- must exist -> IO () -throwDestinationInSource source dest = do +throwDestinationInSource (MkPath sbs) dest@(MkPath dbs) = do + destAbs <- toAbs dest dest' <- (\x -> maybe x (\y -> x y) $ basename dest) - <$> (canonicalizePath $ dirname dest) + <$> (canonicalizePath $ dirname destAbs) dids <- forM (getAllParents dest') $ \p -> do fs <- PF.getSymbolicLinkStatus (fromAbs p) return (PF.deviceID fs, PF.fileID fs) sid <- fmap (\x -> (PF.deviceID x, PF.fileID x)) - $ PF.getFileStatus (fromAbs source) + $ PF.getFileStatus sbs when (elem sid dids) - (throwIO $ DestinationInSource (fromAbs dest) - (fromAbs source)) + (throwIO $ DestinationInSource dbs sbs) -- |Checks if the given file exists and is not a directory. -- Does not follow symlinks. -doesFileExist :: Path Abs -> IO Bool -doesFileExist fp = +doesFileExist :: Path b -> IO Bool +doesFileExist (MkPath bs) = handleIOError (\_ -> return False) $ do - fs <- PF.getSymbolicLinkStatus (fromAbs fp) + fs <- PF.getSymbolicLinkStatus bs return $ not . PF.isDirectory $ fs -- |Checks if the given file exists and is a directory. -- Does not follow symlinks. -doesDirectoryExist :: Path Abs -> IO Bool -doesDirectoryExist fp = +doesDirectoryExist :: Path b -> IO Bool +doesDirectoryExist (MkPath bs) = handleIOError (\_ -> return False) $ do - fs <- PF.getSymbolicLinkStatus (fromAbs fp) + fs <- PF.getSymbolicLinkStatus bs return $ PF.isDirectory fs -- |Checks whether a file or folder is writable. -isWritable :: Path Abs -> IO Bool -isWritable fp = +isWritable :: Path b -> IO Bool +isWritable (MkPath bs) = handleIOError (\_ -> return False) $ - fileAccess (fromAbs fp) False True False + fileAccess bs False True False -- |Checks whether the directory at the given path exists and can be -- opened. This invokes `openDirStream` which follows symlinks. -canOpenDirectory :: Path Abs -> IO Bool -canOpenDirectory fp = +canOpenDirectory :: Path b -> IO Bool +canOpenDirectory (MkPath bs) = handleIOError (\_ -> return False) $ do - bracket (PFD.openDirStream . fromAbs $ fp) + bracket (PFD.openDirStream bs) PFD.closeDirStream (\_ -> return ()) return True diff --git a/test/HPath/IO/ToAbsSpec.hs b/test/HPath/IO/ToAbsSpec.hs new file mode 100644 index 0000000..a4bd5f3 --- /dev/null +++ b/test/HPath/IO/ToAbsSpec.hs @@ -0,0 +1,27 @@ +{-# LANGUAGE OverloadedStrings #-} + + +module HPath.IO.ToAbsSpec where + + +import Test.Hspec +import HPath +import HPath.IO + + + +spec :: Spec +spec = describe "HPath.IO.toAbs" $ do + + -- successes -- + it "toAbs returns absolute paths unchanged" $ do + p1 <- parseAbs "/a/b/c/d" + to <- toAbs p1 + p1 `shouldBe` to + + it "toAbs returns even existing absolute paths unchanged" $ do + p1 <- parseAbs "/home" + to <- toAbs p1 + p1 `shouldBe` to + +