packages feed

hpath 0.8.1 → 0.9.0

raw patch · 10 files changed

+592/−142 lines, 10 filesPVP ok

version bump matches the API change (PVP)

API changes (from Hackage documentation)

+ HPath.IO: toAbs :: Path b -> IO (Path Abs)
- HPath.IO: appendFile :: Path Abs -> ByteString -> IO ()
+ HPath.IO: appendFile :: Path b -> ByteString -> IO ()
- HPath.IO: canonicalizePath :: Path Abs -> IO (Path Abs)
+ HPath.IO: canonicalizePath :: Path b -> IO (Path Abs)
- HPath.IO: copyDirRecursive :: Path Abs -> Path Abs -> CopyMode -> RecursiveErrorMode -> IO ()
+ HPath.IO: copyDirRecursive :: Path b1 -> Path b2 -> CopyMode -> RecursiveErrorMode -> IO ()
- HPath.IO: copyFile :: Path Abs -> Path Abs -> CopyMode -> IO ()
+ HPath.IO: copyFile :: Path b1 -> Path b2 -> CopyMode -> IO ()
- HPath.IO: createDir :: FileMode -> Path Abs -> IO ()
+ HPath.IO: createDir :: FileMode -> Path b -> IO ()
- HPath.IO: createDirRecursive :: FileMode -> Path Abs -> IO ()
+ HPath.IO: createDirRecursive :: FileMode -> Path b -> IO ()
- HPath.IO: createRegularFile :: FileMode -> Path Abs -> IO ()
+ HPath.IO: createRegularFile :: FileMode -> Path b -> IO ()
- HPath.IO: createSymlink :: Path Abs -> ByteString -> IO ()
+ HPath.IO: createSymlink :: Path b -> ByteString -> IO ()
- HPath.IO: deleteDir :: Path Abs -> IO ()
+ HPath.IO: deleteDir :: Path b -> IO ()
- HPath.IO: deleteDirRecursive :: Path Abs -> IO ()
+ HPath.IO: deleteDirRecursive :: Path b -> IO ()
- HPath.IO: deleteFile :: Path Abs -> IO ()
+ HPath.IO: deleteFile :: Path b -> IO ()
- HPath.IO: easyCopy :: Path Abs -> Path Abs -> CopyMode -> RecursiveErrorMode -> IO ()
+ HPath.IO: easyCopy :: Path b1 -> Path b2 -> CopyMode -> RecursiveErrorMode -> IO ()
- HPath.IO: easyDelete :: Path Abs -> IO ()
+ HPath.IO: easyDelete :: Path b -> IO ()
- HPath.IO: executeFile :: Path Abs -> [ByteString] -> IO ProcessID
+ HPath.IO: executeFile :: Path b -> [ByteString] -> IO ProcessID
- HPath.IO: getDirsFiles :: Path Abs -> IO [Path Abs]
+ HPath.IO: getDirsFiles :: Path b -> IO [Path b]
- HPath.IO: getFileType :: Path Abs -> IO FileType
+ HPath.IO: getFileType :: Path b -> IO FileType
- HPath.IO: moveFile :: Path Abs -> Path Abs -> CopyMode -> IO ()
+ HPath.IO: moveFile :: Path b1 -> Path b2 -> CopyMode -> IO ()
- HPath.IO: openFile :: Path Abs -> IO ProcessID
+ HPath.IO: openFile :: Path b -> IO ProcessID
- HPath.IO: readFile :: Path Abs -> IO ByteString
+ HPath.IO: readFile :: Path b -> IO ByteString
- HPath.IO: readFileEOF :: Path Abs -> IO ByteString
+ HPath.IO: readFileEOF :: Path b -> IO ByteString
- HPath.IO: recreateSymlink :: Path Abs -> Path Abs -> CopyMode -> IO ()
+ HPath.IO: recreateSymlink :: Path b1 -> Path b2 -> CopyMode -> IO ()
- HPath.IO: renameFile :: Path Abs -> Path Abs -> IO ()
+ HPath.IO: renameFile :: Path b1 -> Path b2 -> IO ()
- HPath.IO: writeFile :: Path Abs -> ByteString -> IO ()
+ HPath.IO: writeFile :: Path b -> ByteString -> IO ()
- HPath.IO.Errors: CopyFileFailed :: (Path Abs) -> (Path Abs) -> RecursiveFailureHint
+ HPath.IO.Errors: CopyFileFailed :: ByteString -> ByteString -> RecursiveFailureHint
- HPath.IO.Errors: CreateDirFailed :: (Path Abs) -> (Path Abs) -> RecursiveFailureHint
+ HPath.IO.Errors: CreateDirFailed :: ByteString -> ByteString -> RecursiveFailureHint
- HPath.IO.Errors: ReadContentsFailed :: (Path Abs) -> (Path Abs) -> RecursiveFailureHint
+ HPath.IO.Errors: ReadContentsFailed :: ByteString -> ByteString -> RecursiveFailureHint
- HPath.IO.Errors: RecreateSymlinkFailed :: (Path Abs) -> (Path Abs) -> RecursiveFailureHint
+ HPath.IO.Errors: RecreateSymlinkFailed :: ByteString -> ByteString -> RecursiveFailureHint
- HPath.IO.Errors: canOpenDirectory :: Path Abs -> IO Bool
+ HPath.IO.Errors: canOpenDirectory :: Path b -> IO Bool
- HPath.IO.Errors: doesDirectoryExist :: Path Abs -> IO Bool
+ HPath.IO.Errors: doesDirectoryExist :: Path b -> IO Bool
- HPath.IO.Errors: doesFileExist :: Path Abs -> IO Bool
+ HPath.IO.Errors: doesFileExist :: Path b -> IO Bool
- HPath.IO.Errors: isWritable :: Path Abs -> IO Bool
+ HPath.IO.Errors: isWritable :: Path b -> IO Bool
- HPath.IO.Errors: sameFile :: Path Abs -> Path Abs -> IO Bool
+ HPath.IO.Errors: sameFile :: Path b1 -> Path b2 -> IO Bool
- HPath.IO.Errors: throwDestinationInSource :: Path Abs -> Path Abs -> IO ()
+ HPath.IO.Errors: throwDestinationInSource :: Path b1 -> Path b2 -> IO ()
- HPath.IO.Errors: throwDirDoesExist :: Path Abs -> IO ()
+ HPath.IO.Errors: throwDirDoesExist :: Path b -> IO ()
- HPath.IO.Errors: throwFileDoesExist :: Path Abs -> IO ()
+ HPath.IO.Errors: throwFileDoesExist :: Path b -> IO ()
- HPath.IO.Errors: throwSameFile :: Path Abs -> Path Abs -> IO ()
+ HPath.IO.Errors: throwSameFile :: Path b1 -> Path b2 -> IO ()

