packages feed

directory-layout-0.1.0.0: src/System/Directory/Layout/Check.hs

{-# LANGUAGE UnicodeSyntax #-}
-- | Check if current directory layout agrees with specified one
--
-- For example, suppose there is a tree:
--
-- @
-- % tree
-- .
-- ├── baz
-- │   └── twey
-- └── foo
--     ├── bar
--     │   ├── quuz
--     │   └── tatata
--     └── quux
-- @
--
-- then you can write:
--
-- @
-- layout = do
--   directory \"baz\" $
--     file_ \"twey\"
--   directory \"foo\" $ do
--     directory \"bar\" $ do
--       file_ \"quuz\"
--       file_ \"tatata\"
--     file_ \"quux\"
-- @
--
-- and running @check layout \".\"@ should result in @[]@
module System.Directory.Layout.Check
  ( DLCheckFailure(..), check
  ) where

import Control.Arrow (second)
import Control.Monad (unless, when)

import           Control.Monad.Trans.Reader (ReaderT, runReaderT, ask, local)
import           Control.Monad.Trans.Writer (WriterT, execWriterT, tell)
import           Control.Monad.IO.Class (liftIO)
import           Control.Monad.Trans.Class (lift)
import           Data.Text (Text)
import qualified Data.Text.IO as T
import           System.FilePath ((</>), makeRelative)
import           System.Directory

import System.Directory.Layout.Internal


-- | Check directory layout corresponds to specified one
check ∷ Layout
      → FilePath            -- ^ Root directory
      → IO [DLCheckFailure] -- ^ List of failures
check z fp = do
  d ← getCurrentDirectory
  fp' ← canonicalizePath fp
  setCurrentDirectory fp'
  xs ← runCheckT (fp', fp') (f z)
  setCurrentDirectory d
  return xs
 where
  f (E _) = return ()
  f (F p Nothing x) = fileExists p >> f x
  f (F p (Just c) x) = fileExists p >>= \t → when t (fileContains p c) >> f x
  f (D p x y) = dirExists p >>= \t → when t (changeDir p (f x)) >> f y


-- | Data type representing various failures
-- that may occur while checking directory layout
data DLCheckFailure =
    FileDoesNotExist FilePath
  | FileWrongContents FilePath Text
  | DirectoryDoesNotExist FilePath
    deriving (Show, Read, Eq, Ord)


type CheckT = ReaderT (FilePath, FilePath) (WriterT [DLCheckFailure] IO)


runCheckT ∷ (FilePath, FilePath) → CheckT a → IO [DLCheckFailure]
runCheckT e = execWriterT . flip runReaderT e


-- | File existence check
-- emits 'FileDoesNotExist' on failure
fileExists ∷ FilePath → CheckT Bool
fileExists p = do
  (r, d) ← ask
  z ← io $ doesFileExist (d </> p)
  unless z $
    tell' [FileDoesNotExist (makeRelative r d </> p)]
  return z


-- | Directory existence check
-- emits 'DirectoryDoesNotExist' on failure
dirExists ∷ FilePath → CheckT Bool
dirExists p = do
  (r, d) ← ask
  z ← io $ doesDirectoryExist (d </> p)
  unless z $
    tell' [DirectoryDoesNotExist (makeRelative r d </> p)]
  return z


-- | File contents check
-- emits 'FileDoesNotExist' on failure
fileContains ∷ FilePath → Text → CheckT ()
fileContains p c = do
  (r, d) ← ask
  z ← io $ T.readFile (d </> p)
  unless (z == c) $
    tell' [FileWrongContents (makeRelative r d </> p) z]


changeDir ∷ FilePath → CheckT () → CheckT ()
changeDir fp = local (second (</> fp))


io ∷ IO a → CheckT a
io = liftIO


tell' ∷ [DLCheckFailure] → CheckT ()
tell' = lift . tell