packages feed

groot-0.0.1.0: src/Groot/Console.hs

{-# LANGUAGE FlexibleInstances    #-}
{-# LANGUAGE OverloadedStrings    #-}
{-# LANGUAGE TemplateHaskell      #-}
{-# LANGUAGE UndecidableInstances #-}

{-|
Module      : Groot.Console
Description : Code abstrations for Groot to operate with the terminal/console
Copyright   : A. Alonso Dominguez (c) 2017, http://github.com/alonsodomin
License     : Apache 2.0
Maintainer  : A. Alonso Dominguez <alonso.domin (λ) google>
Stability   : experimental
Portability : portable

This is a module providing some abstractions to interact with the user via the
terminal's console in a rich maner.
-}
module Groot.Console
     ( Severity (..)
     , MonadConsole (..)
     -- User Prompts
     , askUserYN
     , askUserToContinue
     -- User messages
     , putInfo
     , putWarn
     , putError
     , putSuccess
     , putDebug
     ) where

import           Control.Monad.IO.Class
import           Control.Monad.Trans
import           Control.Monad.Trans.Identity (IdentityT)
import           Control.Monad.Trans.Maybe    (MaybeT)
import           Data.Semigroup
import           Data.Text                    (Text)
import qualified Data.Text                    as T
import qualified Data.Text.IO                 as T
import           System.IO

import           Groot.Data.Text.Display
import           Groot.Data.Text.Styled       as ST

errorText, warnText, infoText, successText, debugText :: StyledText
errorText   = styled redStyle    "ERROR"
warnText    = styled yellowStyle "WARN"
infoText    = styled blueStyle   "INFO"
successText = styled greenStyle  "DONE"
debugText   = styled cyanStyle   "DEBUG"

-- |Severity level of the different output messages
data Severity = Error | Warn | Info | Success | Debug
  deriving (Eq, Show, Enum, Bounded, Ord)

-- |Definition of operations availbale when interacting with the user via the
-- terminal console
class Monad m => MonadConsole m where
  -- |Puts a message with a given severity in the console output
  putMessage :: Display a => Severity -> a -> m ()
  -- |Prompts the user for input
  askUser :: Display a => a -> m (Maybe Text)

instance (Monad m, MonadIO m) => MonadConsole m where
  putMessage sev txt = do
    display $ levelStr <> (ST.singleton ' ')
    displayLn txt
      where levelStr = case sev of
              Error   -> errorText
              Warn    -> warnText
              Info    -> infoText
              Success -> successText
              Debug   -> debugText

  askUser prompt = do
    answer <- liftIO $ do
      display prompt
      hFlush stdout
      T.getLine
    return $ if answer == ""
      then Nothing
      else Just answer

instance MonadConsole m => MonadConsole (IdentityT m) where
  putMessage sev txt = lift $ putMessage sev txt
  askUser = lift . askUser
instance MonadConsole m => MonadConsole (MaybeT m) where
  putMessage sev txt = lift $ putMessage sev txt
  askUser = lift . askUser

-- |Prompts the user for a 'yes' or 'no' answer
askUserYN :: MonadConsole m => Bool -> Text -> m Bool
askUserYN def msg = do
  answer <- askUser $ T.append msg defStr
  return $ handleAnswer answer
  where defStr = T.concat [ " [", (if def then "Yn" else "yN"), "] "]

        parseAnswer s =
          let s' = T.toLower s
          in (s' == "y") || (s' == "yes")

        handleAnswer Nothing  = def
        handleAnswer (Just s) = parseAnswer s

-- |Prompts the user for a 'yes' or 'no' answer. The given continuation
-- action will be performed in case she answers 'yes' or a synonym.
askUserToContinue :: MonadConsole m => Text -> m () -> m ()
askUserToContinue msg cont = do
  answer <- askUserYN False msg
  if answer then cont
  else return ()

-- |Outputs an 'INFO' message
putInfo :: (MonadConsole m, Display a) => a -> m ()
putInfo = putMessage Info

-- |Outputs an 'WARN' message
putWarn :: (MonadConsole m, Display a) => a -> m ()
putWarn = putMessage Warn

-- |Outputs an 'ERROR' message
putError :: (MonadConsole m, Display a) => a -> m ()
putError = putMessage Error

-- |Outputs an 'SUCCESS' message
putSuccess :: (MonadConsole m, Display a) => a -> m ()
putSuccess = putMessage Success

-- |Outputs an 'DEBUG' message
putDebug :: (MonadConsole m, Display a) => a -> m ()
putDebug = putMessage Debug