{-|
Module      : Headroom.FileSystem
Description : Files/directories manipulation
Copyright   : (c) 2019-2020 Vaclav Svejcar
License     : BSD-3
Maintainer  : vaclav.svejcar@gmail.com
Stability   : experimental
Portability : POSIX

Functions for manipulating files and directories.
-}
{-# LANGUAGE NoImplicitPrelude #-}
module Headroom.FileSystem
  ( findFiles
  , findFilesByExts
  , findFilesByTypes
  , listFiles
  , loadFile
  )
where

import           Headroom.FileType              ( FileType
                                                , listExtensions
                                                )
import           RIO
import           RIO.Directory                  ( doesDirectoryExist
                                                , getDirectoryContents
                                                )
import           RIO.FilePath                   ( isExtensionOf
                                                , (</>)
                                                )
import qualified RIO.Text                      as T


-- | Recursively finds files on given path whose filename matches the predicate.
findFiles :: MonadIO m
          => FilePath           -- ^ path to search
          -> (FilePath -> Bool) -- ^ predicate to match filename
          -> m [FilePath]       -- ^ found files
findFiles :: FilePath -> (FilePath -> Bool) -> m [FilePath]
findFiles path :: FilePath
path predicate :: FilePath -> Bool
predicate = ([FilePath] -> [FilePath]) -> m [FilePath] -> m [FilePath]
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
fmap ((FilePath -> Bool) -> [FilePath] -> [FilePath]
forall a. (a -> Bool) -> [a] -> [a]
filter FilePath -> Bool
predicate) (FilePath -> m [FilePath]
forall (m :: * -> *). MonadIO m => FilePath -> m [FilePath]
listFiles FilePath
path)

-- | Recursively finds files on given path by file extensions.
findFilesByExts :: MonadIO m
                => FilePath     -- ^ path to search
                -> [Text]       -- ^ list of file extensions (without dot)
                -> m [FilePath] -- ^ list of found files
findFilesByExts :: FilePath -> [Text] -> m [FilePath]
findFilesByExts path :: FilePath
path exts :: [Text]
exts = FilePath -> (FilePath -> Bool) -> m [FilePath]
forall (m :: * -> *).
MonadIO m =>
FilePath -> (FilePath -> Bool) -> m [FilePath]
findFiles FilePath
path FilePath -> Bool
predicate
  where predicate :: FilePath -> Bool
predicate p :: FilePath
p = (FilePath -> Bool) -> [FilePath] -> Bool
forall (t :: * -> *) a. Foldable t => (a -> Bool) -> t a -> Bool
any (FilePath -> FilePath -> Bool
`isExtensionOf` FilePath
p) ((Text -> FilePath) -> [Text] -> [FilePath]
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
fmap Text -> FilePath
T.unpack [Text]
exts)

-- | Recursively find files on given path by their file types.
findFilesByTypes :: MonadIO m
                 => FilePath     -- ^ path to search
                 -> [FileType]   -- ^ list of file types
                 -> m [FilePath] -- ^ list of found files
findFilesByTypes :: FilePath -> [FileType] -> m [FilePath]
findFilesByTypes path :: FilePath
path types :: [FileType]
types = FilePath -> [Text] -> m [FilePath]
forall (m :: * -> *).
MonadIO m =>
FilePath -> [Text] -> m [FilePath]
findFilesByExts FilePath
path ([FileType]
types [FileType] -> (FileType -> [Text]) -> [Text]
forall (m :: * -> *) a b. Monad m => m a -> (a -> m b) -> m b
>>= FileType -> [Text]
listExtensions)

-- | Recursively find all files on given path. If file reference is passed
-- instead of directory, such file path is returned.
listFiles :: MonadIO m
          => FilePath     -- ^ path to search
          -> m [FilePath] -- ^ list of found files
listFiles :: FilePath -> m [FilePath]
listFiles fileOrDir :: FilePath
fileOrDir = do
  Bool
isDir <- FilePath -> m Bool
forall (m :: * -> *). MonadIO m => FilePath -> m Bool
doesDirectoryExist FilePath
fileOrDir
  if Bool
isDir then FilePath -> m [FilePath]
forall (m :: * -> *). MonadIO m => FilePath -> m [FilePath]
listDirectory FilePath
fileOrDir else [FilePath] -> m [FilePath]
forall (m :: * -> *) a. Monad m => a -> m a
return [FilePath
fileOrDir]
 where
  listDirectory :: FilePath -> m [FilePath]
listDirectory dir :: FilePath
dir = do
    [FilePath]
names <- FilePath -> m [FilePath]
forall (m :: * -> *). MonadIO m => FilePath -> m [FilePath]
getDirectoryContents FilePath
dir
    let filteredNames :: [FilePath]
filteredNames = (FilePath -> Bool) -> [FilePath] -> [FilePath]
forall a. (a -> Bool) -> [a] -> [a]
filter (FilePath -> [FilePath] -> Bool
forall (t :: * -> *) a. (Foldable t, Eq a) => a -> t a -> Bool
`notElem` [".", ".."]) [FilePath]
names
    [[FilePath]]
paths <- [FilePath] -> (FilePath -> m [FilePath]) -> m [[FilePath]]
forall (t :: * -> *) (m :: * -> *) a b.
(Traversable t, Monad m) =>
t a -> (a -> m b) -> m (t b)
forM [FilePath]
filteredNames ((FilePath -> m [FilePath]) -> m [[FilePath]])
-> (FilePath -> m [FilePath]) -> m [[FilePath]]
forall a b. (a -> b) -> a -> b
$ \name :: FilePath
name -> do
      let path :: FilePath
path = FilePath
dir FilePath -> FilePath -> FilePath
</> FilePath
name
      Bool
isDirectory <- FilePath -> m Bool
forall (m :: * -> *). MonadIO m => FilePath -> m Bool
doesDirectoryExist FilePath
path
      if Bool
isDirectory then FilePath -> m [FilePath]
forall (m :: * -> *). MonadIO m => FilePath -> m [FilePath]
listFiles FilePath
path else [FilePath] -> m [FilePath]
forall (m :: * -> *) a. Monad m => a -> m a
return [FilePath
path]
    [FilePath] -> m [FilePath]
forall (m :: * -> *) a. Monad m => a -> m a
return ([FilePath] -> m [FilePath]) -> [FilePath] -> m [FilePath]
forall a b. (a -> b) -> a -> b
$ [[FilePath]] -> [FilePath]
forall (t :: * -> *) a. Foldable t => t [a] -> [a]
concat [[FilePath]]
paths

-- | Loads file content in UTF8 encoding.
loadFile :: MonadIO m
         => FilePath -- ^ file path
         -> m Text   -- ^ file content
loadFile :: FilePath -> m Text
loadFile = FilePath -> m Text
forall (m :: * -> *). MonadIO m => FilePath -> m Text
readFileUtf8