    {-# LANGUAGE CPP #-}

    #ifndef MIN_VERSION_directory
    #if __GLASGOW_HASKELL__ >= 711
    #define MIN_VERSION_directory(a,b,c) 1
    #else
    #define MIN_VERSION_directory(a,b,c) 0
    #endif
    #endif

    -- | Extra directory functions. Most of these functions provide cleaned up and generalised versions
    --   of 'getDirectoryContents', see 'listContents' for the differences.
    module System.Directory.Extra(
        module System.Directory,
    #if !MIN_VERSION_directory(1,2,3)
        withCurrentDirectory,
    #endif
        createDirectoryPrivate,
        listContents, listDirectories, listFiles, listFilesInside, listFilesRecursive
        ) where

    import System.Directory
    import Control.Monad.Extra
    import System.FilePath
    import Data.List
    #if !MIN_VERSION_directory(1,2,3)
    import Control.Exception
    #endif

    #ifndef mingw32_HOST_OS
    import qualified System.Posix
    #endif

    #if !MIN_VERSION_directory(1,2,3)
    -- | Set the current directory, perform an operation, then change back.
    --   Remember that the current directory is a global variable, so calling this function
    --   multithreaded is almost certain to go wrong. Avoid changing the current directory if you can.
    --
    -- > withTempDir $ \dir -> do writeFile (dir </> "foo.txt") ""; withCurrentDirectory dir $ doesFileExist "foo.txt"
    withCurrentDirectory :: FilePath -> IO a -> IO a
    withCurrentDirectory dir act =
        bracket getCurrentDirectory setCurrentDirectory $ const $ do
            setCurrentDirectory dir; act
    #endif

    -- | List the files and directories directly within a directory.
    --   Each result will be prefixed by the query directory, and the special directories @.@ and @..@ will be ignored.
    --   Intended as a cleaned up version of 'getDirectoryContents'.
    --
    -- > withTempDir $ \dir -> do writeFile (dir </> "test.txt") ""; (== [dir </> "test.txt"]) <$> listContents dir
    -- > let touch = mapM_ $ \x -> createDirectoryIfMissing True (takeDirectory x) >> writeFile x ""
    -- > let listTest op as bs = withTempDir $ \dir -> do touch $ map (dir </>) as; res <- op dir; pure $ map (drop (length dir + 1)) res == bs
    -- > listTest listContents ["bar.txt","foo/baz.txt","zoo"] ["bar.txt","foo","zoo"]
    listContents :: FilePath -> IO [FilePath]
    listContents :: FilePath -> IO [FilePath]
    listContents dir :: FilePath
    dir = do
        [FilePath]
    xs <- FilePath -> IO [FilePath]
    getDirectoryContents FilePath
    dir
        [FilePath] -> IO [FilePath]
    forall (f :: * -> *) a. Applicative f => a -> f a
    pure ([FilePath] -> IO [FilePath]) -> [FilePath] -> IO [FilePath]
    forall a b. (a -> b) -> a -> b
    $ [FilePath] -> [FilePath]
    forall a. Ord a => [a] -> [a]
    sort [FilePath
    dir FilePath -> FilePath -> FilePath
    </> FilePath
    x | FilePath
    x <- [FilePath]
    xs, Bool -> Bool
    not (Bool -> Bool) -> Bool -> Bool
    forall a b. (a -> b) -> a -> b
    $ (Char -> Bool) -> FilePath -> Bool
    forall (t :: * -> *) a. Foldable t => (a -> Bool) -> t a -> Bool
    all (Char -> Char -> Bool
    forall a. Eq a => a -> a -> Bool
    == '.') FilePath
    x]

    -- | Like 'listContents', but only returns the directories in a directory, not the files.
    --   Each directory will be prefixed by the query directory.
    --
    -- > listTest listDirectories ["bar.txt","foo/baz.txt","zoo"] ["foo"]
    listDirectories :: FilePath -> IO [FilePath]
    listDirectories :: FilePath -> IO [FilePath]
    listDirectories dir :: FilePath
    dir = (FilePath -> IO Bool) -> [FilePath] -> IO [FilePath]
    forall (m :: * -> *) a.
    Applicative m =>
    (a -> m Bool) -> [a] -> m [a]
    filterM FilePath -> IO Bool
    doesDirectoryExist ([FilePath] -> IO [FilePath]) -> IO [FilePath] -> IO [FilePath]
    forall (m :: * -> *) a b. Monad m => (a -> m b) -> m a -> m b
    =<< FilePath -> IO [FilePath]
    listContents FilePath
    dir

    -- | Like 'listContents', but only returns the files in a directory, not other directories.
    --   Each file will be prefixed by the query directory.
    --
    -- > listTest listFiles ["bar.txt","foo/baz.txt","zoo"] ["bar.txt","zoo"]
    listFiles :: FilePath -> IO [FilePath]
    listFiles :: FilePath -> IO [FilePath]
    listFiles dir :: FilePath
    dir = (FilePath -> IO Bool) -> [FilePath] -> IO [FilePath]
    forall (m :: * -> *) a.
    Applicative m =>
    (a -> m Bool) -> [a] -> m [a]
    filterM FilePath -> IO Bool
    doesFileExist ([FilePath] -> IO [FilePath]) -> IO [FilePath] -> IO [FilePath]
    forall (m :: * -> *) a b. Monad m => (a -> m b) -> m a -> m b
    =<< FilePath -> IO [FilePath]
    listContents FilePath
    dir

    -- | Like 'listFiles', but goes recursively through all subdirectories.
    --   This function will follow symlinks, and if they form a loop, this function will not terminate.
    --
    -- > listTest listFilesRecursive ["bar.txt","zoo","foo" </> "baz.txt"] ["bar.txt","zoo","foo" </> "baz.txt"]
    listFilesRecursive :: FilePath -> IO [FilePath]
    listFilesRecursive :: FilePath -> IO [FilePath]
    listFilesRecursive = (FilePath -> IO Bool) -> FilePath -> IO [FilePath]
    listFilesInside (IO Bool -> FilePath -> IO Bool
    forall a b. a -> b -> a
    const (IO Bool -> FilePath -> IO Bool) -> IO Bool -> FilePath -> IO Bool
    forall a b. (a -> b) -> a -> b
    $ Bool -> IO Bool
    forall (f :: * -> *) a. Applicative f => a -> f a
    pure Bool
    True)

    -- | Like 'listFilesRecursive', but with a predicate to decide where to recurse into.
    --   Typically directories starting with @.@ would be ignored. The initial argument directory
    --   will have the test applied to it.
    --
    -- > listTest (listFilesInside $ pure . not . isPrefixOf "." . takeFileName)
    -- >     ["bar.txt","foo" </> "baz.txt",".foo" </> "baz2.txt", "zoo"] ["bar.txt","zoo","foo" </> "baz.txt"]
    -- > listTest (listFilesInside $ const $ pure False) ["bar.txt"] []
    listFilesInside :: (FilePath -> IO Bool) -> FilePath -> IO [FilePath]
    listFilesInside :: (FilePath -> IO Bool) -> FilePath -> IO [FilePath]
    listFilesInside test :: FilePath -> IO Bool
    test dir :: FilePath
    dir = IO Bool -> IO [FilePath] -> IO [FilePath] -> IO [FilePath]
    forall (m :: * -> *) a. Monad m => m Bool -> m a -> m a -> m a
    ifM (IO Bool -> IO Bool
    forall (m :: * -> *). Functor m => m Bool -> m Bool
    notM (IO Bool -> IO Bool) -> IO Bool -> IO Bool
    forall a b. (a -> b) -> a -> b
    $ FilePath -> IO Bool
    test (FilePath -> IO Bool) -> FilePath -> IO Bool
    forall a b. (a -> b) -> a -> b
    $ FilePath -> FilePath
    dropTrailingPathSeparator FilePath
    dir) ([FilePath] -> IO [FilePath]
    forall (f :: * -> *) a. Applicative f => a -> f a
    pure []) (IO [FilePath] -> IO [FilePath]) -> IO [FilePath] -> IO [FilePath]
    forall a b. (a -> b) -> a -> b
    $ do
        (dirs :: [FilePath]
    dirs,files :: [FilePath]
    files) <- (FilePath -> IO Bool) -> [FilePath] -> IO ([FilePath], [FilePath])
    forall (m :: * -> *) a.
    Monad m =>
    (a -> m Bool) -> [a] -> m ([a], [a])
    partitionM FilePath -> IO Bool
    doesDirectoryExist ([FilePath] -> IO ([FilePath], [FilePath]))
    -> IO [FilePath] -> IO ([FilePath], [FilePath])
    forall (m :: * -> *) a b. Monad m => (a -> m b) -> m a -> m b
    =<< FilePath -> IO [FilePath]
    listContents FilePath
    dir
        [FilePath]
    rest <- (FilePath -> IO [FilePath]) -> [FilePath] -> IO [FilePath]
    forall (m :: * -> *) a b. Monad m => (a -> m [b]) -> [a] -> m [b]
    concatMapM ((FilePath -> IO Bool) -> FilePath -> IO [FilePath]
    listFilesInside FilePath -> IO Bool
    test) [FilePath]
    dirs
        [FilePath] -> IO [FilePath]
    forall (f :: * -> *) a. Applicative f => a -> f a
    pure ([FilePath] -> IO [FilePath]) -> [FilePath] -> IO [FilePath]
    forall a b. (a -> b) -> a -> b
    $ [FilePath]
    files [FilePath] -> [FilePath] -> [FilePath]
    forall a. [a] -> [a] -> [a]
    ++ [FilePath]
    rest

    -- | Create a directory with permissions so that only the current user can view it.
    --   On Windows this function is equivalent to 'createDirectory'.
    createDirectoryPrivate :: String -> IO ()
    #ifdef mingw32_HOST_OS
    createDirectoryPrivate s = createDirectory s
    #else
    createDirectoryPrivate :: FilePath -> IO ()
    createDirectoryPrivate s :: FilePath
    s = FilePath -> FileMode -> IO ()
    System.Posix.createDirectory FilePath
    s 0o700
    #endif