Files

CHANGELOG view
@@ -1,3 +1,6 @@+0.9.0+	* don't force "Path Abs" anymore in IO module, abstract more over Path types+	* add 'toAbs' 0.8.1 	* add 'readFile', 'readFileEOF', 'writeFile' and 'appendFile' 0.8.0
hpath.cabal view
@@ -1,5 +1,5 @@ name:                hpath-version:             0.8.1+version:             0.9.0 synopsis:            Support for well-typed paths description:         Support for well-typed paths, utilizing ByteString under the hood. license:             BSD3@@ -9,7 +9,7 @@ copyright:           Julian Ospald 2016 category:            Filesystem build-type:          Simple-cabal-version:       >=1.14+cabal-version:       1.14 extra-source-files:  README.md                      CHANGELOG                      cbits/dirutils.h@@ -75,14 +75,15 @@   Hs-Source-Dirs:       test   Main-Is:              Main.hs   other-modules:+                        HPath.IO.AppendFileSpec                         HPath.IO.CanonicalizePathSpec                         HPath.IO.CopyDirRecursiveCollectFailuresSpec                         HPath.IO.CopyDirRecursiveOverwriteSpec                         HPath.IO.CopyDirRecursiveSpec                         HPath.IO.CopyFileOverwriteSpec                         HPath.IO.CopyFileSpec-                        HPath.IO.CreateDirSpec                         HPath.IO.CreateDirRecursiveSpec+                        HPath.IO.CreateDirSpec                         HPath.IO.CreateRegularFileSpec                         HPath.IO.CreateSymlinkSpec                         HPath.IO.DeleteDirRecursiveSpec@@ -92,9 +93,13 @@                         HPath.IO.GetFileTypeSpec                         HPath.IO.MoveFileOverwriteSpec                         HPath.IO.MoveFileSpec+                        HPath.IO.ReadFileEOFSpec+                        HPath.IO.ReadFileSpec                         HPath.IO.RecreateSymlinkOverwriteSpec                         HPath.IO.RecreateSymlinkSpec                         HPath.IO.RenameFileSpec+                        HPath.IO.ToAbsSpec+                        HPath.IO.WriteFileSpec                         Spec                         Utils   GHC-Options:          -Wall
src/HPath/IO.hs view
@@ -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 @@   , getFileType   -- * Others   , canonicalizePath+  , toAbs   )   where @@ -184,6 +188,7 @@ import System.Posix.Directory.ByteString   (     createDirectory+  , getWorkingDirectory   , removeDirectory   ) import System.Posix.Directory.Traversals@@ -326,9 +331,9 @@ -- 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 @@            (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 @@         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 @@ --    - `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 @@ --    - `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 @@           -- 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 :: [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 @@ -- --    * examines filetypes explicitly --    * calls `copyDirRecursive` for directories-easyCopy :: Path Abs-         -> Path Abs+easyCopy :: Path b1+         -> Path b2          -> CopyMode          -> RecursiveErrorMode          -> IO ()@@ -591,8 +595,8 @@ --    - `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 @@ --    - `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 @@ --    - `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 @@ -- --    * 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 @@  -- |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 @@ --    - `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 @@ --    - `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 @@ --      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 @@ --      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 @@ --       (`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 @@ --    - `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 @@                 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 @@ --     - `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 @@ --     - `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 @@ --     - `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 @@ --     - `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 @@ --     - `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 @@ -- --    - `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 @@   --- |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
src/HPath/IO.hs-boot view
@@ -3,5 +3,6 @@  import HPath -canonicalizePath :: Path Abs -> IO (Path Abs)+canonicalizePath :: Path b -> IO (Path Abs) +toAbs :: Path b -> IO (Path Abs)
src/HPath/IO/Errors.hs view
@@ -84,9 +84,14 @@     IOErrorType   ) import HPath+import HPath.Internal+  (+    Path(..)+  ) import {-# SOURCE #-} HPath.IO   (     canonicalizePath+  , toAbs   ) import System.IO.Error   (@@ -119,10 +124,10 @@ -- -- 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 @@   -- |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 @@ -- 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
+ test/HPath/IO/AppendFileSpec.hs view
@@ -0,0 +1,109 @@+{-# LANGUAGE OverloadedStrings #-}+++module HPath.IO.AppendFileSpec where+++import Test.Hspec+import System.IO.Error+  (+    ioeGetErrorType+  )+import GHC.IO.Exception+  (+    IOErrorType(..)+  )+import System.Process+import Utils++++upTmpDir :: IO ()+upTmpDir = do+  setTmpDir "AppendFileSpec"+  createTmpDir++setupFiles :: IO ()+setupFiles = do+  createRegularFile' "fileWithContent"+  createRegularFile' "fileWithoutContent"+  createSymlink' "inputFileSymL" "fileWithContent"+  createDir' "alreadyExistsD"+  createRegularFile' "noPerms"+  noPerms "noPerms"+  createDir' "noPermsD"+  createRegularFile' "noPermsD/inputFile"+  noPerms "noPermsD"+  writeFile' "fileWithContent" "BLKASL"+++cleanupFiles :: IO ()+cleanupFiles = do+  deleteFile' "fileWithContent"+  deleteFile' "fileWithoutContent"+  deleteFile' "inputFileSymL"+  deleteDir' "alreadyExistsD"+  normalFilePerms "noPerms"+  deleteFile' "noPerms"+  normalDirPerms "noPermsD"+  deleteFile' "noPermsD/inputFile"+  deleteDir' "noPermsD"+++spec :: Spec+spec = beforeAll_ (upTmpDir >> setupFiles) $ afterAll_ cleanupFiles $+  describe "HPath.IO.appendFile" $ do++    -- successes --+    it "appendFile file with content, everything clear" $ do+      appendFile' "fileWithContent" "blahfaselllll"+      out <- readFile' "fileWithContent"+      out `shouldBe` "BLKASLblahfaselllll"++    it "appendFile file with content, everything clear" $ do+      appendFile' "fileWithContent" "gagagaga"+      out <- readFile' "fileWithContent"+      out `shouldBe` "BLKASLblahfaselllllgagagaga"++    it "appendFile file with content, everything clear" $ do+      appendFile' "fileWithContent" ""+      out <- readFile' "fileWithContent"+      out `shouldBe` "BLKASLblahfaselllllgagagaga"++    it "appendFile file without content, everything clear" $ do+      appendFile' "fileWithoutContent" "blahfaselllll"+      out <- readFile' "fileWithoutContent"+      out `shouldBe` "blahfaselllll"++    it "appendFile, everything clear" $ do+      appendFile' "fileWithoutContent" "gagagaga"+      out <- readFile' "fileWithoutContent"+      out `shouldBe` "blahfaselllllgagagaga"++    it "appendFile symlink, everything clear" $ do+      appendFile' "inputFileSymL" "blahfaselllll"+      out <- readFile' "inputFileSymL"+      out `shouldBe` "BLKASLblahfaselllllgagagagablahfaselllll"++    it "appendFile symlink, everything clear" $ do+      appendFile' "inputFileSymL" "gagagaga"+      out <- readFile' "inputFileSymL"+      out `shouldBe` "BLKASLblahfaselllllgagagagablahfaselllllgagagaga"+++    -- posix failures --+    it "appendFile to dir, inappropriate type" $ do+      appendFile' "alreadyExistsD" ""+        `shouldThrow` (\e -> ioeGetErrorType e == InappropriateType)++    it "appendFile, no permissions to file" $ do+      appendFile' "noPerms" ""+        `shouldThrow` (\e -> ioeGetErrorType e == PermissionDenied)++    it "appendFile, no permissions to file" $ do+      appendFile' "noPermsD/inputFile" ""+        `shouldThrow` (\e -> ioeGetErrorType e == PermissionDenied)++    it "appendFile, file does not exist" $ do+      appendFile' "gaga" ""+        `shouldThrow` (\e -> ioeGetErrorType e == NoSuchThing)
+ test/HPath/IO/ReadFileEOFSpec.hs view
@@ -0,0 +1,86 @@+{-# LANGUAGE OverloadedStrings #-}+++module HPath.IO.ReadFileEOFSpec where+++import Test.Hspec+import System.IO.Error+  (+    ioeGetErrorType+  )+import GHC.IO.Exception+  (+    IOErrorType(..)+  )+import System.Process+import Utils++++upTmpDir :: IO ()+upTmpDir = do+  setTmpDir "ReadFileEOFSpec"+  createTmpDir++setupFiles :: IO ()+setupFiles = do+  createRegularFile' "fileWithContent"+  createRegularFile' "fileWithoutContent"+  createSymlink' "inputFileSymL" "fileWithContent"+  createDir' "alreadyExistsD"+  createRegularFile' "noPerms"+  noPerms "noPerms"+  createDir' "noPermsD"+  createRegularFile' "noPermsD/inputFile"+  noPerms "noPermsD"+  writeFile' "fileWithContent" "Blahfaselgagaga"+++cleanupFiles :: IO ()+cleanupFiles = do+  deleteFile' "fileWithContent"+  deleteFile' "fileWithoutContent"+  deleteFile' "inputFileSymL"+  deleteDir' "alreadyExistsD"+  normalFilePerms "noPerms"+  deleteFile' "noPerms"+  normalDirPerms "noPermsD"+  deleteFile' "noPermsD/inputFile"+  deleteDir' "noPermsD"+++spec :: Spec+spec = beforeAll_ (upTmpDir >> setupFiles) $ afterAll_ cleanupFiles $+  describe "HPath.IO.readFileEOF" $ do++    -- successes --+    it "readFileEOF (Strict) file with content, everything clear" $ do+      out <- readFileEOF' "fileWithContent"+      out `shouldBe` "Blahfaselgagaga"++    it "readFileEOF (Strict) symlink, everything clear" $ do+      out <- readFileEOF' "inputFileSymL"+      out `shouldBe` "Blahfaselgagaga"++    it "readFileEOF (Strict) empty file, everything clear" $ do+      out <- readFileEOF' "fileWithoutContent"+      out `shouldBe` ""+++    -- posix failures --+    it "readFileEOF (Strict) directory, wrong file type" $ do+      readFileEOF' "alreadyExistsD"+        `shouldThrow` (\e -> ioeGetErrorType e == InappropriateType)++    it "readFileEOF (Strict) file, no permissions" $ do+      readFileEOF' "noPerms"+        `shouldThrow` (\e -> ioeGetErrorType e == PermissionDenied)++    it "readFileEOF (Strict) file, no permissions on dir" $ do+      readFileEOF' "noPermsD/inputFile"+        `shouldThrow` (\e -> ioeGetErrorType e == PermissionDenied)++    it "readFileEOF (Strict) file, no such file" $ do+      readFileEOF' "lalala"+        `shouldThrow` (\e -> ioeGetErrorType e == NoSuchThing)
+ test/HPath/IO/ReadFileSpec.hs view
@@ -0,0 +1,86 @@+{-# LANGUAGE OverloadedStrings #-}+++module HPath.IO.ReadFileSpec where+++import Test.Hspec+import System.IO.Error+  (+    ioeGetErrorType+  )+import GHC.IO.Exception+  (+    IOErrorType(..)+  )+import System.Process+import Utils++++upTmpDir :: IO ()+upTmpDir = do+  setTmpDir "ReadFileSpec"+  createTmpDir++setupFiles :: IO ()+setupFiles = do+  createRegularFile' "fileWithContent"+  createRegularFile' "fileWithoutContent"+  createSymlink' "inputFileSymL" "fileWithContent"+  createDir' "alreadyExistsD"+  createRegularFile' "noPerms"+  noPerms "noPerms"+  createDir' "noPermsD"+  createRegularFile' "noPermsD/inputFile"+  noPerms "noPermsD"+  writeFile' "fileWithContent" "Blahfaselgagaga"+++cleanupFiles :: IO ()+cleanupFiles = do+  deleteFile' "fileWithContent"+  deleteFile' "fileWithoutContent"+  deleteFile' "inputFileSymL"+  deleteDir' "alreadyExistsD"+  normalFilePerms "noPerms"+  deleteFile' "noPerms"+  normalDirPerms "noPermsD"+  deleteFile' "noPermsD/inputFile"+  deleteDir' "noPermsD"+++spec :: Spec+spec = beforeAll_ (upTmpDir >> setupFiles) $ afterAll_ cleanupFiles $+  describe "HPath.IO.readFile" $ do++    -- successes --+    it "readFile (Strict) file with content, everything clear" $ do+      out <- readFile' "fileWithContent"+      out `shouldBe` "Blahfaselgagaga"++    it "readFile (Strict) symlink, everything clear" $ do+      out <- readFile' "inputFileSymL"+      out `shouldBe` "Blahfaselgagaga"++    it "readFile (Strict) empty file, everything clear" $ do+      out <- readFile' "fileWithoutContent"+      out `shouldBe` ""+++    -- posix failures --+    it "readFile (Strict) directory, wrong file type" $ do+      readFile' "alreadyExistsD"+        `shouldThrow` (\e -> ioeGetErrorType e == InappropriateType)++    it "readFile (Strict) file, no permissions" $ do+      readFile' "noPerms"+        `shouldThrow` (\e -> ioeGetErrorType e == PermissionDenied)++    it "readFile (Strict) file, no permissions on dir" $ do+      readFile' "noPermsD/inputFile"+        `shouldThrow` (\e -> ioeGetErrorType e == PermissionDenied)++    it "readFile (Strict) file, no such file" $ do+      readFile' "lalala"+        `shouldThrow` (\e -> ioeGetErrorType e == NoSuchThing)
+ test/HPath/IO/ToAbsSpec.hs view
@@ -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++
+ test/HPath/IO/WriteFileSpec.hs view
@@ -0,0 +1,109 @@+{-# LANGUAGE OverloadedStrings #-}+++module HPath.IO.WriteFileSpec where+++import Test.Hspec+import System.IO.Error+  (+    ioeGetErrorType+  )+import GHC.IO.Exception+  (+    IOErrorType(..)+  )+import System.Process+import Utils++++upTmpDir :: IO ()+upTmpDir = do+  setTmpDir "WriteFileSpec"+  createTmpDir++setupFiles :: IO ()+setupFiles = do+  createRegularFile' "fileWithContent"+  createRegularFile' "fileWithoutContent"+  createSymlink' "inputFileSymL" "fileWithContent"+  createDir' "alreadyExistsD"+  createRegularFile' "noPerms"+  noPerms "noPerms"+  createDir' "noPermsD"+  createRegularFile' "noPermsD/inputFile"+  noPerms "noPermsD"+  writeFile' "fileWithContent" "BLKASL"+++cleanupFiles :: IO ()+cleanupFiles = do+  deleteFile' "fileWithContent"+  deleteFile' "fileWithoutContent"+  deleteFile' "inputFileSymL"+  deleteDir' "alreadyExistsD"+  normalFilePerms "noPerms"+  deleteFile' "noPerms"+  normalDirPerms "noPermsD"+  deleteFile' "noPermsD/inputFile"+  deleteDir' "noPermsD"+++spec :: Spec+spec = beforeAll_ (upTmpDir >> setupFiles) $ afterAll_ cleanupFiles $+  describe "HPath.IO.writeFile" $ do++    -- successes --+    it "writeFile file with content, everything clear" $ do+      writeFile' "fileWithContent" "blahfaselllll"+      out <- readFile' "fileWithContent"+      out `shouldBe` "blahfaselllll"++    it "writeFile file with content, everything clear" $ do+      writeFile' "fileWithContent" "gagagaga"+      out <- readFile' "fileWithContent"+      out `shouldBe` "gagagaga"++    it "writeFile file with content, everything clear" $ do+      writeFile' "fileWithContent" ""+      out <- readFile' "fileWithContent"+      out `shouldBe` ""++    it "writeFile file without content, everything clear" $ do+      writeFile' "fileWithoutContent" "blahfaselllll"+      out <- readFile' "fileWithoutContent"+      out `shouldBe` "blahfaselllll"++    it "writeFile, everything clear" $ do+      writeFile' "fileWithoutContent" "gagagaga"+      out <- readFile' "fileWithoutContent"+      out `shouldBe` "gagagaga"++    it "writeFile symlink, everything clear" $ do+      writeFile' "inputFileSymL" "blahfaselllll"+      out <- readFile' "inputFileSymL"+      out `shouldBe` "blahfaselllll"++    it "writeFile symlink, everything clear" $ do+      writeFile' "inputFileSymL" "gagagaga"+      out <- readFile' "inputFileSymL"+      out `shouldBe` "gagagaga"+++    -- posix failures --+    it "writeFile to dir, inappropriate type" $ do+      writeFile' "alreadyExistsD" ""+        `shouldThrow` (\e -> ioeGetErrorType e == InappropriateType)++    it "writeFile, no permissions to file" $ do+      writeFile' "noPerms" ""+        `shouldThrow` (\e -> ioeGetErrorType e == PermissionDenied)++    it "writeFile, no permissions to file" $ do+      writeFile' "noPermsD/inputFile" ""+        `shouldThrow` (\e -> ioeGetErrorType e == PermissionDenied)++    it "writeFile, file does not exist" $ do+      writeFile' "gaga" ""+        `shouldThrow` (\e -> ioeGetErrorType e == NoSuchThing)