    {-# LANGUAGE ScopedTypeVariables #-}
    {-# LANGUAGE CPP                 #-}
    --------------------------------------------------------------------------------
    -- | A module containing various file utility functions
    module Hakyll.Core.Util.File
        ( makeDirectories
        , getRecursiveContents
        , removeDirectory
        ) where

    --------------------------------------------------------------------------------
    import           Control.Monad       (filterM, forM)
    import           System.Directory    (createDirectoryIfMissing,
                                          doesDirectoryExist, getDirectoryContents)
    import           System.FilePath     (takeDirectory, (</>))
    #ifndef mingw32_HOST_OS
    import           Control.Monad       (when)
    import           System.Directory    (removeDirectoryRecursive)
    #else
    import           Control.Concurrent  (threadDelay)
    import           Control.Exception   (SomeException, catch)
    import           System.Directory    (removePathForcibly)
    #endif

    --------------------------------------------------------------------------------
    -- | Given a path to a file, try to make the path writable by making
    --   all directories on the path.
    makeDirectories :: FilePath -> IO ()
    makeDirectories :: FilePath -> IO ()
    makeDirectories = Bool -> FilePath -> IO ()
    createDirectoryIfMissing Bool
    True (FilePath -> IO ()) -> (FilePath -> FilePath) -> FilePath -> IO ()
    forall b c a. (b -> c) -> (a -> b) -> a -> c
    . FilePath -> FilePath
    takeDirectory

    --------------------------------------------------------------------------------
    -- | Get all contents of a directory.
    getRecursiveContents :: (FilePath -> IO Bool)  -- ^ Ignore this file/directory
                         -> FilePath               -- ^ Directory to search
                         -> IO [FilePath]          -- ^ List of files found
    getRecursiveContents :: (FilePath -> IO Bool) -> FilePath -> IO [FilePath]
    getRecursiveContents FilePath -> IO Bool
    ignore FilePath
    top = FilePath -> IO [FilePath]
    go FilePath
    ""
      where
        isProper :: FilePath -> IO Bool
    isProper FilePath
    x
            | FilePath
    x FilePath -> [FilePath] -> Bool
    forall a. Eq a => a -> [a] -> Bool
    forall (t :: * -> *) a. (Foldable t, Eq a) => a -> t a -> Bool
    `elem` [FilePath
    ".", FilePath
    ".."] = Bool -> IO Bool
    forall a. a -> IO a
    forall (m :: * -> *) a. Monad m => a -> m a
    return Bool
    False
            | Bool
    otherwise            = Bool -> Bool
    not (Bool -> Bool) -> IO Bool -> IO Bool
    forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
    <$> FilePath -> IO Bool
    ignore FilePath
    x

        go :: FilePath -> IO [FilePath]
    go FilePath
    dir     = do
            Bool
    dirExists <- FilePath -> IO Bool
    doesDirectoryExist (FilePath
    top FilePath -> FilePath -> FilePath
    </> FilePath
    dir)
            if Bool -> Bool
    not Bool
    dirExists
                then [FilePath] -> IO [FilePath]
    forall a. a -> IO a
    forall (m :: * -> *) a. Monad m => a -> m a
    return []
                else do
                    [FilePath]
    names <- (FilePath -> IO Bool) -> [FilePath] -> IO [FilePath]
    forall (m :: * -> *) a.
    Applicative m =>
    (a -> m Bool) -> [a] -> m [a]
    filterM FilePath -> IO Bool
    isProper ([FilePath] -> IO [FilePath]) -> IO [FilePath] -> IO [FilePath]
    forall (m :: * -> *) a b. Monad m => (a -> m b) -> m a -> m b
    =<< FilePath -> IO [FilePath]
    getDirectoryContents (FilePath
    top FilePath -> FilePath -> FilePath
    </> FilePath
    dir)
                    [[FilePath]]
    paths <- [FilePath] -> (FilePath -> IO [FilePath]) -> IO [[FilePath]]
    forall (t :: * -> *) (m :: * -> *) a b.
    (Traversable t, Monad m) =>
    t a -> (a -> m b) -> m (t b)
    forM [FilePath]
    names ((FilePath -> IO [FilePath]) -> IO [[FilePath]])
    -> (FilePath -> IO [FilePath]) -> IO [[FilePath]]
    forall a b. (a -> b) -> a -> b
    $ \FilePath
    name -> do
                        let rel :: FilePath
    rel = FilePath
    dir FilePath -> FilePath -> FilePath
    </> FilePath
    name
                        Bool
    isDirectory <- FilePath -> IO Bool
    doesDirectoryExist (FilePath
    top FilePath -> FilePath -> FilePath
    </> FilePath
    rel)
                        if Bool
    isDirectory
                            then FilePath -> IO [FilePath]
    go FilePath
    rel
                            else [FilePath] -> IO [FilePath]
    forall a. a -> IO a
    forall (m :: * -> *) a. Monad m => a -> m a
    return [FilePath
    rel]

                    [FilePath] -> IO [FilePath]
    forall a. a -> IO a
    forall (m :: * -> *) a. Monad m => a -> m a
    return ([FilePath] -> IO [FilePath]) -> [FilePath] -> IO [FilePath]
    forall a b. (a -> b) -> a -> b
    $ [[FilePath]] -> [FilePath]
    forall (t :: * -> *) a. Foldable t => t [a] -> [a]
    concat [[FilePath]]
    paths

    --------------------------------------------------------------------------------
    removeDirectory :: FilePath -> IO ()
    #ifndef mingw32_HOST_OS
    removeDirectory :: FilePath -> IO ()
    removeDirectory FilePath
    fp = do
        Bool
    e <- FilePath -> IO Bool
    doesDirectoryExist FilePath
    fp
        Bool -> IO () -> IO ()
    forall (f :: * -> *). Applicative f => Bool -> f () -> f ()
    when Bool
    e (IO () -> IO ()) -> IO () -> IO ()
    forall a b. (a -> b) -> a -> b
    $ FilePath -> IO ()
    removeDirectoryRecursive FilePath
    fp
    #else
    -- Deleting files on Windows is unreliable. If a file/directory is open by a program (e.g. antivirus),
    -- then removing related directories *quickly* may fail with strange messages.
    -- See here for discussions:
    --      https://github.com/haskell/directory/issues/96
    --      https://github.com/haskell/win32/pull/129
    --
    -- The hacky solution is to retry deleting directories a few times,
    -- with a delay, on Windows only.
    removeDirectory = retryWithDelay 10 . removePathForcibly

    --------------------------------------------------------------------------------
    -- | Retry an operation at most /n/ times (/n/ must be positive).
    --   If the operation fails the /n/th time it will throw that final exception.
    --   A delay of 100ms is introduced between every retry.
    retryWithDelay :: Int -> IO a -> IO a
    retryWithDelay i x
        | i <= 0    = error "Hakyll.Core.Util.File.retry: retry count must be 1 or more"
        | i == 1    = x
        | otherwise = catch x $ \(_::SomeException) -> threadDelay 100 >> retryWithDelay (i-1) x
    #endif