packages feed

life-sync-1.1.1.0: src/Life/Message.hs

{-# LANGUAGE MultiWayIf   #-}
{-# LANGUAGE ViewPatterns #-}

{- |
Copyright:  (c) 2017-2020 Kowainik
SPDX-License-Identifier: MPL-2.0
Maintainer: Kowainik <xrom.xkov@gmail.com>

Contains functions for colorful printing, prompt and other terminal related
messages.
-}

module Life.Message
    ( prompt
    , promptNonEmpty
    , abortCmd

      -- * Questions
    , choose
    , chooseYesNo
    ) where

import Colourista (blue, bold, errorMessage, formatWith, warningMessage)
import System.IO (hFlush)

import qualified Data.Text as T


----------------------------------------------------------------------------
-- Ansi-terminal
----------------------------------------------------------------------------

-- Explicit flush ensures prompt messages are in the correct order on all systems.
putStrFlush :: Text -> IO ()
putStrFlush msg = do
    putText msg
    hFlush stdout

{- | Read 'Text' from standard input after arrow prompt.
-}
prompt :: IO Text
prompt = do
    putStrFlush $ formatWith [blue] "  ⟳   "
    getLine

promptNonEmpty :: IO Text
promptNonEmpty = do
    res <- T.strip <$> prompt
    if T.null res
        then warningMessage "The answer shouldn't be empty" >> promptNonEmpty
        else pure res

boldDefault :: Text -> Text
boldDefault message = formatWith [bold] $ " [" <> message <> "]"

-- | Print message and abort current process.
abortCmd :: Text -> Text -> IO ()
abortCmd cmd msg = do
    warningMessage msg
    errorMessage $ "Aborting 'life " <> cmd <> "' command."
    exitFailure

----------------------------------------------------------------------------
-- Questions
----------------------------------------------------------------------------

printQuestion :: Text -> [Text] -> IO ()
printQuestion question [] = putTextLn question
printQuestion question (def:rest) = do
    let restSlash = T.intercalate "/" rest
    putStrFlush $ question <> boldDefault def
    putTextLn $ "/" <> restSlash

choose :: Text -> NonEmpty Text -> IO Text
choose question choices = do
    printQuestion question $ toList choices
    answer <- prompt
    if | T.null answer -> pure (head choices)
       | T.toLower answer `elem` choices -> pure answer
       | otherwise -> do
           errorMessage "This wasn't a valid choice."
           choose question choices

data Answer
    = Y
    | N

yesOrNo :: Text -> Maybe Answer
yesOrNo (T.toLower -> answer )
    | T.null answer = Just Y
    | answer `elem` ["yes", "y", "ys"] = Just Y
    | answer `elem` ["no", "n"]  = Just N
    | otherwise = Nothing

chooseYesNo :: Text -> IO Bool
chooseYesNo q = do
    printQuestion q ["y", "n"]
    answer <- yesOrNo <$> prompt
    case answer of
        Nothing -> do
           errorMessage "This wasn't a valid choice."
           chooseYesNo q
        Just Y -> pure True
        Just N -> pure False