packages feed

sensei-0.8.0: src/Trigger.hs

{-# LANGUAGE CPP #-}
module Trigger (
  Hook
, HookResult(..)
, Hooks(..)
, defaultHooks
, Result(..)
, trigger
, triggerAll
#ifdef TEST
, reloadedSuccessfully
, removeProgress
#endif
) where

import           Imports

#if MIN_VERSION_mtl(2,3,1)
import           Control.Monad.Trans.Writer.CPS (runWriterT)
import           Control.Monad.Writer.CPS hiding (pass)
#else
import           Control.Monad.Writer.Strict hiding (pass)
#endif

import           Control.Monad.Except

import           Util
import           Config (Hook, HookResult(..))
import           Session (Session, isFailure, isSuccess, hspecPreviousSummary, resetSummary)
import qualified Session

data Hooks = Hooks {
  beforeReload :: Hook
, afterReload :: Hook
}

defaultHooks :: Hooks
defaultHooks = Hooks {
  beforeReload = return HookSuccess
, afterReload = return HookSuccess
}

data Result = HookFailed | Failure | Success
  deriving (Eq, Show)

triggerAll :: Session -> Hooks -> IO (Result, String)
triggerAll session hooks = do
  resetSummary session
  trigger session hooks

reloadedSuccessfully :: String -> Bool
reloadedSuccessfully = any success . lines
  where
    success :: String -> Bool
    success x = case stripPrefix "Ok, " x of
      Just "one module loaded." -> True
      Just "1 module loaded." -> True
      Just xs | [_number, "modules", "loaded."] <- words xs -> True
      Just xs -> "modules loaded: " `isPrefixOf` xs
      Nothing -> False

removeProgress :: String -> String
removeProgress xs = case break (== '\r') xs of
  (_, "") -> xs
  (ys, _ : zs) -> dropLastLine ys ++ removeProgress zs
  where
    dropLastLine :: String -> String
    dropLastLine = reverse . dropWhile (/= '\n') . reverse

type Trigger = ExceptT Result (WriterT String IO)

trigger :: Session -> Hooks -> IO (Result, String)
trigger session hooks = runWriterT (runExceptT go) >>= \ case
  (Left result, output) -> return (result, output)
  (Right (), output) -> return (Success, output)
  where
    go :: Trigger ()
    go = do
      runHook hooks.beforeReload
      output <- Session.reload session
      tell output
      case reloadedSuccessfully output of
        False -> do
          echo $ withColor Red "RELOADING FAILED" <> "\n"
          abort
        True -> do
          echo $ withColor Green "RELOADING SUCCEEDED" <> "\n"

      runHook hooks.afterReload
      getRunSpec >>= \ case
        Just hspec -> rerunAllOnSuccess hspec
        Nothing -> pass

    abort :: Trigger a
    abort = throwError Failure

    rerunAllOnSuccess :: Trigger () -> Trigger ()
    rerunAllOnSuccess hspec = do
      failedPreviously <- isFailure <$> hspecPreviousSummary session
      hspec
      when failedPreviously hspec

    getRunSpec :: MonadIO m => m (Maybe (Trigger ()))
    getRunSpec = liftIO $ fmap runSpec <$> Session.getRunSpec session

    runSpec :: IO String -> Trigger ()
    runSpec hspec = do
      liftIO hspec >>= tell . removeProgress
      result <- hspecPreviousSummary session
      unless (isSuccess result) abort

    runHook :: Hook -> Trigger ()
    runHook hook = liftIO hook >>= \ case
      HookSuccess -> pass
      HookFailure message -> echo message >> throwError HookFailed

    echo :: String -> Trigger ()
    echo message = do
      tell message
      liftIO $ Session.echo session message