packages feed

path-io 1.6.3 → 1.8.2

raw patch · 5 files changed

Files

CHANGELOG.md view
@@ -1,3 +1,25 @@+*Path IO follows [SemVer](https://semver.org/).*++## Path IO 1.8.2++* Maintenance release, compatibility with GHC 9.6, 9.8, and 9.10.++## Path IO 1.8.1++* Fixed a “permission denied” error occurring when `copyDirRecur` is used to+  copy a tree with non-empty read-only directories. [PR+  82](https://github.com/mrkkrp/path-io/pull/82).++## Path IO 1.8.0++* Added instances of `AnyPath` for `SomeBase`. [PR+  72](https://github.com/mrkkrp/path-io/pull/72).++## Path IO 1.7.0++* Added `doesPathExist`, `getFileSize`, `renamePath`, and+  `removePathForcibly`.+ ## Path IO 1.6.3  * Fixed a bug that caused `removeDirLink` fail on Linux because of a
Path/IO.hs view
@@ -23,7 +23,9 @@     ensureDir,     removeDir,     removeDirRecur,+    removePathForcibly,     renameDir,+    renamePath,     listDir,     listDirRel,     listDirRecur,@@ -64,6 +66,7 @@     removeFile,     renameFile,     copyFile,+    getFileSize,     findExecutable,     findFile,     findFiles,@@ -86,6 +89,7 @@     createTempDir,      -- * Existence tests+    doesPathExist,     doesFileExist,     doesDirExist,     isLocationOccupied,@@ -122,19 +126,19 @@ import Control.Monad.Trans.Class (lift) import Control.Monad.Trans.Maybe (MaybeT (..), runMaybeT) import Control.Monad.Trans.Writer.Strict (WriterT, execWriterT, tell)-import qualified Data.DList as DList+import Data.DList qualified as DList import Data.Either (lefts, rights) import Data.Kind (Type) import Data.List ((\\))-import qualified Data.Set as S+import Data.Set qualified as S import Data.Time (UTCTime) import Path-import qualified System.Directory as D-import qualified System.FilePath as F+import System.Directory qualified as D+import System.FilePath qualified as F import System.IO (Handle) import System.IO.Error (isDoesNotExistError)-import qualified System.IO.Temp as T-import qualified System.PosixCompat.Files as P+import System.IO.Temp qualified as T+import System.PosixCompat.Files qualified as P  ---------------------------------------------------------------------------- -- Actions on directories@@ -171,14 +175,14 @@ -- * 'InappropriateType' -- The path refers to an existing non-directory object. -- @[EEXIST]@-createDir :: MonadIO m => Path b Dir -> m ()+createDir :: (MonadIO m) => Path b Dir -> m () createDir = liftD D.createDirectory  -- | @'createDirIfMissing' parents dir@ creates a new directory @dir@ if it -- doesn't exist. If the first argument is 'True' the function will also -- create all parent directories if they are missing. createDirIfMissing ::-  MonadIO m =>+  (MonadIO m) =>   -- | Create its parents too?   Bool ->   -- | The path to the directory you want to make@@ -192,7 +196,7 @@ -- > ensureDir = createDirIfMissing True -- -- @since 0.3.1-ensureDir :: MonadIO m => Path b Dir -> m ()+ensureDir :: (MonadIO m) => Path b Dir -> m () ensureDir = createDirIfMissing True  -- | @'removeDir' dir@ removes an existing directory @dir@. The@@ -232,15 +236,36 @@ -- * 'InappropriateType' -- The operand refers to an existing non-directory object. -- @[ENOTDIR]@-removeDir :: MonadIO m => Path b Dir -> m ()+removeDir :: (MonadIO m) => Path b Dir -> m () removeDir = liftD D.removeDirectory  -- | @'removeDirRecur' dir@ removes an existing directory @dir@ together -- with its contents and sub-directories. Within this directory, symbolic -- links are removed without affecting their targets.-removeDirRecur :: MonadIO m => Path b Dir -> m ()+removeDirRecur :: (MonadIO m) => Path b Dir -> m () removeDirRecur = liftD D.removeDirectoryRecursive +-- | Remove a file or directory at /path/ together with its contents and+-- subdirectories. Symbolic links are removed without affecting their+-- targets. If the path does not exist, nothing happens.+--+-- Unlike other removal functions, this function will also attempt to delete+-- files marked as read-only or otherwise made unremovable due to permissions.+-- As a result, if the removal is incomplete, the permissions or attributes on+-- the remaining files may be altered.  If there are hard links in the+-- directory, then permissions on all related hard links may be altered.+--+-- If an entry within the directory vanishes while @removePathForcibly@ is+-- running, it is silently ignored.+--+-- If an exception occurs while removing an entry, @removePathForcibly@ will+-- still try to remove as many entries as it can before failing with an+-- exception.  The first exception that it encountered is re-thrown.+--+-- @since 1.7.0+removePathForcibly :: (MonadIO m) => Path b t -> m ()+removePathForcibly = liftD D.removePathForcibly+ -- | @'renameDir' old new@ changes the name of an existing directory from --  @old@ to @new@. If the @new@ directory already exists, it is atomically --  replaced by the @old@ directory. If the @new@ directory is neither the@@ -287,7 +312,7 @@ --  Either path refers to an existing non-directory object. --  @[ENOTDIR, EISDIR]@ renameDir ::-  MonadIO m =>+  (MonadIO m) =>   -- | Old name   Path b0 Dir ->   -- | New name@@ -295,6 +320,51 @@   m () renameDir = liftD2 D.renameDirectory +-- | Rename a file or directory.  If the destination path already exists, it+-- is replaced atomically.  The destination path must not point to an existing+-- directory.  A conformant implementation need not support renaming files in+-- all situations (e.g. renaming across different physical devices), but the+-- constraints must be documented.+--+-- The operation may fail with:+--+-- * @HardwareFault@+-- A physical I\/O error has occurred.+-- @[EIO]@+--+-- * @InvalidArgument@+-- Either operand is not a valid file name.+-- @[ENAMETOOLONG, ELOOP]@+--+-- * 'isDoesNotExistError'+-- The original file does not exist, or there is no path to the target.+-- @[ENOENT, ENOTDIR]@+--+-- * 'isPermissionError'+-- The process has insufficient privileges to perform the operation.+-- @[EROFS, EACCES, EPERM]@+--+-- * 'System.IO.isFullError'+-- Insufficient resources are available to perform the operation.+-- @[EDQUOT, ENOSPC, ENOMEM, EMLINK]@+--+-- * @UnsatisfiedConstraints@+-- Implementation-dependent constraints are not satisfied.+-- @[EBUSY]@+--+-- * @UnsupportedOperation@+-- The implementation does not support renaming in this situation.+-- @[EXDEV]@+--+-- * @InappropriateType@+-- Either the destination path refers to an existing directory, or one of the+-- parent segments in the destination path is not a directory.+-- @[ENOTDIR, EISDIR, EINVAL, EEXIST, ENOTEMPTY]@+--+-- @since 1.7.0+renamePath :: (MonadIO m) => Path b0 t -> Path b1 t -> m ()+renamePath = liftD2 D.renamePath+ -- | @'listDir' dir@ returns a list of /all/ entries in @dir@ without the -- special entries (@.@ and @..@). Entries are not sorted. --@@ -324,7 +394,7 @@ --   The path refers to an existing non-directory object. --   @[ENOTDIR]@ listDir ::-  MonadIO m =>+  (MonadIO m) =>   -- | Directory to list   Path b Dir ->   -- | Sub-directories and files@@ -341,7 +411,7 @@ -- -- @since 1.4.0 listDirRel ::-  MonadIO m =>+  (MonadIO m) =>   -- | Directory to list   Path b Dir ->   -- | Sub-directories and files@@ -361,7 +431,7 @@ -- -- __Note__: before version /1.3.0/, this function followed symlinks. listDirRecur ::-  MonadIO m =>+  (MonadIO m) =>   -- | Directory to list   Path b Dir ->   -- | Sub-directories and files@@ -383,7 +453,7 @@ -- -- @since 1.4.2 listDirRecurRel ::-  MonadIO m =>+  (MonadIO m) =>   -- | Directory to list   Path b Dir ->   -- | Sub-directories and files@@ -417,7 +487,7 @@ -- traversing symlinked directories, but recreating symlinks in the target -- directory according to their targets in the source directory. copyDirRecur ::-  (MonadIO m, MonadCatch m) =>+  (MonadIO m) =>   -- | Source   Path b0 Dir ->   -- | Destination@@ -442,7 +512,7 @@ -- traversing symlinked directories, but recreating symlinks in the target -- directory according to their targets in the source directory. copyDirRecur' ::-  (MonadIO m, MonadCatch m) =>+  (MonadIO m) =>   -- | Source   Path b0 Dir ->   -- | Destination@@ -454,7 +524,7 @@ -- to preserve directory permissions or not. /Does not/ follow symbolic -- links. Internal function. copyDirRecurGen ::-  MonadIO m =>+  (MonadIO m) =>   -- | Should we preserve directory permissions?   Bool ->   -- | Source@@ -475,7 +545,7 @@         (new </>)           <$> stripProperPrefix old path   ensureDir bdest-  forM_ dirs $ \srcDir -> do+  copyPermissionsIOs <- forM dirs $ \srcDir -> do     destDir <- swapParent bsrc bdest srcDir     dirIsSymlink <- isSymlink srcDir     if dirIsSymlink@@ -484,8 +554,7 @@         D.createDirectoryLink target $           toFilePath' destDir       else ensureDir destDir-    when preserveDirPermissions $-      ignoringIOErrors (copyPermissions srcDir destDir)+    pure $ ignoringIOErrors (copyPermissions srcDir destDir)   forM_ files $ \srcFile -> do     destFile <- swapParent bsrc bdest srcFile     fileIsSymlink <- isSymlink srcFile@@ -494,8 +563,9 @@         target <- getSymlinkTarget srcFile         D.createFileLink target (toFilePath destFile)       else copyFile srcFile destFile-  when preserveDirPermissions $+  when preserveDirPermissions $ do     ignoringIOErrors (copyPermissions bsrc bdest)+    sequence_ copyPermissionsIOs  ---------------------------------------------------------------------------- -- Walking directory trees@@ -556,7 +626,7 @@ -- -- @since 1.2.0 walkDir ::-  MonadIO m =>+  (MonadIO m) =>   -- | Handler (@dir -> subdirs -> files -> 'WalkAction'@)   (Path Abs Dir -> [Path Abs Dir] -> [Path Abs File] -> m (WalkAction Abs)) ->   -- | Directory where traversal begins@@ -598,7 +668,7 @@ -- -- @since 1.4.2 walkDirRel ::-  MonadIO m =>+  (MonadIO m) =>   -- | Handler (@dir -> subdirs -> files -> 'WalkAction'@)   ( Path Rel Dir ->     [Path Rel Dir] ->@@ -739,7 +809,7 @@ -- -- * 'UnsupportedOperation' -- The operating system has no notion of current working directory.-getCurrentDir :: MonadIO m => m (Path Abs Dir)+getCurrentDir :: (MonadIO m) => m (Path Abs Dir) getCurrentDir = liftIO $ D.getCurrentDirectory >>= parseAbsDir  -- | Change the working directory to the given path.@@ -774,7 +844,7 @@ -- * 'InappropriateType' -- The path refers to an existing non-directory object. -- @[ENOTDIR]@-setCurrentDir :: MonadIO m => Path b Dir -> m ()+setCurrentDir :: (MonadIO m) => Path b Dir -> m () setCurrentDir = liftD D.setCurrentDirectory  -- | Run an 'IO' action with the given working directory and restore the@@ -814,7 +884,7 @@ -- * 'isDoesNotExistError' -- The home directory for the current user does not exist, or -- cannot be found.-getHomeDir :: MonadIO m => m (Path Abs Dir)+getHomeDir :: (MonadIO m) => m (Path Abs Dir) getHomeDir = liftIO D.getHomeDirectory >>= resolveDir'  -- | Obtain the path to a special directory for storing user-specific@@ -841,7 +911,7 @@ --   The home directory for the current user does not exist, or cannot be --   found. getAppUserDataDir ::-  MonadIO m =>+  (MonadIO m) =>   -- | Name of application (used in path construction)   String ->   m (Path Abs Dir)@@ -865,7 +935,7 @@ -- * 'isDoesNotExistError' -- The document directory for the current user does not exist, or -- cannot be found.-getUserDocsDir :: MonadIO m => m (Path Abs Dir)+getUserDocsDir :: (MonadIO m) => m (Path Abs Dir) getUserDocsDir = liftIO $ D.getUserDocumentsDirectory >>= parseAbsDir  -- | Return the current directory for temporary files.@@ -893,7 +963,7 @@ -- The operating system has no notion of temporary directory. -- -- The function doesn't verify whether the path exists.-getTempDir :: MonadIO m => m (Path Abs Dir)+getTempDir :: (MonadIO m) => m (Path Abs Dir) getTempDir = liftIO D.getTemporaryDirectory >>= resolveDir'  -- | Obtain the paths to special directories for storing user-specific@@ -913,7 +983,7 @@ -- -- @since 1.2.1 getXdgDir ::-  MonadIO m =>+  (MonadIO m) =>   -- | Which special directory   D.XdgDirectory ->   -- | A relative path that is appended to the path; if 'Nothing', the@@ -933,7 +1003,7 @@ -- -- @since 1.5.0 getXdgDirList ::-  MonadIO m =>+  (MonadIO m) =>   -- | Which special directory list   D.XdgDirectoryList ->   m [Path Abs Dir]@@ -981,7 +1051,7 @@   -- Please note that before version 1.2.3.0 of the @directory@ package,   -- this function had unpredictable behavior on non-existent paths.   canonicalizePath ::-    MonadIO m =>+    (MonadIO m) =>     path ->     m (AbsPath path) @@ -991,7 +1061,7 @@   -- If the path is already absolute, the operation never fails. Otherwise,   -- the operation may fail with the same exceptions as 'getCurrentDir'.   makeAbsolute ::-    MonadIO m =>+    (MonadIO m) =>     path ->     m (AbsPath path) @@ -999,7 +1069,7 @@   --   -- @since 0.3.0   makeRelative ::-    MonadThrow m =>+    (MonadThrow m) =>     -- | Base directory     Path Abs Dir ->     -- | Path that will be made relative to base directory@@ -1010,7 +1080,7 @@   --   -- @since 0.3.0   makeRelativeToCurrentDir ::-    MonadIO m =>+    (MonadIO m) =>     path ->     m (RelPath path) @@ -1032,12 +1102,54 @@   makeRelative b p = parseRelDir (F.makeRelative (toFilePath b) (toFilePath p))   makeRelativeToCurrentDir p = liftIO $ getCurrentDir >>= flip makeRelative p +-- | @since 1.8.0+instance AnyPath (SomeBase File) where+  type AbsPath (SomeBase File) = Path Abs File+  type RelPath (SomeBase File) = Path Rel File++  canonicalizePath s = case s of+    Abs a -> canonicalizePath a+    Rel a -> canonicalizePath a++  makeAbsolute s = case s of+    Abs a -> makeAbsolute a+    Rel a -> makeAbsolute a++  makeRelative r s = case s of+    Abs a -> makeRelative r a+    Rel a -> makeRelative r a++  makeRelativeToCurrentDir s = case s of+    Abs a -> makeRelativeToCurrentDir a+    Rel a -> makeRelativeToCurrentDir a++-- | @since 1.8.0+instance AnyPath (SomeBase Dir) where+  type AbsPath (SomeBase Dir) = Path Abs Dir+  type RelPath (SomeBase Dir) = Path Rel Dir++  canonicalizePath s = case s of+    Abs a -> canonicalizePath a+    Rel a -> canonicalizePath a++  makeAbsolute s = case s of+    Abs a -> makeAbsolute a+    Rel a -> makeAbsolute a++  makeRelative r s = case s of+    Abs a -> makeRelative r a+    Rel a -> makeRelative r a++  makeRelativeToCurrentDir s = case s of+    Abs a -> makeRelativeToCurrentDir a+    Rel a -> makeRelativeToCurrentDir a+ -- | Append stringly-typed path to an absolute path and then canonicalize -- it. -- -- @since 0.3.0 resolveFile ::-  MonadIO m =>+  (MonadIO m) =>   -- | Base directory   Path Abs Dir ->   -- | Path to resolve@@ -1049,7 +1161,7 @@ -- -- @since 0.3.0 resolveFile' ::-  MonadIO m =>+  (MonadIO m) =>   -- | Path to resolve   FilePath ->   m (Path Abs File)@@ -1059,7 +1171,7 @@ -- -- @since 0.3.0 resolveDir ::-  MonadIO m =>+  (MonadIO m) =>   -- | Base directory   Path Abs Dir ->   -- | Path to resolve@@ -1071,7 +1183,7 @@ -- -- @since 0.3.0 resolveDir' ::-  MonadIO m =>+  (MonadIO m) =>   -- | Path to resolve   FilePath ->   m (Path Abs Dir)@@ -1110,7 +1222,7 @@ -- * 'InappropriateType' -- The operand refers to an existing directory. -- @[EPERM, EINVAL]@-removeFile :: MonadIO m => Path b File -> m ()+removeFile :: (MonadIO m) => Path b File -> m () removeFile = liftD D.removeFile  -- | @'renameFile' old new@ changes the name of an existing file system@@ -1154,7 +1266,7 @@ -- Either path refers to an existing directory. -- @[ENOTDIR, EISDIR, EINVAL, EEXIST, ENOTEMPTY]@ renameFile ::-  MonadIO m =>+  (MonadIO m) =>   -- | Original location   Path b0 File ->   -- | New location@@ -1167,7 +1279,7 @@ -- file. Neither path may refer to an existing directory. The permissions of -- @old@ are copied to @new@, if possible. copyFile ::-  MonadIO m =>+  (MonadIO m) =>   -- | Original location   Path b0 File ->   -- | Where to put copy@@ -1175,6 +1287,12 @@   m () copyFile = liftD2 D.copyFile +-- | Obtain the size of a file in bytes.+--+-- @since 1.7.0+getFileSize :: (MonadIO m) => Path b File -> m Integer+getFileSize = liftD D.getFileSize+ -- | Given an executable file name, search for such file in the directories -- listed in system @PATH@. The returned value is the path to the found -- executable or 'Nothing' if an executable with the given name was not@@ -1190,7 +1308,7 @@ -- the directory containing the current executable. See -- <http://msdn.microsoft.com/en-us/library/aa365527.aspx> for more details. findExecutable ::-  MonadIO m =>+  (MonadIO m) =>   -- | Executable file name   Path Rel File ->   -- | Path to found executable@@ -1199,7 +1317,7 @@  -- | Search through the given set of directories for the given file. findFile ::-  MonadIO m =>+  (MonadIO m) =>   -- | Set of directories to search in   [Path b Dir] ->   -- | Filename of interest@@ -1217,7 +1335,7 @@ -- | Search through the given set of directories for the given file and -- return a list of paths where the given file exists. findFiles ::-  MonadIO m =>+  (MonadIO m) =>   -- | Set of directories to search in   [Path b Dir] ->   -- | Filename of interest@@ -1230,7 +1348,7 @@ -- the given property (usually permissions) and return a list of paths where -- the given file exists and has the property. findFilesWith ::-  MonadIO m =>+  (MonadIO m) =>   -- | How to test the files   (Path Abs File -> m Bool) ->   -- | Set of directories to search in@@ -1279,7 +1397,7 @@ -- -- @since 1.5.0 createFileLink ::-  MonadIO m =>+  (MonadIO m) =>   -- | Path to the target file   Path b0 File ->   -- | Path to the link to be created@@ -1316,7 +1434,7 @@ -- -- @since 1.5.0 createDirLink ::-  MonadIO m =>+  (MonadIO m) =>   -- | Path to the target directory   Path b0 Dir ->   -- | Path to the link to be created@@ -1336,7 +1454,7 @@ -- -- @since 1.5.0 removeDirLink ::-  MonadIO m =>+  (MonadIO m) =>   -- | Path to the link to be removed   Path b Dir ->   m ()@@ -1355,7 +1473,7 @@ -- -- @since 1.5.0 getSymlinkTarget ::-  MonadIO m =>+  (MonadIO m) =>   -- | Symlink path   Path b t ->   m FilePath@@ -1373,7 +1491,7 @@ -- | Check if the given path is a symbolic link. -- -- @since 1.3.0-isSymlink :: MonadIO m => Path b t -> m Bool+isSymlink :: (MonadIO m) => Path b t -> m Bool isSymlink = liftD D.pathIsSymbolicLink  ----------------------------------------------------------------------------@@ -1471,7 +1589,7 @@ -- -- @since 0.2.0 openTempFile ::-  MonadIO m =>+  (MonadIO m) =>   -- | Directory to create file in   Path b Dir ->   -- | File name template; if the template is "foo.ext" then the created@@ -1494,7 +1612,7 @@ -- -- @since 0.2.0 openBinaryTempFile ::-  MonadIO m =>+  (MonadIO m) =>   -- | Directory to create file in   Path b Dir ->   -- | File name template, see 'openTempFile'@@ -1514,7 +1632,7 @@ -- -- @since 0.2.0 createTempDir ::-  MonadIO m =>+  (MonadIO m) =>   -- | Directory to create file in   Path b Dir ->   -- | Directory name template, see 'openTempFile'@@ -1529,19 +1647,27 @@ ---------------------------------------------------------------------------- -- Existence tests +-- | Test whether the given path points to an existing filesystem object. If+-- the user lacks necessary permissions to search the parent directories,+-- this function may return false even if the file does actually exist.+--+-- @since 1.7.0+doesPathExist :: (MonadIO m) => Path b t -> m Bool+doesPathExist = liftD D.doesPathExist+ -- | The operation 'doesFileExist' returns 'True' if the argument file -- exists and is not a directory, and 'False' otherwise.-doesFileExist :: MonadIO m => Path b File -> m Bool+doesFileExist :: (MonadIO m) => Path b File -> m Bool doesFileExist = liftD D.doesFileExist  -- | The operation 'doesDirExist' returns 'True' if the argument file exists -- and is either a directory or a symbolic link to a directory, and 'False' -- otherwise.-doesDirExist :: MonadIO m => Path b Dir -> m Bool+doesDirExist :: (MonadIO m) => Path b Dir -> m Bool doesDirExist = liftD D.doesDirectoryExist  -- | Check if there is a file or directory on specified path.-isLocationOccupied :: MonadIO m => Path b t -> m Bool+isLocationOccupied :: (MonadIO m) => Path b t -> m Bool isLocationOccupied path = do   let fp = toFilePath path   file <- liftIO (D.doesFileExist fp)@@ -1578,7 +1704,7 @@ --   the permissions; or -- -- * 'isDoesNotExistError' if the file or directory does not exist.-getPermissions :: MonadIO m => Path b t -> m D.Permissions+getPermissions :: (MonadIO m) => Path b t -> m D.Permissions getPermissions = liftD D.getPermissions  -- | The 'setPermissions' operation sets the permissions for the file or@@ -1590,13 +1716,13 @@ --   the permissions; or -- -- * 'isDoesNotExistError' if the file or directory does not exist.-setPermissions :: MonadIO m => Path b t -> D.Permissions -> m ()+setPermissions :: (MonadIO m) => Path b t -> D.Permissions -> m () setPermissions = liftD2' D.setPermissions  -- | Set permissions for the object found on second given path so they match -- permissions of the object on the first path. copyPermissions ::-  MonadIO m =>+  (MonadIO m) =>   -- | From where to copy   Path b0 t0 ->   -- | What to modify@@ -1622,7 +1748,7 @@ -- -- Note: this is a piece of conditional API, only available if -- @directory-1.2.3.0@ or later is used.-getAccessTime :: MonadIO m => Path b t -> m UTCTime+getAccessTime :: (MonadIO m) => Path b t -> m UTCTime getAccessTime = liftD D.getAccessTime  -- | Change the time at which the file or directory was last accessed.@@ -1649,7 +1775,7 @@ -- -- Note: this is a piece of conditional API, only available if -- @directory-1.2.3.0@ or later is used.-setAccessTime :: MonadIO m => Path b t -> UTCTime -> m ()+setAccessTime :: (MonadIO m) => Path b t -> UTCTime -> m () setAccessTime = liftD2' D.setAccessTime  -- | Change the time at which the file or directory was last modified.@@ -1676,7 +1802,7 @@ -- -- Note: this is a piece of conditional API, only available if -- @directory-1.2.3.0@ or later is used.-setModificationTime :: MonadIO m => Path b t -> UTCTime -> m ()+setModificationTime :: (MonadIO m) => Path b t -> UTCTime -> m () setModificationTime = liftD2' D.setModificationTime  -- | Obtain the time at which the file or directory was last modified.@@ -1691,7 +1817,7 @@ -- Caveat for POSIX systems: This function returns a timestamp with -- sub-second resolution only if this package is compiled against -- @unix-2.6.0.0@ or later and the underlying filesystem supports them.-getModificationTime :: MonadIO m => Path b t -> m UTCTime+getModificationTime :: (MonadIO m) => Path b t -> m UTCTime getModificationTime = liftD D.getModificationTime  ----------------------------------------------------------------------------@@ -1700,7 +1826,7 @@ -- | Lift an action in 'IO' that takes 'FilePath' into an action in slightly -- more abstract monad that takes 'Path'. liftD ::-  MonadIO m =>+  (MonadIO m) =>   -- | Original action   (FilePath -> IO a) ->   -- | 'Path' argument@@ -1712,7 +1838,7 @@  -- | Similar to 'liftD' but for functions with arity 2. liftD2 ::-  MonadIO m =>+  (MonadIO m) =>   -- | Original action   (FilePath -> FilePath -> IO a) ->   -- | First 'Path' argument@@ -1726,7 +1852,7 @@ -- | Similar to 'liftD2', but allows us to pass second argument of arbitrary -- type. liftD2' ::-  MonadIO m =>+  (MonadIO m) =>   -- | Original action   (FilePath -> v -> IO a) ->   -- | First 'Path' argument@@ -1745,5 +1871,5 @@ ignoringIOErrors :: IO () -> IO () ignoringIOErrors ioe = ioe `catch` handler   where-    handler :: Monad m => IOError -> m ()+    handler :: (Monad m) => IOError -> m ()     handler = const (return ())
− Setup.hs
@@ -1,6 +0,0 @@-module Main (main) where--import Distribution.Simple--main :: IO ()-main = defaultMain
path-io.cabal view
@@ -1,11 +1,11 @@-cabal-version:   1.18+cabal-version:   2.4 name:            path-io-version:         1.6.3-license:         BSD3+version:         1.8.2+license:         BSD-3-Clause license-file:    LICENSE.md maintainer:      Mark Karpov <markkarpov92@gmail.com> author:          Mark Karpov <markkarpov92@gmail.com>-tested-with:     ghc ==8.8.4 ghc ==8.10.4 ghc ==9.0.1+tested-with:     ghc ==9.6.5 ghc ==9.8.2 ghc ==9.10.1 homepage:        https://github.com/mrkkrp/path-io bug-reports:     https://github.com/mrkkrp/path-io/issues synopsis:        Interface to ‘directory’ package for users of ‘path’@@ -27,49 +27,48 @@  library     exposed-modules:  Path.IO-    default-language: Haskell2010+    default-language: GHC2021     build-depends:-        base >=4.13 && <5.0,+        base >=4.15 && <5,         containers,         directory >=1.3.2.0 && <1.4,-        dlist >=0.8 && <2.0,+        dlist >=0.8 && <2,         exceptions >=0.8 && <0.11,-        filepath >=1.2 && <1.5,-        path >=0.6 && <0.9,+        filepath >=1.2 && <1.6,+        path >=0.7.1 && <0.10,         temporary >=1.1 && <1.4,-        time >=1.4 && <1.11,-        transformers >=0.3 && <0.6,+        time >=1.4 && <1.15,+        transformers >=0.3 && <0.7,         unix-compat      if flag(dev)-        ghc-options: -Wall -Werror+        ghc-options:+            -Wall -Werror -Wredundant-constraints -Wpartial-fields+            -Wunused-packages      else         ghc-options: -O2 -Wall -    if flag(dev)-        ghc-options:-            -Wcompat -Wincomplete-record-updates -Wincomplete-uni-patterns-            -Wnoncanonical-monad-instances- test-suite tests     type:             exitcode-stdio-1.0     main-is:          Main.hs     hs-source-dirs:   tests-    default-language: Haskell2010+    default-language: GHC2021     build-depends:-        base >=4.13 && <5.0,-        directory >=1.3.2.0 && <1.4,+        base >=4.15 && <5,         exceptions >=0.8 && <0.11,-        hspec >=2.0 && <3.0,-        filepath >=1.2 && <1.5,-        path >=0.6 && <0.9,+        hspec >=2 && <3,+        path >=0.7.1 && <0.10,         path-io,-        transformers >=0.3 && <0.6,         unix-compat      if flag(dev)-        ghc-options: -Wall -Werror+        ghc-options:+            -Wall -Werror -Wredundant-constraints -Wpartial-fields+            -Wunused-packages      else         ghc-options: -O2 -Wall++    if impl(ghc >=9.8)+        ghc-options: -Wno-x-partial
tests/Main.hs view
@@ -16,6 +16,9 @@  main :: IO () main = hspec . around withSandbox $ do++{- ORMOLU_DISABLE -}+ #ifndef mingw32_HOST_OS   beforeWith populatedDir $ do     -- NOTE These tests fail on Windows as unix-compat does not implement@@ -37,7 +40,6 @@   describe "setCurrentDir" setCurrentDirSpec   describe "withCurrentDir" withCurrentDirSpec   describe "walkDirRel" walkDirRelSpec- #ifndef mingw32_HOST_OS   -- NOTE We can't quite test this on Windows as well, because the   -- environmental variables HOME and TMPDIR do not exist there.@@ -48,6 +50,8 @@   describe "getXdgDir Cache"  getXdgCacheDirSpec #endif +{- ORMOLU_ENABLE -}+ listDirSpec :: SpecWith (Path Abs Dir) listDirSpec = it "lists directory" $ \dir ->   getDirStructure listDir dir `shouldReturn` populatedDirTop@@ -162,7 +166,7 @@  -- | Follows symbolic links. listDirRecurCyclic ::-  (MonadIO m, MonadThrow m) =>+  (MonadIO m) =>   -- | Directory to list   Path b Dir ->   -- | Sub-directories and files@@ -270,12 +274,21 @@       pdir = root </> $(mkRelDir "pdir")       withinSandbox = (pdir </>)   ensureDir pdir-  ensureDir $ withinSandbox $(mkRelDir "b")+  let b = withinSandbox $(mkRelDir "b")+  ensureDir b   ensureDir $ withinSandbox $(mkRelDir "b/c")-  -- to verify that we do not follow symbolic links. We should not list b's-  -- tree under 'a'.+  -- Create a read-only directory with a file inside to test that the code+  -- that copies directory permissions can handle that gracefully.+  --+  -- See: https://github.com/mrkkrp/path-io/pull/82+  let readonlyDir = withinSandbox $(mkRelDir "readonly-dir")+  ensureDir readonlyDir+  -- We should not list b's tree under 'a' in order to verify that we do not+  -- follow symbolic links.   createSymbolicLink "b" (toFilePath $ withinSandbox $(mkRelFile "a"))   forM_ files $ (`writeFile` "") . toFilePath . withinSandbox+  getPermissions readonlyDir+    >>= setPermissions readonlyDir . setOwnerWritable False   return pdir  -- | Get the inner structure of a directory. Items are sorted, so it's@@ -308,11 +321,13 @@ populatedDirStructure =   ( [ $(mkRelDir "a"),       $(mkRelDir "b"),-      $(mkRelDir "b/c")+      $(mkRelDir "b/c"),+      $(mkRelDir "readonly-dir")     ],     [ $(mkRelFile "b/c/three.txt"),       $(mkRelFile "b/two.txt"),-      $(mkRelFile "one.txt")+      $(mkRelFile "one.txt"),+      $(mkRelFile "readonly-dir/two.txt")     ]   ) @@ -355,26 +370,28 @@   createSymbolicLink "../../e" (toFilePath $ withinSandbox $(mkRelFile "e/f/g"))   return pdir --- | Top-level structure of populated directory as it should be scanned by--- the 'listDir' function.+-- | Top-level structure of the populated directory as it should be scanned+-- by the 'listDir' function. populatedDirTop :: ([Path Rel Dir], [Path Rel File]) populatedDirTop =   ( [ $(mkRelDir "a"),-      $(mkRelDir "b")+      $(mkRelDir "b"),+      $(mkRelDir "readonly-dir")     ],     [ $(mkRelFile "one.txt")     ]   ) --- | Structure of populated directory as it should be scanned by--- 'listDirRecurWith' function using predicates to filter out dir 'c' and the--- file 'two.txt'+-- | Structure of the populated directory as it should be scanned by+-- 'listDirRecurWith' function using predicates to filter out dir 'c' and+-- the file @two.txt@. populatedDirRecurWith :: ([Path Rel Dir], [Path Rel File]) populatedDirRecurWith =   ( [ $(mkRelDir "a"),-      $(mkRelDir "b")+      $(mkRelDir "b"),+      $(mkRelDir "readonly-dir")     ],-    [ $(mkRelFile "a/c/three.txt"), -- via symbolic link+    [ $(mkRelFile "a/c/three.txt"), -- via a symbolic link       $(mkRelFile "b/c/three.txt"),       $(mkRelFile "one.txt")     ]