-- module: Hscaffold -- author: Pedro Tacla Yamada -- synopsis: Very simple file/directory structure scaffolding writer monad EDSL {-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE FlexibleInstances #-} {-# LANGUAGE GeneralizedNewtypeDeriving #-} {-# LANGUAGE MultiParamTypeClasses #-} module Hscaffold ( runHscaffold , runAction , runWriter , runWriterT , directory , file , link , copy , fileWith , directoryWith , permissions , Permissions(..) , ScaffoldActionType(..) , ScaffoldAction , ScaffoldActionV , module Data.Text , module Control.Monad.IO.Class , module Control.Monad.Writer , module System.Directory , module System.FilePath ) where import Control.Applicative import Control.Monad.IO.Class import Control.Monad.Writer import Data.Text (Text) import qualified Data.Text import qualified Data.Text.IO as Text import System.Directory -- TODO - Disable this on Windows import System.FilePath import System.Posix.Files -- | Run the scaffolding writer on the IO monad with no extensions -- -- @ -- runHscaffold "." $ do -- file "./.gitignore" (Text.unlines [ ".stack-work" -- , "stuff" -- , "here" -- ]) -- directory "./src" $ do -- file "./Main.hs" "main = putStrLn \\"Hello World\\"" -- file "./Other.hs" "other = putStrLn \\"Hello You\\"" -- @ runHscaffold :: FilePath -> WriterT ScaffoldActionV IO a -> IO a runHscaffold root w = do (o, ws) <- runWriterT w mapM_ (runAction root) ws return o -- | Run a single scaffolding action on the IO monad with no extensions runAction :: FilePath -> ScaffoldActionType () -> IO () runAction root (SetPermissions perms fp) = setPermissions fp perms runAction root (Link fp1 fp2) = createSymbolicLink fp1 fp2 runAction root (File fp txt) = Text.writeFile (root fp) txt runAction root (Directory fp nested) = do createDirectoryIfMissing True (root fp) mapM_ (runAction (root fp)) nested runAction root (Copy fp1 fp2) = do let fp1' = makeAbsolute fp1 fp2' = makeAbsolute fp2 copyFile fp1' fp2' where makeAbsolute fp = if isAbsolute fp then fp else root fp -- | Accumulator for actions type ScaffoldAction e = [ScaffoldActionType e] -- | Accumulator for actions set with void extension type ScaffoldActionV = ScaffoldAction () -- | Type of actions scaffolding can make, 'ScaffoldActionTypeExtension' is open -- for extension through other data-types data ScaffoldActionType e = File FilePath Text | Link FilePath FilePath | Directory FilePath (ScaffoldAction e) | SetPermissions Permissions FilePath | Copy FilePath FilePath | ScaffoldActionTypeExtension e deriving(Show, Eq, Ord) -- | Create a directory with the nested contents directory :: MonadWriter (ScaffoldAction e) m => FilePath -> WriterT (ScaffoldAction e) m b -> m b directory fp nested = do (x, nested') <- runWriterT nested tell [Directory fp nested'] return x -- | Create a directory with the nested contents and permissions directoryWith :: MonadWriter (ScaffoldAction e) m => Permissions -> FilePath -> WriterT (ScaffoldAction e) m b -> m b directoryWith perms fp nested = do x <- directory fp nested tell [SetPermissions perms fp] return x -- | Create a file with the given contents file :: MonadWriter (ScaffoldAction e) m => FilePath -> Text -> m () file fp txt = tell [File fp txt] -- | Create a file with the given contents and permissions fileWith :: MonadWriter (ScaffoldAction e) m => Permissions -> FilePath -> Text -> m () fileWith perms fp txt = do file fp txt tell [SetPermissions perms fp] -- | Set permissions on a filepath permissions :: MonadWriter (ScaffoldAction e) m => FilePath -> Permissions -> m () permissions fp perms = tell [SetPermissions perms fp] -- | Create a symbolic link between two filepaths link :: MonadWriter (ScaffoldAction e) m => FilePath -> FilePath -> m () link fp1 fp2 = tell [Link fp1 fp2] -- | Copy a file from A to B -- -- *Non-absolute paths are treated relative to the root* copy :: MonadWriter (ScaffoldAction e) m => FilePath -> FilePath -> m () copy fp1 fp2 = tell [Copy fp1 fp2]