{-# LANGUAGE BangPatterns #-}
{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE ExistentialQuantification #-}
{-# LANGUAGE RankNTypes #-}
module Devops.Base (
PreOp (..)
, rawpreop
, Op (..)
, OpDescription (..)
, OpFunctions (..)
, DevOp , DevOpT
, runPreOp
, preopType
, OpUniqueId , preOpUniqueId
, OpCheck , CheckResult (..) , fromBool , noCheck
, OpAction , noAction
, buildOp
, buildPreOp
, noop
, neutralize
, TypedPreOp , castPreop
, devop
, Name
, track
, declare
, inject
, guardEnv
, runDevOp
, getDependenciesOnly
) where
import Control.Applicative (Alternative)
import Control.Monad (guard)
import Control.Monad.Identity (Identity, runIdentity)
import Control.Monad.Reader (ReaderT, runReaderT, ask, lift)
import Data.Hashable (Hashable (..), hash)
import Data.Proxy
import qualified Safe
import Data.Text (Text)
import Data.Tree (Forest)
import Data.Typeable (TypeRep, Typeable, cast, typeOf)
import GHC.Generics (Generic)
import DepTrack (DepTrackT)
import qualified DepTrack
type Name = Text
type DevOpT e m = ReaderT e (DepTrackT PreOp m)
type DevOp env = DevOpT env []
runDevOp :: env -> DevOp env a -> Maybe a
runDevOp env = Safe.headMay . DepTrack.value . flip runReaderT env
getDependenciesOnly :: env -> DevOp env a -> Forest PreOp
getDependenciesOnly env devop =
let
res = DepTrack.evalDepForest1 $ runReaderT devop env
in
case res of [] -> [] ; ((_, forest):_) -> forest
data PreOp = forall a. Typeable a => PreOp !a !(a -> Op)
runPreOp :: PreOp -> Op
runPreOp (PreOp x f) = f x
type TypedPreOp a = (a, a -> Op)
castPreop :: Typeable a => Proxy a -> PreOp -> Maybe (TypedPreOp a)
castPreop _ (PreOp x f) = (,) <$> cast x <*> cast f
preopType :: PreOp -> TypeRep
preopType (PreOp x _) = typeOf x
preOpUniqueId :: PreOp -> OpUniqueId
preOpUniqueId = opUniqueId . runPreOp
instance Show PreOp where
show = show . runPreOp
instance Eq PreOp where
preop1 == preop2 = opDescription (runPreOp preop1) == opDescription (runPreOp preop2)
type OpUniqueId = Int
data Op = Op { opDescription :: !OpDescription
, opFunctions :: !OpFunctions
, opUniqueId :: !OpUniqueId
}
instance Show Op where
show (Op desc _ _) = "Op (" ++ show desc ++ ", <...functions...>)"
data OpDescription = OpDescription { opName :: !Name
, opDocumentation :: !Text
} deriving (Show, Eq, Ord, Generic)
instance Hashable OpDescription
type Reason = String
data CheckResult =
Skipped
| Unknown
| Success
| Failure !Reason
deriving (Show, Read, Eq, Ord)
fromBool :: Bool -> CheckResult
fromBool (!True) = Success
fromBool (!False) = Failure "false (fromBool)"
type OpCheck = IO CheckResult
type OpAction = IO ()
data OpFunctions = OpFunctions { opCheck :: !OpCheck
, opTurnup :: !OpAction
, opTurndown :: !OpAction
, opReload :: !OpAction
}
noCheck :: OpCheck
noCheck = return Skipped
noAction :: OpAction
noAction = return ()
rawpreop :: Typeable a => a -> (a -> Op) -> PreOp
rawpreop v f = PreOp v f
buildOp :: Name -> Text -> OpCheck -> OpAction -> OpAction -> OpAction -> Op
buildOp a b f1 f2 f3 f4 =
let desc = (OpDescription a b) in
let oid = hash desc in
let functions = OpFunctions f1 f2 f3 f4 in
Op desc functions oid
buildPreOp :: Name -> Text -> OpCheck -> OpAction -> OpAction -> OpAction -> PreOp
buildPreOp a b f1 f2 f3 f4 = let val = buildOp a b f1 f2 f3 f4
in rawpreop val id
data NoOp = NoOp deriving (Show,Typeable)
noop :: Name -> Text -> PreOp
noop a b = rawpreop NoOp (const $ buildOp a b noCheck noAction noAction noAction)
neutralize :: Op -> PreOp
neutralize (Op desc _ oid) =
let val = Op desc (OpFunctions noCheck noAction noAction noAction) oid
in rawpreop val id
devop
:: (Typeable b, Monad m)
=> (a -> b)
-> (a -> Op)
-> DevOpT e m a
-> DevOpT e m b
devop f g a = do
env <- ask
let tracked = DepTrack.track g' (runReaderT a env)
fmap f $ lift tracked
where
g' v = let !o = g v in rawpreop (f v) (const o)
track :: (Monad m)
=> (a -> PreOp)
-> DevOpT e m a
-> DevOpT e m a
track f a = do
env <- ask
let tracked = DepTrack.track f (runReaderT a env)
lift tracked
declare :: (Monad m)
=> PreOp
-> DevOpT e m a
-> DevOpT e m a
declare obj = track (const obj)
inject :: (Monad m)
=> DevOpT e m a
-> DevOpT e m b
-> DevOpT e m (a, b)
inject m1 m2 = do
env <- ask
let tracked = DepTrack.inject (runReaderT m1 env) (runReaderT m2 env)
lift tracked
guardEnv :: (Monad m, Alternative m) => (e -> Bool) -> DevOpT e m ()
guardEnv f = ask >>= guard . f