packages feed

axel-0.0.11: src/Axel/Eff/FileSystem.hs

{-# LANGUAGE GADTs #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TemplateHaskell #-}

module Axel.Eff.FileSystem where

import Axel.Prelude

import Control.Monad (forM)

import qualified Data.Text as T
import qualified Data.Text.IO as T

import qualified Polysemy as Sem

import Axel.Utils.FilePath ((</>))
import qualified System.Directory
  ( copyFile
  , createDirectoryIfMissing
  , doesDirectoryExist
  , getCurrentDirectory
  , getDirectoryContents
  , getTemporaryDirectory
  , removeFile
  , setCurrentDirectory
  )

data FileSystem m a where
  AppendFile :: FilePath -> Text -> FileSystem m ()
  CopyFile :: FilePath -> FilePath -> FileSystem m ()
  CreateDirectoryIfMissing :: Bool -> FilePath -> FileSystem m ()
  DoesDirectoryExist :: FilePath -> FileSystem m Bool
  GetCurrentDirectory :: FileSystem m FilePath
  GetDirectoryContents :: FilePath -> FileSystem m [FilePath]
  GetTemporaryDirectory :: FileSystem m FilePath
  ReadFile :: FilePath -> FileSystem m Text
  RemoveFile :: FilePath -> FileSystem m ()
  SetCurrentDirectory :: FilePath -> FileSystem m ()
  WriteFile :: FilePath -> Text -> FileSystem m ()

Sem.makeSem ''FileSystem

runFileSystem ::
     (Sem.Member (Sem.Embed IO) effs)
  => Sem.Sem (FileSystem ': effs) a
  -> Sem.Sem effs a
runFileSystem =
  Sem.interpret
    (\case
       AppendFile (FilePath path) contents ->
         Sem.embed $ T.appendFile (T.unpack path) contents
       CopyFile (FilePath src) (FilePath dest) ->
         Sem.embed $ System.Directory.copyFile (T.unpack src) (T.unpack dest)
       CreateDirectoryIfMissing createParentDirs (FilePath path) ->
         Sem.embed $
         System.Directory.createDirectoryIfMissing
           createParentDirs
           (T.unpack path)
       DoesDirectoryExist (FilePath path) ->
         Sem.embed $ System.Directory.doesDirectoryExist (T.unpack path)
       GetCurrentDirectory ->
         Sem.embed $ FilePath . T.pack <$> System.Directory.getCurrentDirectory
       GetDirectoryContents (FilePath path) ->
         Sem.embed $
         map (FilePath . T.pack) <$>
         System.Directory.getDirectoryContents (T.unpack path)
       GetTemporaryDirectory ->
         Sem.embed $
         FilePath . T.pack <$> System.Directory.getTemporaryDirectory
       ReadFile (FilePath path) -> Sem.embed $ T.readFile (T.unpack path)
       RemoveFile (FilePath path) ->
         Sem.embed $ System.Directory.removeFile (T.unpack path)
       SetCurrentDirectory (FilePath path) ->
         Sem.embed $ System.Directory.setCurrentDirectory (T.unpack path)
       WriteFile (FilePath path) contents ->
         Sem.embed $ T.writeFile (T.unpack path) contents)

-- Adapted from http://book.realworldhaskell.org/read/io-case-study-a-library-for-searching-the-filesystem.html.
getDirectoryContentsRec ::
     (Sem.Member FileSystem effs) => FilePath -> Sem.Sem effs [FilePath]
getDirectoryContentsRec dir = do
  names <- getDirectoryContents dir
  let properNames =
        filter (\(FilePath path) -> path `notElem` [".", ".."]) names
  paths <-
    forM properNames $ \name -> do
      let path = dir </> name
      isDirectory <- doesDirectoryExist path
      if isDirectory
        then getDirectoryContentsRec path
        else pure [path]
  pure $ concat paths

withCurrentDirectory ::
     (Sem.Member FileSystem effs)
  => FilePath
  -> Sem.Sem effs a
  -> Sem.Sem effs a
withCurrentDirectory directory f = do
  originalDirectory <- getCurrentDirectory
  setCurrentDirectory directory
  result <- f
  setCurrentDirectory originalDirectory
  pure result

withTemporaryDirectory ::
     (Sem.Member FileSystem effs)
  => (FilePath -> Sem.Sem effs a)
  -> Sem.Sem effs a
withTemporaryDirectory action = getTemporaryDirectory >>= action