packages feed

snowtify-0.1.0.2: src/Main.hs

{-# LANGUAGE OverloadedStrings #-}

module Main (main) where

import Control.Exception.Safe (MonadThrow, SomeException)
import Control.Monad (void)
import Control.Monad.IO.Class (MonadIO, liftIO)
import Control.Monad.Trans.Either (runEitherT)
import Data.Monoid ((<>))
import Data.Text (Text)
import Safe (headMay)
import Turtle (Shell, ExitCode, Pattern)
import qualified Data.Text as T
import qualified Turtle as TT


-- | Call `execute` with a haskell-stack command
main :: IO ()
main = runEitherT getBuildCommand >>= void . TT.sh . execute
  where
    -- Get a sub command of haskell-stack from CLI arguments.
    -- This must be "build" or "test", or empty.
    -- If CLI arguments is not given, return "build".
    -- If CLI arguments is neither "build" nor "test", throw an exception.
    getBuildCommand :: (MonadIO m, MonadThrow m) => m Text
    getBuildCommand = do
      args <- headMay <$> liftIO TT.arguments
      case args of
        Nothing      -> return "build" -- run 'build' by default
        Just "test"  -> return "test"
        Just "build" -> return "build"
        Just unknown -> fail . T.unpack $ "snowtify doesn't know '" <> unknown <> "' command x("


execute :: Either SomeException Text -> Shell ExitCode
execute (Left err)      = notifySend . T.pack $ show err
execute (Right command) = do
  (exitCode, out, err) <- TT.procStrictWithErr "stack" [command] ""
  let result = out <> err
  let notifyer = if exitCode == TT.ExitSuccess
                    then notifySucceeding
                    else notifyErrors
  notifyer command result


-- | Show succeeding with the notify-daemon
notifySucceeding :: Text -> Text -> Shell ExitCode
notifySucceeding command result = do
  notifySend $ "stack " <> command <> " is succeed"
  --TODO: Don't work
  notifySections ["warning"] result

-- | Show errors with the notify-daemon
notifyErrors :: Text -> Text -> Shell ExitCode
notifyErrors command result = do
  notifySend $ "stack " <> command <> " is finished with errors"
  notifySections ["error", "warning"] result


-- |
-- This is used for judge any section from a result of `stack (build|test)`.
--
-- This value is \1 of "^.*:\w+:\w+: (.+):$".
type SectionWord = Text


-- |
-- Send sections to the notify-daemon.
-- The sections are cut from a result of `stack (build|test)` with `[SectionWord]`.
notifySections :: [SectionWord] -> Text -> Shell ExitCode
notifySections = ((totalize <$>) .) . (mapM notifySend .) . sections
  where
    sections :: [SectionWord] -> Text -> [Text]
    sections sWords result =
      let blobs = TT.cut resultDelimiter result
      in [y | p <- map isItSection sWords, (x,y) <- map twice blobs, p x]

    -- A delimiter for cut result to sections
    resultDelimiter :: Pattern ()
    resultDelimiter = void $ do
      TT.newline
      TT.spaces
      TT.newline

    isItSection :: Text -> Text -> Bool
    isItSection it x =
      let it' = it <> ":" -- 'it' is \1 of "^.*:\w+:\w+: (.+):$"
      in case headMay $ T.lines x of
        Nothing        -> False
        Just firstLine -> any (== it') $ T.words firstLine


-- |
-- If all `ExitCode` is `ExitSuccess`, return `ExitSuccess`.
-- Otherwise, return `ExitFailure 1`.
totalize :: [ExitCode] -> ExitCode
totalize = exitCode . extremize . sum . map weaken
  where
    weaken :: ExitCode -> Int
    weaken TT.ExitSuccess     = 0
    weaken (TT.ExitFailure n) = n

    extremize :: Int -> Int
    extremize 0 = 0
    extremize _ = 1


-- | Generalized a value constructor of `ExitCode`
exitCode :: Int -> ExitCode
exitCode 0 = TT.ExitSuccess
exitCode n = TT.ExitFailure n


-- | Send a message to the notify-daemon
notifySend :: Text -> Shell ExitCode
notifySend msg = TT.proc "notify-send" ["snowtify", msg] ""


twice :: a -> (a, a)
twice x = (x, x)