{-# 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