packages feed

gipeda-0.3: src/Development/Shake/Fancy.hs

{-# LANGUAGE GeneralizedNewtypeDeriving, ConstraintKinds #-}
module Development.Shake.Fancy
    ( module Development.Shake

    , cmdWrap

    , shake
    , shakeArgs
    , Action
    , action
    , actionFinally
    , putNormal
    , (%>)
    , (~>)
    , writeFile'
    , writeFileChanged
    , need
    , orderOnly
    , readFileLines
    , liftAction
    , doesFileExist
    , addOracle
    , newCache
    , alwaysRerun
    , readFile'
    )
    where

import Development.Shake hiding
    ( Action
    , shake
    , shakeArgs
    , action
    , actionFinally
    , putNormal
    , (%>)
    , (~>)
    , writeFile'
    , writeFileChanged
    , need
    , orderOnly
    , readFileLines
    , doesFileExist
    , addOracle
    , newCache
    , alwaysRerun
    , readFile'
    )
import qualified Development.Shake as S
import qualified Development.Shake.Rule as S
import qualified Development.Shake.Classes as S
import qualified Development.Shake.Command as S
import System.Console.Concurrent
import System.Console.Regions
import Control.Monad.Trans.Reader
import Control.Monad.IO.Class
import Control.Monad
import Control.Applicative
import Data.List

-- | Wrapper around 'S.shake'
shake :: ShakeOptions -> Rules () -> IO ()
shake opts rules = displayConsoleRegions $ S.shake opts rules

-- | Wrapper around 'S.shakeArgs'
shakeArgs :: ShakeOptions -> Rules () -> IO ()
shakeArgs opts rules = displayConsoleRegions $ S.shakeArgs opts rules

data FancyEnv = FancyEnv
    { currentTarget :: String
    , currentRegion :: ConsoleRegion
    }

-- | Wrapper around 'S.Action'
newtype Action a = Action (ReaderT FancyEnv S.Action a)
    deriving (Monad, Applicative, Functor, MonadIO)

runAction :: Action a -> FancyEnv -> S.Action a
runAction (Action fa) = runReaderT fa

mkAction :: (FancyEnv -> S.Action a) -> Action a
mkAction act = Action (ReaderT act)

liftAction :: S.Action a -> Action a
liftAction act = mkAction (const act)


finish :: FancyEnv -> IO ()
finish env = finishConsoleRegion (currentRegion env) $
    "✓ " ++ currentTarget env ++ " done"

wrapAction :: Action a -> String -> S.Action a
wrapAction act target = do
    region <- liftIO $ openConsoleRegion Linear
    let env = FancyEnv target region
    runAction act env `S.actionFinally` finish env

setDefaultMessage :: Action ()
setDefaultMessage = mkAction $ \env ->
   liftIO $ setConsoleRegion (currentRegion env) $
    "  " ++ currentTarget env ++ " processing..."

setMessage :: Char -> String -> Action ()
setMessage c doing = mkAction $ \env ->
   liftIO $ setConsoleRegion (currentRegion env) $
    [c] ++ " " ++ currentTarget env ++ " " ++ doing


describe :: S.Action a -> Char -> String -> Action a
describe act symb desc = do
    setMessage symb desc
    x <- liftAction act
    setDefaultMessage
    return x

-- | Wrapper around 'S.action'
action :: Action a -> Rules ()
action act = S.action $ wrapAction act "some action"

-- | Wrapper around 'S.actionFinally'
actionFinally :: Action a -> IO b -> Action a
actionFinally act io = mkAction $ \env -> runAction act env `S.actionFinally` io

-- | Wrapper around 'S.putNormal'
putNormal :: String -> Action ()
putNormal txt = mkAction $ \env -> do
    verb <- getVerbosity
    when (Normal >= verb) $ liftIO $ outputConcurrent $ currentTarget env ++ ": " ++ txt

-- | Wrapper around '%>'
(%>) :: FilePattern -> (FilePath -> Action ()) -> Rules () 
pat %> act = pat S.%> (\out -> wrapAction (act out) out)

(~>) :: String -> Action () -> Rules ()
target ~> act = target S.~> wrapAction act target


-- | Wrapper around 'writeFile''
writeFile' :: FilePath -> String -> Action ()
writeFile' filepath content =
    describe (S.writeFile' filepath content) '→' ("writing " ++ filepath)

-- | Wrapper around 'writeFile''
writeFileChanged :: FilePath -> String -> Action ()
writeFileChanged filepath content =
    describe (S.writeFileChanged filepath content) '→' ("writing " ++ filepath)

readFileLines :: FilePath -> Action [String]
readFileLines filepath =
    describe (S.readFileLines filepath) '←' ("reading " ++ filepath)

doesFileExist :: FilePath -> Action Bool
doesFileExist filepath = liftAction $ S.doesFileExist filepath

need :: [FilePath] -> Action ()
need filepaths =
    describe (S.need filepaths) '…' ("waiting for " ++ take 60 (intercalate ", " filepaths))

readFile' :: FilePath -> Action String
readFile' x = need [x] >> liftIO (readFile x)

orderOnly :: [FilePath] -> Action ()
orderOnly filepaths =
    describe (S.orderOnly filepaths) '…' ("waiting for " ++ take 60 (intercalate ", " filepaths))


alwaysRerun :: Action ()
alwaysRerun = liftAction S.alwaysRerun

cmdWrap :: String -> S.Action a -> Action a
cmdWrap cmd act =
    describe (quietly act) '!' ("running " ++ cmd)

addOracle :: (S.ShakeValue q, S.ShakeValue a) => (q -> Action a) -> S.Rules (q -> Action a)
addOracle action = do
    query <- S.addOracle (\q -> wrapAction (action q) (show q))
    return $ liftAction . query

newCache :: (Eq k, S.Hashable k) => (k -> Action v) -> Rules (k -> Action v)
newCache cache = do
    query <- S.newCache (\k -> wrapAction (cache k) "cache")
    return $ liftAction . query