packages feed

filepather-0.6.1: src/System/FilePather/FilePather.hs

{-# OPTIONS_GHC -Wall #-}

-- |
-- Module      : System.FilePather.FilePather
-- Description : Platform-native FilePath and Directory operations via Kleisli type aliases
module System.FilePather.FilePather
  ( -- * Types
    FilePather,
    FilePather',
    FilePatherA,
    FilePatherA',
    FilePatherMaybe,
    FilePatherMaybeA,
    FilePatherIO,
    FilePatherIOA,

    -- * Isomorphisms
    filePather,
    proFilePather,
    contraFilePather,

    -- * System.FilePath re-implementations
    splitExtension,
    takeExtension,
    replaceExtension,
    dropExtension,
    addExtension,
    hasExtension,
    splitExtensions,
    takeExtensions,
    dropExtensions,
    replaceExtensions,
    isExtensionOf,
    stripExtension,
    splitFileName,
    takeFileName,
    replaceFileName,
    dropFileName,
    takeBaseName,
    replaceBaseName,
    takeDirectory,
    replaceDirectory,
    combine,
    splitPath,
    FP.joinPath,
    splitDirectories,
    splitDrive,
    joinDrive,
    takeDrive,
    hasDrive,
    dropDrive,
    isDrive,
    hasTrailingPathSeparator,
    addTrailingPathSeparator,
    dropTrailingPathSeparator,
    normalise,
    makeRelative,
    equalFilePath,
    isRelative,
    isAbsolute,
    isValid,
    makeValid,
    (</>),
    (<.>),
    (-<.>),

    -- * System.FilePath re-exports
    FP.FilePath,
    FP.pathSeparator,
    FP.pathSeparators,
    FP.isPathSeparator,
    FP.searchPathSeparator,
    FP.isSearchPathSeparator,
    FP.extSeparator,
    FP.isExtSeparator,
    FP.splitSearchPath,
    FP.getSearchPath,

    -- * System.Directory re-implementations
    createDirectory,
    createDirectoryIfMissing,
    removeDirectory,
    removeDirectoryRecursive,
    removePathForcibly,
    renameDirectory,
    listDirectory,
    getDirectoryContents,
    setCurrentDirectory,
    withCurrentDirectory,
    getXdgDirectory,
    getAppUserDataDirectory,
    removeFile,
    renameFile,
    renamePath,
    copyFile,
    copyFileWithMetadata,
    canonicalizePath,
    makeAbsolute,
    makeRelativeToCurrentDirectory,
    doesPathExist,
    doesFileExist,
    doesDirectoryExist,
    Dir.findFile,
    Dir.findFiles,
    Dir.findFileWith,
    Dir.findFilesWith,
    createFileLink,
    createDirectoryLink,
    removeDirectoryLink,
    getSymbolicLinkTarget,
    isSymbolicLink,
    pathIsSymbolicLink,
    getPermissions,
    setPermissions,
    copyPermissions,
    getAccessTime,
    getModificationTime,
    setAccessTime,
    setModificationTime,
    getFileSize,

    -- * System.Directory re-exports
    Dir.Permissions,
    Dir.emptyPermissions,
    Dir.setOwnerReadable,
    Dir.setOwnerWritable,
    Dir.setOwnerExecutable,
    Dir.setOwnerSearchable,
    Dir.XdgDirectory (..),
    Dir.XdgDirectoryList (..),
    Dir.getXdgDirectoryList,
    Dir.exeExtension,
    Dir.getCurrentDirectory,
    Dir.getHomeDirectory,
    Dir.getTemporaryDirectory,
    Dir.getUserDocumentsDirectory,
    Dir.findExecutable,
    Dir.findExecutables,
    Dir.findExecutablesInDirectories,
  )
where

import Control.Lens (Iso, _Wrapped)
import Data.Functor.Identity (Identity)
import Data.Kleisli (ContraKleisli, Kleisli (..), ProKleisli, mkKleisli', _Kleisli_ContraKleisli, _Kleisli_ProKleisli)
import Data.Time.Clock (UTCTime)
import qualified System.Directory as Dir
import qualified System.FilePath as FP

-- $setup
-- >>> import Control.Lens (view, review)
-- >>> import Data.Functor.Identity (Identity(..))
-- >>> import Data.Kleisli (Kleisli(..), ProKleisli(..), ContraKleisli(..))
-- >>> import qualified System.Directory as Dir
-- >>> import qualified System.FilePath as FP
-- >>> let run (Kleisli f) x = runIdentity (f x)

type FilePather p f a = Kleisli p FilePath f a

type FilePather' p a = FilePather p Identity a

type FilePatherA f a = FilePather (->) f a

type FilePatherA' a = FilePatherA Identity a

type FilePatherMaybe p a = FilePather p Maybe a

type FilePatherMaybeA a = FilePatherMaybe (->) a

type FilePatherIO p a = FilePather p IO a

type FilePatherIOA a = FilePatherIO (->) a

-- | An isomorphism between a 'FilePather' and its unwrapped representation.
--
-- >>> view filePather takeExtension "/tmp/foo.hs"
-- Identity ".hs"
--
-- >>> run (review filePather (Identity . FP.takeExtension)) "/tmp/foo.hs"
-- ".hs"
filePather :: Iso (FilePather p f a) (FilePather p' f' a') (p FilePath (f a)) (p' FilePath (f' a'))
filePather = _Wrapped
{-# INLINE filePather #-}

-- | An isomorphism between a 'FilePather' and a 'ProKleisli', reordering
-- type parameters to enable 'Category' and 'Arrow' instances.
--
-- >>> let ProKleisli f = view proFilePather takeExtension in runIdentity (f "/tmp/foo.hs")
-- ".hs"
proFilePather :: Iso (FilePather p f a) (FilePather p' f' a') (ProKleisli p f FilePath a) (ProKleisli p' f' FilePath a')
proFilePather = _Kleisli_ProKleisli
{-# INLINE proFilePather #-}

-- | An isomorphism between a 'FilePather' and a 'ContraKleisli', reordering
-- type parameters to enable 'Contravariant' instances.
--
-- >>> let ContraKleisli f = view contraFilePather takeExtension in runIdentity (f "/tmp/foo.hs")
-- ".hs"
contraFilePather :: Iso (FilePather p f a) (FilePather p' f' a') (ContraKleisli p a f FilePath) (ContraKleisli p' a' f' FilePath)
contraFilePather = _Kleisli_ContraKleisli
{-# INLINE contraFilePather #-}

------------------------------------------------------------
-- System.FilePath
------------------------------------------------------------

-- | Split a file path into the stem and extension.
--
-- >>> run splitExtension "/tmp/foo.hs"
-- ("/tmp/foo",".hs")
--
-- >>> run splitExtension "/tmp/foo"
-- ("/tmp/foo","")
--
-- >>> run splitExtension "/tmp/foo.tar.gz"
-- ("/tmp/foo.tar",".gz")
splitExtension :: FilePatherA' (String, String)
splitExtension = mkKleisli' FP.splitExtension
{-# INLINE splitExtension #-}

-- | Get the extension of a file path.
--
-- >>> run takeExtension "/tmp/foo.hs"
-- ".hs"
--
-- >>> run takeExtension "/tmp/foo"
-- ""
--
-- >>> run takeExtension "/tmp/foo.tar.gz"
-- ".gz"
takeExtension :: FilePatherA' String
takeExtension = mkKleisli' FP.takeExtension
{-# INLINE takeExtension #-}

-- | Replace the extension of a file path.
--
-- >>> run (replaceExtension ".md") "/tmp/foo.hs"
-- "/tmp/foo.md"
--
-- >>> run (replaceExtension ".txt") "/tmp/foo"
-- "/tmp/foo.txt"
--
-- >>> run (replaceExtension "") "/tmp/foo.hs"
-- "/tmp/foo"
replaceExtension :: String -> FilePatherA' FilePath
replaceExtension s = mkKleisli' (`FP.replaceExtension` s)
{-# INLINE replaceExtension #-}

-- | Remove the extension from a file path.
--
-- >>> run dropExtension "/tmp/foo.hs"
-- "/tmp/foo"
--
-- >>> run dropExtension "/tmp/foo"
-- "/tmp/foo"
--
-- >>> run dropExtension "/tmp/foo.tar.gz"
-- "/tmp/foo.tar"
dropExtension :: FilePatherA' FilePath
dropExtension = mkKleisli' FP.dropExtension
{-# INLINE dropExtension #-}

-- | Add an extension to a file path.
--
-- >>> run (addExtension ".hs") "/tmp/foo"
-- "/tmp/foo.hs"
--
-- >>> run (addExtension ".gz") "/tmp/foo.tar"
-- "/tmp/foo.tar.gz"
--
-- >>> run (addExtension "hs") "/tmp/foo"
-- "/tmp/foo.hs"
addExtension :: String -> FilePatherA' FilePath
addExtension s = mkKleisli' (`FP.addExtension` s)
{-# INLINE addExtension #-}

-- | Does the file path have an extension?
--
-- >>> run hasExtension "/tmp/foo.hs"
-- True
--
-- >>> run hasExtension "/tmp/foo"
-- False
hasExtension :: FilePatherA' Bool
hasExtension = mkKleisli' FP.hasExtension
{-# INLINE hasExtension #-}

-- | Split all extensions from a file path.
--
-- >>> run splitExtensions "/tmp/foo.tar.gz"
-- ("/tmp/foo",".tar.gz")
--
-- >>> run splitExtensions "/tmp/foo"
-- ("/tmp/foo","")
splitExtensions :: FilePatherA' (FilePath, String)
splitExtensions = mkKleisli' FP.splitExtensions
{-# INLINE splitExtensions #-}

-- | Get all extensions from a file path.
--
-- >>> run takeExtensions "/tmp/foo.tar.gz"
-- ".tar.gz"
--
-- >>> run takeExtensions "/tmp/foo"
-- ""
--
-- >>> run takeExtensions "/tmp/foo.a.b.c"
-- ".a.b.c"
takeExtensions :: FilePatherA' String
takeExtensions = mkKleisli' FP.takeExtensions
{-# INLINE takeExtensions #-}

-- | Drop all extensions from a file path.
--
-- >>> run dropExtensions "/tmp/foo.tar.gz"
-- "/tmp/foo"
--
-- >>> run dropExtensions "/tmp/foo"
-- "/tmp/foo"
dropExtensions :: FilePatherA' FilePath
dropExtensions = mkKleisli' FP.dropExtensions
{-# INLINE dropExtensions #-}

-- | Replace all extensions of a file path.
--
-- >>> run (replaceExtensions ".txt") "/tmp/foo.tar.gz"
-- "/tmp/foo.txt"
--
-- >>> run (replaceExtensions ".a.b") "/tmp/foo.c"
-- "/tmp/foo.a.b"
replaceExtensions :: String -> FilePatherA' FilePath
replaceExtensions s = mkKleisli' (`FP.replaceExtensions` s)
{-# INLINE replaceExtensions #-}

-- | Is the given string an extension of the file path?
--
-- >>> run (isExtensionOf ".hs") "/tmp/foo.hs"
-- True
--
-- >>> run (isExtensionOf ".md") "/tmp/foo.hs"
-- False
--
-- >>> run (isExtensionOf "hs") "/tmp/foo.hs"
-- True
--
-- >>> run (isExtensionOf ".gz") "/tmp/foo.tar.gz"
-- True
isExtensionOf :: String -> FilePatherA' Bool
isExtensionOf s = mkKleisli' (FP.isExtensionOf s)
{-# INLINE isExtensionOf #-}

-- | Strip an extension from a file path, returning 'Nothing' if the
-- extension does not match.
--
-- >>> let Kleisli f = stripExtension ".hs" in f "/tmp/foo.hs"
-- Just "/tmp/foo"
--
-- >>> let Kleisli f = stripExtension ".md" in f "/tmp/foo.hs"
-- Nothing
--
-- >>> let Kleisli f = stripExtension ".gz" in f "/tmp/foo.tar.gz"
-- Just "/tmp/foo.tar"
--
-- >>> let Kleisli f = stripExtension ".tar.gz" in f "/tmp/foo.tar.gz"
-- Just "/tmp/foo"
stripExtension :: String -> FilePatherMaybeA FilePath
stripExtension s = Kleisli (FP.stripExtension s)
{-# INLINE stripExtension #-}

-- | Split a file path into directory and file components.
--
-- >>> run splitFileName "/tmp/foo.hs"
-- ("/tmp/","foo.hs")
--
-- >>> run splitFileName "/tmp/"
-- ("/tmp/","")
--
-- >>> run splitFileName "foo.hs"
-- ("./","foo.hs")
splitFileName :: FilePatherA' (String, String)
splitFileName = mkKleisli' FP.splitFileName
{-# INLINE splitFileName #-}

-- | Get the file name component of a file path.
--
-- >>> run takeFileName "/tmp/foo.hs"
-- "foo.hs"
--
-- >>> run takeFileName "/tmp/"
-- ""
--
-- >>> run takeFileName "foo.hs"
-- "foo.hs"
takeFileName :: FilePatherA' FilePath
takeFileName = mkKleisli' FP.takeFileName
{-# INLINE takeFileName #-}

-- | Replace the file name component of a file path.
--
-- >>> run (replaceFileName "bar.md") "/tmp/foo.hs"
-- "/tmp/bar.md"
--
-- >>> run (replaceFileName "baz") "/tmp/foo.hs"
-- "/tmp/baz"
replaceFileName :: String -> FilePatherA' FilePath
replaceFileName s = mkKleisli' (`FP.replaceFileName` s)
{-# INLINE replaceFileName #-}

-- | Drop the file name component of a file path.
--
-- >>> run dropFileName "/tmp/foo.hs"
-- "/tmp/"
--
-- >>> run dropFileName "foo.hs"
-- "./"
dropFileName :: FilePatherA' FilePath
dropFileName = mkKleisli' FP.dropFileName
{-# INLINE dropFileName #-}

-- | Get the base name (file name without extension) of a file path.
--
-- >>> run takeBaseName "/tmp/foo.hs"
-- "foo"
--
-- >>> run takeBaseName "/tmp/foo.tar.gz"
-- "foo.tar"
--
-- >>> run takeBaseName "/tmp/"
-- ""
takeBaseName :: FilePatherA' String
takeBaseName = mkKleisli' FP.takeBaseName
{-# INLINE takeBaseName #-}

-- | Replace the base name of a file path.
--
-- >>> run (replaceBaseName "bar") "/tmp/foo.hs"
-- "/tmp/bar.hs"
--
-- >>> run (replaceBaseName "quux") "/tmp/foo.tar.gz"
-- "/tmp/quux.gz"
replaceBaseName :: String -> FilePatherA' FilePath
replaceBaseName s = mkKleisli' (`FP.replaceBaseName` s)
{-# INLINE replaceBaseName #-}

-- | Get the directory component of a file path.
--
-- >>> run takeDirectory "/tmp/foo.hs"
-- "/tmp"
--
-- >>> run takeDirectory "/tmp/"
-- "/tmp"
--
-- >>> run takeDirectory "foo.hs"
-- "."
--
-- >>> run takeDirectory "/tmp/src/foo.hs"
-- "/tmp/src"
takeDirectory :: FilePatherA' FilePath
takeDirectory = mkKleisli' FP.takeDirectory
{-# INLINE takeDirectory #-}

-- | Replace the directory component of a file path.
--
-- >>> run (replaceDirectory "/usr") "/tmp/foo.hs"
-- "/usr/foo.hs"
--
-- >>> run (replaceDirectory "src") "/tmp/foo.hs"
-- "src/foo.hs"
replaceDirectory :: String -> FilePatherA' FilePath
replaceDirectory s = mkKleisli' (`FP.replaceDirectory` s)
{-# INLINE replaceDirectory #-}

-- | Combine a directory path with a file path.
--
-- >>> run (combine "/tmp") "foo.hs"
-- "/tmp/foo.hs"
--
-- >>> run (combine "/tmp") "/usr/foo.hs"
-- "/usr/foo.hs"
--
-- >>> run (combine "src") "Data/Foo.hs"
-- "src/Data/Foo.hs"
combine :: FilePath -> FilePatherA' FilePath
combine s = mkKleisli' (FP.combine s)
{-# INLINE combine #-}

-- | Split a file path into its path components.
--
-- >>> run splitPath "/tmp/src/foo.hs"
-- ["/","tmp/","src/","foo.hs"]
--
-- >>> run splitPath "src/foo.hs"
-- ["src/","foo.hs"]
--
-- >>> run splitPath "foo.hs"
-- ["foo.hs"]
splitPath :: FilePatherA' [FilePath]
splitPath = mkKleisli' FP.splitPath
{-# INLINE splitPath #-}

-- | Split a file path into its directory components.
--
-- >>> run splitDirectories "/tmp/src/foo.hs"
-- ["/","tmp","src","foo.hs"]
--
-- >>> run splitDirectories "src/foo.hs"
-- ["src","foo.hs"]
--
-- >>> run splitDirectories "/"
-- ["/"]
splitDirectories :: FilePatherA' [FilePath]
splitDirectories = mkKleisli' FP.splitDirectories
{-# INLINE splitDirectories #-}

-- | Split a file path into drive and remainder.
--
-- >>> run splitDrive "/tmp/foo"
-- ("/","tmp/foo")
--
-- >>> run splitDrive "tmp/foo"
-- ("","tmp/foo")
splitDrive :: FilePatherA' (FilePath, FilePath)
splitDrive = mkKleisli' FP.splitDrive
{-# INLINE splitDrive #-}

-- | Join a drive and the rest of a path.
--
-- >>> run (joinDrive "/") "tmp/foo"
-- "/tmp/foo"
--
-- >>> run (joinDrive "") "tmp/foo"
-- "tmp/foo"
joinDrive :: FilePath -> FilePatherA' FilePath
joinDrive s = mkKleisli' (FP.joinDrive s)
{-# INLINE joinDrive #-}

-- | Get the drive of a file path.
--
-- >>> run takeDrive "/tmp/foo"
-- "/"
--
-- >>> run takeDrive "tmp/foo"
-- ""
takeDrive :: FilePatherA' FilePath
takeDrive = mkKleisli' FP.takeDrive
{-# INLINE takeDrive #-}

-- | Does the file path have a drive?
--
-- >>> run hasDrive "/tmp/foo"
-- True
--
-- >>> run hasDrive "tmp/foo"
-- False
hasDrive :: FilePatherA' Bool
hasDrive = mkKleisli' FP.hasDrive
{-# INLINE hasDrive #-}

-- | Drop the drive from a file path.
--
-- >>> run dropDrive "/tmp/foo"
-- "tmp/foo"
--
-- >>> run dropDrive "tmp/foo"
-- "tmp/foo"
dropDrive :: FilePatherA' FilePath
dropDrive = mkKleisli' FP.dropDrive
{-# INLINE dropDrive #-}

-- | Is the file path a drive?
--
-- >>> run isDrive "/"
-- True
--
-- >>> run isDrive "/tmp"
-- False
isDrive :: FilePatherA' Bool
isDrive = mkKleisli' FP.isDrive
{-# INLINE isDrive #-}

-- | Does the file path have a trailing path separator?
--
-- >>> run hasTrailingPathSeparator "/tmp/"
-- True
--
-- >>> run hasTrailingPathSeparator "/tmp"
-- False
hasTrailingPathSeparator :: FilePatherA' Bool
hasTrailingPathSeparator = mkKleisli' FP.hasTrailingPathSeparator
{-# INLINE hasTrailingPathSeparator #-}

-- | Add a trailing path separator if one is not present.
--
-- >>> run addTrailingPathSeparator "/tmp"
-- "/tmp/"
--
-- >>> run addTrailingPathSeparator "/tmp/"
-- "/tmp/"
addTrailingPathSeparator :: FilePatherA' FilePath
addTrailingPathSeparator = mkKleisli' FP.addTrailingPathSeparator
{-# INLINE addTrailingPathSeparator #-}

-- | Drop the trailing path separator if present.
--
-- >>> run dropTrailingPathSeparator "/tmp/"
-- "/tmp"
--
-- >>> run dropTrailingPathSeparator "/tmp"
-- "/tmp"
--
-- >>> run dropTrailingPathSeparator "/"
-- "/"
dropTrailingPathSeparator :: FilePatherA' FilePath
dropTrailingPathSeparator = mkKleisli' FP.dropTrailingPathSeparator
{-# INLINE dropTrailingPathSeparator #-}

-- | Normalise a file path.
--
-- >>> run normalise "/tmp//foo"
-- "/tmp/foo"
--
-- >>> run normalise "/tmp/./foo"
-- "/tmp/foo"
--
-- >>> run normalise "/tmp/foo/"
-- "/tmp/foo/"
normalise :: FilePatherA' FilePath
normalise = mkKleisli' FP.normalise
{-# INLINE normalise #-}

-- | Make a file path relative to a given directory.
--
-- >>> run (makeRelative "/tmp") "/tmp/foo.hs"
-- "foo.hs"
--
-- >>> run (makeRelative "/tmp") "/tmp/src/foo.hs"
-- "src/foo.hs"
--
-- >>> run (makeRelative "/usr") "/tmp/foo.hs"
-- "/tmp/foo.hs"
makeRelative :: FilePath -> FilePatherA' FilePath
makeRelative s = mkKleisli' (FP.makeRelative s)
{-# INLINE makeRelative #-}

-- | Are two file paths equal (accounting for normalisation)?
--
-- >>> run (equalFilePath "/tmp/foo") "/tmp/foo"
-- True
--
-- >>> run (equalFilePath "/tmp/foo") "/tmp/bar"
-- False
--
-- >>> run (equalFilePath "/tmp/foo") "/tmp/./foo"
-- True
equalFilePath :: FilePath -> FilePatherA' Bool
equalFilePath s = mkKleisli' (FP.equalFilePath s)
{-# INLINE equalFilePath #-}

-- | Is the file path relative?
--
-- >>> run isRelative "foo/bar"
-- True
--
-- >>> run isRelative "/foo/bar"
-- False
--
-- >>> run isRelative "./foo"
-- True
isRelative :: FilePatherA' Bool
isRelative = mkKleisli' FP.isRelative
{-# INLINE isRelative #-}

-- | Is the file path absolute?
--
-- >>> run isAbsolute "/foo/bar"
-- True
--
-- >>> run isAbsolute "foo/bar"
-- False
--
-- >>> run isAbsolute "/"
-- True
isAbsolute :: FilePatherA' Bool
isAbsolute = mkKleisli' FP.isAbsolute
{-# INLINE isAbsolute #-}

-- | Is the file path valid?
--
-- >>> run isValid "/tmp/foo"
-- True
--
-- >>> run isValid ""
-- False
isValid :: FilePatherA' Bool
isValid = mkKleisli' FP.isValid
{-# INLINE isValid #-}

-- | Make a file path valid by replacing invalid characters.
--
-- >>> run makeValid ""
-- "_"
--
-- >>> run makeValid "/tmp/foo"
-- "/tmp/foo"
makeValid :: FilePatherA' FilePath
makeValid = mkKleisli' FP.makeValid
{-# INLINE makeValid #-}

-- | Combine two file paths with a path separator. Synonym for 'combine'.
--
-- >>> run (combine "/tmp") "foo.hs"
-- "/tmp/foo.hs"
--
-- >>> run (combine "src") "Data/Foo.hs"
-- "src/Data/Foo.hs"
(</>) :: FilePath -> FilePatherA' FilePath
(</>) = combine
{-# INLINE (</>) #-}

-- | Add an extension to a file path. Synonym for 'addExtension'.
--
-- >>> run (addExtension ".hs") "/tmp/foo"
-- "/tmp/foo.hs"
--
-- >>> run (addExtension ".gz") "/tmp/foo.tar"
-- "/tmp/foo.tar.gz"
(<.>) :: String -> FilePatherA' FilePath
(<.>) = addExtension
{-# INLINE (<.>) #-}

-- | Replace the extension of a file path. Synonym for 'replaceExtension'.
--
-- >>> run (replaceExtension ".md") "/tmp/foo.hs"
-- "/tmp/foo.md"
--
-- >>> run (replaceExtension ".txt") "/tmp/foo"
-- "/tmp/foo.txt"
(-<.>) :: String -> FilePatherA' FilePath
(-<.>) = replaceExtension
{-# INLINE (-<.>) #-}

------------------------------------------------------------
-- System.Directory
------------------------------------------------------------

-- | Create a directory.
--
-- >>> let Kleisli f = createDirectory in f "/tmp/filepather_test_create_938271" >> Dir.removeDirectory "/tmp/filepather_test_create_938271"
createDirectory :: FilePatherIOA ()
createDirectory = Kleisli Dir.createDirectory
{-# INLINE createDirectory #-}

-- | Create a directory and any missing parent directories.
--
-- >>> let Kleisli f = createDirectoryIfMissing True in f "/tmp/filepather_test_mkdirs_938271/a/b" >> Dir.removeDirectoryRecursive "/tmp/filepather_test_mkdirs_938271"
createDirectoryIfMissing :: Bool -> FilePatherIOA ()
createDirectoryIfMissing b = Kleisli (Dir.createDirectoryIfMissing b)
{-# INLINE createDirectoryIfMissing #-}

-- | Remove an existing directory.
--
-- >>> Dir.createDirectory "/tmp/filepather_test_rmdir_938271"
-- >>> let Kleisli f = removeDirectory in f "/tmp/filepather_test_rmdir_938271"
removeDirectory :: FilePatherIOA ()
removeDirectory = Kleisli Dir.removeDirectory
{-# INLINE removeDirectory #-}

-- | Remove a directory and all its contents recursively.
--
-- >>> Dir.createDirectoryIfMissing True "/tmp/filepather_test_rmr_938271/x/y"
-- >>> let Kleisli f = removeDirectoryRecursive in f "/tmp/filepather_test_rmr_938271"
removeDirectoryRecursive :: FilePatherIOA ()
removeDirectoryRecursive = Kleisli Dir.removeDirectoryRecursive
{-# INLINE removeDirectoryRecursive #-}

-- | Remove a path forcibly (file or directory, recursively).
--
-- >>> Dir.createDirectory "/tmp/filepather_test_rmf_938271"
-- >>> let Kleisli f = removePathForcibly in f "/tmp/filepather_test_rmf_938271"
removePathForcibly :: FilePatherIOA ()
removePathForcibly = Kleisli Dir.removePathForcibly
{-# INLINE removePathForcibly #-}

-- | Rename a directory to a new path.
--
-- >>> Dir.createDirectory "/tmp/filepather_test_rend_938271"
-- >>> let Kleisli f = renameDirectory "/tmp/filepather_test_rend2_938271" in f "/tmp/filepather_test_rend_938271"
-- >>> Dir.removeDirectory "/tmp/filepather_test_rend2_938271"
renameDirectory :: FilePath -> FilePatherIOA ()
renameDirectory dest = Kleisli (`Dir.renameDirectory` dest)
{-# INLINE renameDirectory #-}

-- | List the contents of a directory (excluding @.@ and @..@).
--
-- >>> let Kleisli f = listDirectory in f "/" >>= \xs -> pure (not (null xs))
-- True
listDirectory :: FilePatherIOA [FilePath]
listDirectory = Kleisli Dir.listDirectory
{-# INLINE listDirectory #-}

-- | Get all entries in a directory (including @.@ and @..@).
--
-- >>> let Kleisli f = getDirectoryContents in f "/" >>= \xs -> pure (elem "." xs && elem ".." xs)
-- True
getDirectoryContents :: FilePatherIOA [FilePath]
getDirectoryContents = Kleisli Dir.getDirectoryContents
{-# INLINE getDirectoryContents #-}

-- | Set the current working directory.
--
-- >>> let Kleisli f = setCurrentDirectory in Dir.getCurrentDirectory >>= \old -> f "/tmp" >> Dir.getCurrentDirectory >>= \new -> Dir.setCurrentDirectory old >> pure (new == "/tmp")
-- True
setCurrentDirectory :: FilePatherIOA ()
setCurrentDirectory = Kleisli Dir.setCurrentDirectory
{-# INLINE setCurrentDirectory #-}

-- | Run an IO action with the given directory as the working directory.
--
-- >>> let Kleisli f = withCurrentDirectory (pure ()) in f "/tmp"
withCurrentDirectory :: IO a -> FilePatherIOA a
withCurrentDirectory act = Kleisli (`Dir.withCurrentDirectory` act)
{-# INLINE withCurrentDirectory #-}

-- | Get an XDG directory for the given application.
--
-- >>> let Kleisli f = getXdgDirectory Dir.XdgConfig in f "myapp" >>= \d -> pure (not (null d))
-- True
getXdgDirectory :: Dir.XdgDirectory -> FilePatherIOA FilePath
getXdgDirectory xdg = Kleisli (Dir.getXdgDirectory xdg)
{-# INLINE getXdgDirectory #-}

-- | Get the application user data directory.
--
-- >>> let Kleisli f = getAppUserDataDirectory in f "myapp" >>= \d -> pure (not (null d))
-- True
getAppUserDataDirectory :: FilePatherIOA FilePath
getAppUserDataDirectory = Kleisli Dir.getAppUserDataDirectory
{-# INLINE getAppUserDataDirectory #-}

-- | Remove a file.
--
-- >>> writeFile "/tmp/filepather_test_rm_938271" ""
-- >>> let Kleisli f = removeFile in f "/tmp/filepather_test_rm_938271"
removeFile :: FilePatherIOA ()
removeFile = Kleisli Dir.removeFile
{-# INLINE removeFile #-}

-- | Rename a file to a new path.
--
-- >>> writeFile "/tmp/filepather_test_renf_938271" ""
-- >>> let Kleisli f = renameFile "/tmp/filepather_test_renf2_938271" in f "/tmp/filepather_test_renf_938271"
-- >>> Dir.removeFile "/tmp/filepather_test_renf2_938271"
renameFile :: FilePath -> FilePatherIOA ()
renameFile dest = Kleisli (`Dir.renameFile` dest)
{-# INLINE renameFile #-}

-- | Rename a file or directory to a new path.
--
-- >>> writeFile "/tmp/filepather_test_renp_938271" ""
-- >>> let Kleisli f = renamePath "/tmp/filepather_test_renp2_938271" in f "/tmp/filepather_test_renp_938271"
-- >>> Dir.removeFile "/tmp/filepather_test_renp2_938271"
renamePath :: FilePath -> FilePatherIOA ()
renamePath dest = Kleisli (`Dir.renamePath` dest)
{-# INLINE renamePath #-}

-- | Copy a file to a new path.
--
-- >>> writeFile "/tmp/filepather_test_cp_938271" ""
-- >>> let Kleisli f = copyFile "/tmp/filepather_test_cp2_938271" in f "/tmp/filepather_test_cp_938271"
-- >>> Dir.removeFile "/tmp/filepather_test_cp_938271" >> Dir.removeFile "/tmp/filepather_test_cp2_938271"
copyFile :: FilePath -> FilePatherIOA ()
copyFile dest = Kleisli (`Dir.copyFile` dest)
{-# INLINE copyFile #-}

-- | Copy a file preserving metadata.
--
-- >>> writeFile "/tmp/filepather_test_cpm_938271" ""
-- >>> let Kleisli f = copyFileWithMetadata "/tmp/filepather_test_cpm2_938271" in f "/tmp/filepather_test_cpm_938271"
-- >>> Dir.removeFile "/tmp/filepather_test_cpm_938271" >> Dir.removeFile "/tmp/filepather_test_cpm2_938271"
copyFileWithMetadata :: FilePath -> FilePatherIOA ()
copyFileWithMetadata dest = Kleisli (`Dir.copyFileWithMetadata` dest)
{-# INLINE copyFileWithMetadata #-}

-- | Canonicalize a file path (resolve symlinks and relative components).
--
-- >>> let Kleisli f = canonicalizePath in f "/" >>= \d -> pure (d == "/")
-- True
--
-- >>> let Kleisli f = canonicalizePath in f "/tmp/../tmp" >>= \d -> pure (not (null d))
-- True
canonicalizePath :: FilePatherIOA FilePath
canonicalizePath = Kleisli Dir.canonicalizePath
{-# INLINE canonicalizePath #-}

-- | Make a file path absolute.
--
-- >>> let Kleisli f = makeAbsolute in f "/tmp" >>= \d -> pure (d == "/tmp")
-- True
--
-- >>> let Kleisli f = makeAbsolute in f "foo" >>= \d -> pure (head d == '/')
-- True
makeAbsolute :: FilePatherIOA FilePath
makeAbsolute = Kleisli Dir.makeAbsolute
{-# INLINE makeAbsolute #-}

-- | Make a file path relative to the current directory.
--
-- >>> let Kleisli f = makeRelativeToCurrentDirectory in f "/" >>= \d -> pure (not (null d))
-- True
makeRelativeToCurrentDirectory :: FilePatherIOA FilePath
makeRelativeToCurrentDirectory = Kleisli Dir.makeRelativeToCurrentDirectory
{-# INLINE makeRelativeToCurrentDirectory #-}

-- | Does the path exist (file or directory)?
--
-- >>> let Kleisli f = doesPathExist in f "/"
-- True
--
-- >>> let Kleisli f = doesPathExist in f "/nonexistent_path_938271"
-- False
doesPathExist :: FilePatherIOA Bool
doesPathExist = Kleisli Dir.doesPathExist
{-# INLINE doesPathExist #-}

-- | Does the file exist?
--
-- >>> let Kleisli f = doesFileExist in f "/nonexistent_file_938271"
-- False
doesFileExist :: FilePatherIOA Bool
doesFileExist = Kleisli Dir.doesFileExist
{-# INLINE doesFileExist #-}

-- | Does the directory exist?
--
-- >>> let Kleisli f = doesDirectoryExist in f "/"
-- True
--
-- >>> let Kleisli f = doesDirectoryExist in f "/nonexistent_dir_938271"
-- False
doesDirectoryExist :: FilePatherIOA Bool
doesDirectoryExist = Kleisli Dir.doesDirectoryExist
{-# INLINE doesDirectoryExist #-}

-- | Create a file symbolic link. The first argument is the link target.
--
-- >>> Dir.removePathForcibly "/tmp/filepather_test_flink_938271"
-- >>> let Kleisli f = createFileLink "/dev/null" in f "/tmp/filepather_test_flink_938271" >> Dir.pathIsSymbolicLink "/tmp/filepather_test_flink_938271" >>= \b -> Dir.removeFile "/tmp/filepather_test_flink_938271" >> pure b
-- True
createFileLink :: FilePath -> FilePatherIOA ()
createFileLink target = Kleisli (Dir.createFileLink target)
{-# INLINE createFileLink #-}

-- | Create a directory symbolic link. The first argument is the link target.
--
-- >>> Dir.removePathForcibly "/tmp/filepather_test_dlink_938271"
-- >>> let Kleisli f = createDirectoryLink "/tmp" in f "/tmp/filepather_test_dlink_938271" >> Dir.pathIsSymbolicLink "/tmp/filepather_test_dlink_938271" >>= \b -> Dir.removeDirectoryLink "/tmp/filepather_test_dlink_938271" >> pure b
-- True
createDirectoryLink :: FilePath -> FilePatherIOA ()
createDirectoryLink target = Kleisli (Dir.createDirectoryLink target)
{-# INLINE createDirectoryLink #-}

-- | Remove a directory symbolic link.
--
-- >>> Dir.removePathForcibly "/tmp/filepather_test_rmdl_938271"
-- >>> Dir.createDirectoryLink "/tmp" "/tmp/filepather_test_rmdl_938271"
-- >>> let Kleisli f = removeDirectoryLink in f "/tmp/filepather_test_rmdl_938271"
removeDirectoryLink :: FilePatherIOA ()
removeDirectoryLink = Kleisli Dir.removeDirectoryLink
{-# INLINE removeDirectoryLink #-}

-- | Get the target of a symbolic link.
--
-- >>> Dir.removePathForcibly "/tmp/filepather_test_gsl_938271"
-- >>> Dir.createFileLink "/dev/null" "/tmp/filepather_test_gsl_938271"
-- >>> let Kleisli f = getSymbolicLinkTarget in f "/tmp/filepather_test_gsl_938271" >>= \t -> Dir.removeFile "/tmp/filepather_test_gsl_938271" >> pure t
-- "/dev/null"
getSymbolicLinkTarget :: FilePatherIOA FilePath
getSymbolicLinkTarget = Kleisli Dir.getSymbolicLinkTarget
{-# INLINE getSymbolicLinkTarget #-}

-- | Is the path a symbolic link?
--
-- >>> let Kleisli f = isSymbolicLink in f "/"
-- False
isSymbolicLink :: FilePatherIOA Bool
isSymbolicLink = Kleisli Dir.pathIsSymbolicLink
{-# INLINE isSymbolicLink #-}

-- | Is the path a symbolic link? (Same as 'isSymbolicLink'.)
--
-- >>> let Kleisli f = pathIsSymbolicLink in f "/"
-- False
pathIsSymbolicLink :: FilePatherIOA Bool
pathIsSymbolicLink = Kleisli Dir.pathIsSymbolicLink
{-# INLINE pathIsSymbolicLink #-}

-- | Get the permissions of a file or directory.
--
-- >>> let Kleisli f = getPermissions in f "/" >>= \p -> pure (Dir.searchable p)
-- True
getPermissions :: FilePatherIOA Dir.Permissions
getPermissions = Kleisli Dir.getPermissions
{-# INLINE getPermissions #-}

-- | Set the permissions of a file or directory.
--
-- >>> writeFile "/tmp/filepather_test_sp_938271" ""
-- >>> let Kleisli f = setPermissions (Dir.setOwnerReadable True (Dir.setOwnerWritable True Dir.emptyPermissions)) in f "/tmp/filepather_test_sp_938271" >> Dir.getPermissions "/tmp/filepather_test_sp_938271" >>= \p -> Dir.removeFile "/tmp/filepather_test_sp_938271" >> pure (Dir.readable p)
-- True
setPermissions :: Dir.Permissions -> FilePatherIOA ()
setPermissions p = Kleisli (`Dir.setPermissions` p)
{-# INLINE setPermissions #-}

-- | Copy the permissions from one path to another.
--
-- >>> writeFile "/tmp/filepather_test_cpp_src_938271" ""
-- >>> writeFile "/tmp/filepather_test_cpp_938271" ""
-- >>> let Kleisli f = copyPermissions "/tmp/filepather_test_cpp_src_938271" in f "/tmp/filepather_test_cpp_938271" >> Dir.removeFile "/tmp/filepather_test_cpp_938271" >> Dir.removeFile "/tmp/filepather_test_cpp_src_938271"
copyPermissions :: FilePath -> FilePatherIOA ()
copyPermissions dest = Kleisli (`Dir.copyPermissions` dest)
{-# INLINE copyPermissions #-}

-- | Get the access time of a file or directory.
--
-- >>> let Kleisli f = getAccessTime in f "/" >>= \t -> pure (t > read "2000-01-01 00:00:00 UTC")
-- True
getAccessTime :: FilePatherIOA UTCTime
getAccessTime = Kleisli Dir.getAccessTime
{-# INLINE getAccessTime #-}

-- | Get the modification time of a file or directory.
--
-- >>> let Kleisli f = getModificationTime in f "/" >>= \t -> pure (t > read "2000-01-01 00:00:00 UTC")
-- True
getModificationTime :: FilePatherIOA UTCTime
getModificationTime = Kleisli Dir.getModificationTime
{-# INLINE getModificationTime #-}

-- | Set the access time of a file or directory.
--
-- >>> writeFile "/tmp/filepather_test_sat_938271" ""
-- >>> let Kleisli f = setAccessTime (read "2020-06-15 12:00:00 UTC") in f "/tmp/filepather_test_sat_938271" >> Dir.getAccessTime "/tmp/filepather_test_sat_938271" >>= \t -> Dir.removeFile "/tmp/filepather_test_sat_938271" >> pure (t == read "2020-06-15 12:00:00 UTC")
-- True
setAccessTime :: UTCTime -> FilePatherIOA ()
setAccessTime t = Kleisli (`Dir.setAccessTime` t)
{-# INLINE setAccessTime #-}

-- | Set the modification time of a file or directory.
--
-- >>> writeFile "/tmp/filepather_test_smt_938271" ""
-- >>> let Kleisli f = setModificationTime (read "2020-06-15 12:00:00 UTC") in f "/tmp/filepather_test_smt_938271" >> Dir.getModificationTime "/tmp/filepather_test_smt_938271" >>= \t -> Dir.removeFile "/tmp/filepather_test_smt_938271" >> pure (t == read "2020-06-15 12:00:00 UTC")
-- True
setModificationTime :: UTCTime -> FilePatherIOA ()
setModificationTime t = Kleisli (`Dir.setModificationTime` t)
{-# INLINE setModificationTime #-}

-- | Get the size of a file in bytes.
--
-- >>> writeFile "/tmp/filepather_test_gs_938271" "hello"
-- >>> let Kleisli f = getFileSize in f "/tmp/filepather_test_gs_938271" >>= \s -> Dir.removeFile "/tmp/filepather_test_gs_938271" >> pure s
-- 5
getFileSize :: FilePatherIOA Integer
getFileSize = Kleisli Dir.getFileSize
{-# INLINE getFileSize #-}