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

This module introduces helpful pure codes to customise the terminal output view.
-}

module Colourista.Pure
    ( formatWith
      -- * Colour
    , red
    , green
    , blue
    , yellow
    , black
    , white
    , magenta
    , cyan

      -- * Background
    , redBg
    , greenBg
    , blueBg
    , yellowBg
    , blackBg
    , whiteBg
    , magentaBg
    , cyanBg

      -- * Emphasis
    , bold
    , italic
    , underline
    , doubleUnderline
    , noUnderline
    , indent

      -- * Reset
    , reset
    ) where

import Data.ByteString (ByteString)
import Data.List.NonEmpty (NonEmpty (..))
import Data.Semigroup (Semigroup (..))
import Data.String (IsString (..))
import Data.Text (Text)
import System.Console.ANSI (Color (..), ColorIntensity (Vivid), ConsoleIntensity (BoldIntensity),
                            ConsoleLayer (Background, Foreground), SGR (..), Underlining (..),
                            setSGRCode)


{- | General purpose function to format strings with multiple
options. If this function takes empty list as an argument, no
formatting is applied.

Some typical usages include but not limited to:

1. Green text: @'formatWith' ['green'] myString@
2. Bold red text: @'formatWith' ['bold', 'red'] myString@
3. Blue text on white background: @'formatWith' ['blue', 'whiteBg'] myString@
4. Italicized yellow on cyan background: @'formatWith' ['italic', 'yellow', 'cyanBg'] myString@

![Colored examples](https://user-images.githubusercontent.com/4276606/74608609-8acced80-50da-11ea-9a32-e64eba6935c1.png)

__⚠ Caution:__ Double underlining 'doubleUnderline' is not widely supported.
It is also not natively supported on Windows 10.

-}
formatWith
    :: (IsString str, Semigroup str)
    => [str]
    -> str
    -> str
formatWith :: forall str. (IsString str, Semigroup str) => [str] -> str -> str
formatWith [str]
formatting str
str = case [str]
formatting of
    []   -> str
str
    str
x:[str]
xs -> forall a. Semigroup a => NonEmpty a -> a
sconcat (str
x forall a. a -> [a] -> NonEmpty a
:| [str]
xs) forall a. Semigroup a => a -> a -> a
<> str
str forall a. Semigroup a => a -> a -> a
<> forall str. IsString str => str
reset
{-# SPECIALIZE formatWith :: [String]     -> String     -> String     #-}
{-# SPECIALIZE formatWith :: [Text]       -> Text       -> Text       #-}
{-# SPECIALIZE formatWith :: [ByteString] -> ByteString -> ByteString #-}

----------------------------------------------------------------------------
-- Colours
----------------------------------------------------------------------------

-- | Code to apply 'Red' colouring for the terminal output.
red :: IsString str => str
red :: forall str. IsString str => str
red = forall a. IsString a => String -> a
fromString forall a b. (a -> b) -> a -> b
$ [SGR] -> String
setSGRCode [ConsoleLayer -> ColorIntensity -> Color -> SGR
SetColor ConsoleLayer
Foreground ColorIntensity
Vivid Color
Red]
{-# SPECIALIZE red :: String     #-}
{-# SPECIALIZE red :: Text       #-}
{-# SPECIALIZE red :: ByteString #-}

-- | Code to apply 'Green' colouring for the terminal output.
green :: IsString str => str
green :: forall str. IsString str => str
green = forall a. IsString a => String -> a
fromString forall a b. (a -> b) -> a -> b
$ [SGR] -> String
setSGRCode [ConsoleLayer -> ColorIntensity -> Color -> SGR
SetColor ConsoleLayer
Foreground ColorIntensity
Vivid Color
Green]
{-# SPECIALIZE green :: String     #-}
{-# SPECIALIZE green :: Text       #-}
{-# SPECIALIZE green :: ByteString #-}

-- | Code to apply 'Blue' colouring for the terminal output.
blue :: IsString str => str
blue :: forall str. IsString str => str
blue = forall a. IsString a => String -> a
fromString forall a b. (a -> b) -> a -> b
$ [SGR] -> String
setSGRCode [ConsoleLayer -> ColorIntensity -> Color -> SGR
SetColor ConsoleLayer
Foreground ColorIntensity
Vivid Color
Blue]
{-# SPECIALIZE blue :: String     #-}
{-# SPECIALIZE blue :: Text       #-}
{-# SPECIALIZE blue :: ByteString #-}

-- | Code to apply 'Yellow' colouring for the terminal output.
yellow :: IsString str => str
yellow :: forall str. IsString str => str
yellow = forall a. IsString a => String -> a
fromString forall a b. (a -> b) -> a -> b
$ [SGR] -> String
setSGRCode [ConsoleLayer -> ColorIntensity -> Color -> SGR
SetColor ConsoleLayer
Foreground ColorIntensity
Vivid Color
Yellow]
{-# SPECIALIZE yellow :: String     #-}
{-# SPECIALIZE yellow :: Text       #-}
{-# SPECIALIZE yellow :: ByteString #-}

-- | Code to apply 'Black' colouring for the terminal output.
black :: IsString str => str
black :: forall str. IsString str => str
black = forall a. IsString a => String -> a
fromString forall a b. (a -> b) -> a -> b
$ [SGR] -> String
setSGRCode [ConsoleLayer -> ColorIntensity -> Color -> SGR
SetColor ConsoleLayer
Foreground ColorIntensity
Vivid Color
Black]
{-# SPECIALIZE black :: String     #-}
{-# SPECIALIZE black :: Text       #-}
{-# SPECIALIZE black :: ByteString #-}

-- | Code to apply 'White' colouring for the terminal output.
white :: IsString str => str
white :: forall str. IsString str => str
white = forall a. IsString a => String -> a
fromString forall a b. (a -> b) -> a -> b
$ [SGR] -> String
setSGRCode [ConsoleLayer -> ColorIntensity -> Color -> SGR
SetColor ConsoleLayer
Foreground ColorIntensity
Vivid Color
White]
{-# SPECIALIZE white :: String     #-}
{-# SPECIALIZE white :: Text       #-}
{-# SPECIALIZE white :: ByteString #-}

-- | Code to apply 'Magenta' colouring for the terminal output.
magenta :: IsString str => str
magenta :: forall str. IsString str => str
magenta = forall a. IsString a => String -> a
fromString forall a b. (a -> b) -> a -> b
$ [SGR] -> String
setSGRCode [ConsoleLayer -> ColorIntensity -> Color -> SGR
SetColor ConsoleLayer
Foreground ColorIntensity
Vivid Color
Magenta]
{-# SPECIALIZE magenta :: String     #-}
{-# SPECIALIZE magenta :: Text       #-}
{-# SPECIALIZE magenta :: ByteString #-}

-- | Code to apply 'Cyan' colouring for the terminal output.
cyan :: IsString str => str
cyan :: forall str. IsString str => str
cyan = forall a. IsString a => String -> a
fromString forall a b. (a -> b) -> a -> b
$ [SGR] -> String
setSGRCode [ConsoleLayer -> ColorIntensity -> Color -> SGR
SetColor ConsoleLayer
Foreground ColorIntensity
Vivid Color
Cyan]
{-# SPECIALIZE cyan :: String     #-}
{-# SPECIALIZE cyan :: Text       #-}
{-# SPECIALIZE cyan :: ByteString #-}

----------------------------------------------------------------------------
-- Background
----------------------------------------------------------------------------

-- | Code to apply 'Red' background colouring for the terminal output.
redBg :: IsString str => str
redBg :: forall str. IsString str => str
redBg = forall a. IsString a => String -> a
fromString forall a b. (a -> b) -> a -> b
$ [SGR] -> String
setSGRCode [ConsoleLayer -> ColorIntensity -> Color -> SGR
SetColor ConsoleLayer
Background ColorIntensity
Vivid Color
Red]
{-# SPECIALIZE redBg :: String     #-}
{-# SPECIALIZE redBg :: Text       #-}
{-# SPECIALIZE redBg :: ByteString #-}

-- | Code to apply 'Green' background colouring for the terminal output.
greenBg :: IsString str => str
greenBg :: forall str. IsString str => str
greenBg = forall a. IsString a => String -> a
fromString forall a b. (a -> b) -> a -> b
$ [SGR] -> String
setSGRCode [ConsoleLayer -> ColorIntensity -> Color -> SGR
SetColor ConsoleLayer
Background ColorIntensity
Vivid Color
Green]
{-# SPECIALIZE greenBg :: String     #-}
{-# SPECIALIZE greenBg :: Text       #-}
{-# SPECIALIZE greenBg :: ByteString #-}

-- | Code to apply 'Blue' background colouring for the terminal output.
blueBg :: IsString str => str
blueBg :: forall str. IsString str => str
blueBg = forall a. IsString a => String -> a
fromString forall a b. (a -> b) -> a -> b
$ [SGR] -> String
setSGRCode [ConsoleLayer -> ColorIntensity -> Color -> SGR
SetColor ConsoleLayer
Background ColorIntensity
Vivid Color
Blue]
{-# SPECIALIZE blueBg :: String     #-}
{-# SPECIALIZE blueBg :: Text       #-}
{-# SPECIALIZE blueBg :: ByteString #-}

-- | Code to apply 'Yellow' background colouring for the terminal output.
yellowBg :: IsString str => str
yellowBg :: forall str. IsString str => str
yellowBg = forall a. IsString a => String -> a
fromString forall a b. (a -> b) -> a -> b
$ [SGR] -> String
setSGRCode [ConsoleLayer -> ColorIntensity -> Color -> SGR
SetColor ConsoleLayer
Background ColorIntensity
Vivid Color
Yellow]
{-# SPECIALIZE yellowBg :: String     #-}
{-# SPECIALIZE yellowBg :: Text       #-}
{-# SPECIALIZE yellowBg :: ByteString #-}

-- | Code to apply 'Black' background colouring for the terminal output.
blackBg :: IsString str => str
blackBg :: forall str. IsString str => str
blackBg = forall a. IsString a => String -> a
fromString forall a b. (a -> b) -> a -> b
$ [SGR] -> String
setSGRCode [ConsoleLayer -> ColorIntensity -> Color -> SGR
SetColor ConsoleLayer
Background ColorIntensity
Vivid Color
Black]
{-# SPECIALIZE blackBg :: String     #-}
{-# SPECIALIZE blackBg :: Text       #-}
{-# SPECIALIZE blackBg :: ByteString #-}

-- | Code to apply 'White' background colouring for the terminal output.
whiteBg :: IsString str => str
whiteBg :: forall str. IsString str => str
whiteBg = forall a. IsString a => String -> a
fromString forall a b. (a -> b) -> a -> b
$ [SGR] -> String
setSGRCode [ConsoleLayer -> ColorIntensity -> Color -> SGR
SetColor ConsoleLayer
Background ColorIntensity
Vivid Color
White]
{-# SPECIALIZE whiteBg :: String     #-}
{-# SPECIALIZE whiteBg :: Text       #-}
{-# SPECIALIZE whiteBg :: ByteString #-}

-- | Code to apply 'Magenta' background colouring for the terminal output.
magentaBg :: IsString str => str
magentaBg :: forall str. IsString str => str
magentaBg = forall a. IsString a => String -> a
fromString forall a b. (a -> b) -> a -> b
$ [SGR] -> String
setSGRCode [ConsoleLayer -> ColorIntensity -> Color -> SGR
SetColor ConsoleLayer
Background ColorIntensity
Vivid Color
Magenta]
{-# SPECIALIZE magentaBg :: String     #-}
{-# SPECIALIZE magentaBg :: Text       #-}
{-# SPECIALIZE magentaBg :: ByteString #-}

-- | Code to apply 'Cyan' background colouring for the terminal output.
cyanBg :: IsString str => str
cyanBg :: forall str. IsString str => str
cyanBg = forall a. IsString a => String -> a
fromString forall a b. (a -> b) -> a -> b
$ [SGR] -> String
setSGRCode [ConsoleLayer -> ColorIntensity -> Color -> SGR
SetColor ConsoleLayer
Background ColorIntensity
Vivid Color
Cyan]
{-# SPECIALIZE cyanBg :: String     #-}
{-# SPECIALIZE cyanBg :: Text       #-}
{-# SPECIALIZE cyanBg :: ByteString #-}

----------------------------------------------------------------------------
-- Emphasis
----------------------------------------------------------------------------

-- | Code to apply __bold__ emphasis for the terminal output.
bold :: IsString str => str
bold :: forall str. IsString str => str
bold = forall a. IsString a => String -> a
fromString forall a b. (a -> b) -> a -> b
$ [SGR] -> String
setSGRCode [ConsoleIntensity -> SGR
SetConsoleIntensity ConsoleIntensity
BoldIntensity]
{-# SPECIALIZE bold :: String     #-}
{-# SPECIALIZE bold :: Text       #-}
{-# SPECIALIZE bold :: ByteString #-}

-- | Code to apply /italic/ emphasis for the terminal output.
italic :: IsString str => str
italic :: forall str. IsString str => str
italic = forall a. IsString a => String -> a
fromString forall a b. (a -> b) -> a -> b
$ [SGR] -> String
setSGRCode [Bool -> SGR
SetItalicized Bool
True]
{-# SPECIALIZE italic :: String     #-}
{-# SPECIALIZE italic :: Text       #-}
{-# SPECIALIZE italic :: ByteString #-}

-- | Code to apply __underline__ emphasis for the terminal output.
underline :: IsString str => str
underline :: forall str. IsString str => str
underline = forall a. IsString a => String -> a
fromString forall a b. (a -> b) -> a -> b
$ [SGR] -> String
setSGRCode [Underlining -> SGR
SetUnderlining Underlining
SingleUnderline]
{-# SPECIALIZE underline :: String     #-}
{-# SPECIALIZE underline :: Text       #-}
{-# SPECIALIZE underline :: ByteString #-}

{- | Code to apply __double underline__ emphasis for the terminal output.

__⚠ Caution:__ This is not widely supported. It is not natively supported on
Windows 10
-}
doubleUnderline :: IsString str => str
doubleUnderline :: forall str. IsString str => str
doubleUnderline = forall a. IsString a => String -> a
fromString forall a b. (a -> b) -> a -> b
$ [SGR] -> String
setSGRCode [Underlining -> SGR
SetUnderlining Underlining
DoubleUnderline]
{-# SPECIALIZE doubleUnderline :: String     #-}
{-# SPECIALIZE doubleUnderline :: Text       #-}
{-# SPECIALIZE doubleUnderline :: ByteString #-}

-- | Code to apply __no underline__ emphasis for the terminal output.
noUnderline :: IsString str => str
noUnderline :: forall str. IsString str => str
noUnderline = forall a. IsString a => String -> a
fromString forall a b. (a -> b) -> a -> b
$ [SGR] -> String
setSGRCode [Underlining -> SGR
SetUnderlining Underlining
NoUnderline]
{-# SPECIALIZE noUnderline :: String     #-}
{-# SPECIALIZE noUnderline :: Text       #-}
{-# SPECIALIZE noUnderline :: ByteString #-}

-- | Code to indent the terminal output by the given amount of spaces.
indent :: (IsString str, Semigroup str) => Int -> str
indent :: forall str. (IsString str, Semigroup str) => Int -> str
indent Int
n
    | Int
n forall a. Ord a => a -> a -> Bool
<= Int
0 = str
""
    | Bool
otherwise = forall a b. (Semigroup a, Integral b) => b -> a -> a
stimes Int
n str
" "
{-# SPECIALIZE indent :: Int -> String     #-}
{-# SPECIALIZE indent :: Int -> Text       #-}
{-# SPECIALIZE indent :: Int -> ByteString #-}

-- | Code to reset all previous code applied for the terminal output.
reset :: IsString str => str
reset :: forall str. IsString str => str
reset = forall a. IsString a => String -> a
fromString forall a b. (a -> b) -> a -> b
$ [SGR] -> String
setSGRCode [SGR
Reset]
{-# SPECIALIZE reset :: String     #-}
{-# SPECIALIZE reset :: Text       #-}
{-# SPECIALIZE reset :: ByteString #-}