Skip to content

Commit d2bcd11

Browse files
authored
[#26] Ability to disable and enable colouring (#38)
* [#26] Ability to disable and enable colouring Resolves #26 * Fix build on old GHCs * [#26] Move ColourMode to separate module, improve docs Resolves #26
1 parent 69af0b3 commit d2bcd11

File tree

9 files changed

+310
-152
lines changed

9 files changed

+310
-152
lines changed

CHANGELOG.md

+13
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,19 @@
33
`colourista` uses [PVP Versioning][1].
44
The changelog is available [on GitHub][2].
55

6+
## 0.2.0.0
7+
8+
* [#26](https://github.com/kowainik/colourista/issues/26):
9+
Support enabling and disable of colouring with implicit parameters.
10+
11+
__Migration guide:__ You can continue using `colourista` without
12+
changing anything and you still get colourful formatting.
13+
14+
However, if you want to enable or disable colouring, you need to add
15+
the `HasColourMode =>` constraint to all functions that format data
16+
or call such formatting functions, and set the value of the
17+
`?colourMode` variable in the beginning of your application.
18+
619
## 0.1.0.0 — May 2, 2020 🌈
720

821
* [#22](https://github.com/kowainik/colourista/issues/22):

README.md

+18-6
Original file line numberDiff line numberDiff line change
@@ -14,11 +14,16 @@
1414
>
1515
> — Coco Chanel
1616
17-
`colourista` is the library that provides a convenient interface for printing
18-
colourful messages to the terminal. It is based on
19-
[`ansi-terminal`](https://hackage.haskell.org/package/ansi-terminal), however,
20-
in contradistinction to this Haskell library, `colourista` is a high-level
21-
wrapper focused on easily achieved output modification without low-level setup.
17+
`colourista` is the library that provides a simple and convenient
18+
interface for printing colourful messages to the
19+
terminal. Additionally, `colourista` allows to easily control enabling
20+
and disabling of colours.
21+
22+
The library is based on
23+
[`ansi-terminal`](https://hackage.haskell.org/package/ansi-terminal),
24+
however, in contradistinction to this Haskell library, `colourista` is
25+
a high-level wrapper focused on easily achieved output modification
26+
without low-level setup.
2227

2328
## Interface
2429

@@ -32,10 +37,17 @@ The two main functions that `colourista` provides are:
3237
The library also provides a set of different pure and impure helpers for the
3338
colouring and emphasis.
3439

35-
## Example
40+
## Examples
41+
42+
Simple output example:
3643

3744
![output](https://user-images.githubusercontent.com/8126674/74609327-0a5dbb00-50e1-11ea-8c4b-2db4ab5b42a2.png)
3845

46+
Example of disabling colouring. The colour mode controlling is based on the
47+
[Implicit Parameters](https://downloads.haskell.org/ghc/latest/docs/html/users_guide/glasgow_exts.html#implicit-parameters) GHC feature.
48+
49+
![Colour mode](https://user-images.githubusercontent.com/4276606/90915207-0c2d7180-e3d7-11ea-934c-ec840118ed73.png)
50+
3951
## How to use
4052

4153
`colourista` is compatible with the latest GHC compiler versions starting from `8.2.2`.

colourista.cabal

+14-3
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,17 @@
11
cabal-version: 2.4
22
name: colourista
3-
version: 0.1.0.0
4-
synopsis: Convenient interface for printing colourful messages
5-
description: Convenient interface for printing colourful messages based on the @ansi-terminal@ library.
3+
version: 0.2.0.0
4+
synopsis: Simple and convenient interface for colourful outputting
5+
description:
6+
Convenient interface for printing colourful messages based on the @ansi-terminal@ library.
7+
Supports enabling and disabling of colouring based on
8+
the [Implicit Parameters](https://downloads.haskell.org/ghc/latest/docs/html/users_guide/glasgow_exts.html#implicit-parameters)
9+
GHC feature.
10+
.
11+
Usage and output example:
12+
.
13+
* ![Example](https://user-images.githubusercontent.com/4276606/90915207-0c2d7180-e3d7-11ea-934c-ec840118ed73.png)
14+
615
homepage: https://github.com/kowainik/colourista
716
bug-reports: https://github.com/kowainik/colourista/issues
817
license: MPL-2.0
@@ -28,12 +37,14 @@ library
2837
hs-source-dirs: src
2938
exposed-modules: Colourista
3039
Colourista.IO
40+
Colourista.Mode
3141
Colourista.Pure
3242
Colourista.Short
3343

3444
build-depends: base >= 4.10.1.0 && < 4.15
3545
, ansi-terminal ^>= 0.10
3646
, bytestring ^>= 0.10
47+
, ghc-prim >= 0.5 && < 0.7
3748
, text ^>= 1.2.3.0
3849

3950
ghc-options: -Wall

src/Colourista.hs

+8
Original file line numberDiff line numberDiff line change
@@ -14,9 +14,12 @@ module Colourista
1414
-- $pure
1515
, module Colourista.IO
1616
-- $io
17+
, module Colourista.Mode
18+
-- $mode
1719
) where
1820

1921
import Colourista.IO
22+
import Colourista.Mode
2023
import Colourista.Pure
2124

2225

@@ -29,3 +32,8 @@ customisation: colouring and emphasis.
2932
The set of functions that work in 'IO' to output formatted messages
3033
directly to terminal.
3134
-}
35+
36+
{- $mode
37+
'ColourMode' data type that allows disabling and enabling colouring
38+
based on the implicit @?colourMode@ parameter in scope.
39+
-}

src/Colourista/IO.hs

+18-16
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,8 @@ import Data.Semigroup (Semigroup (..))
3737
#endif
3838
import Data.Text (Text)
3939

40+
import Colourista.Mode (HasColourMode)
41+
4042
import qualified Data.Text.IO as TIO
4143

4244
import qualified Colourista.Pure as Colourista
@@ -46,42 +48,42 @@ import qualified Colourista.Pure as Colourista
4648
----------------------------------------------------------------------------
4749

4850
-- | Print 'Text' coloured in 'Colourista.red'.
49-
redMessage :: Text -> IO ()
51+
redMessage :: HasColourMode => Text -> IO ()
5052
redMessage = formattedMessage [Colourista.red]
5153
{-# INLINE redMessage #-}
5254

5355
-- | Print 'Text' coloured in 'Colourista.green'.
54-
greenMessage :: Text -> IO ()
56+
greenMessage :: HasColourMode => Text -> IO ()
5557
greenMessage = formattedMessage [Colourista.green]
5658
{-# INLINE greenMessage #-}
5759

5860
-- | Print 'Text' coloured in 'Colourista.blue'.
59-
blueMessage :: Text -> IO ()
61+
blueMessage :: HasColourMode => Text -> IO ()
6062
blueMessage = formattedMessage [Colourista.blue]
6163
{-# INLINE blueMessage #-}
6264

6365
-- | Print 'Text' coloured in 'Colourista.yellow'.
64-
yellowMessage :: Text -> IO ()
66+
yellowMessage :: HasColourMode => Text -> IO ()
6567
yellowMessage = formattedMessage [Colourista.yellow]
6668
{-# INLINE yellowMessage #-}
6769

6870
-- | Print 'Text' coloured in 'Colourista.black'.
69-
blackMessage :: Text -> IO ()
71+
blackMessage :: HasColourMode => Text -> IO ()
7072
blackMessage = formattedMessage [Colourista.black]
7173
{-# INLINE blackMessage #-}
7274

7375
-- | Print 'Text' coloured in 'Colourista.white'.
74-
whiteMessage :: Text -> IO ()
76+
whiteMessage :: HasColourMode => Text -> IO ()
7577
whiteMessage = formattedMessage [Colourista.white]
7678
{-# INLINE whiteMessage #-}
7779

7880
-- | Print 'Text' coloured in 'Colourista.magenta'.
79-
magentaMessage :: Text -> IO ()
81+
magentaMessage :: HasColourMode => Text -> IO ()
8082
magentaMessage = formattedMessage [Colourista.magenta]
8183
{-# INLINE magentaMessage #-}
8284

8385
-- | Print 'Text' coloured in 'Colourista.cyan'.
84-
cyanMessage :: Text -> IO ()
86+
cyanMessage :: HasColourMode => Text -> IO ()
8587
cyanMessage = formattedMessage [Colourista.cyan]
8688
{-# INLINE cyanMessage #-}
8789

@@ -93,39 +95,39 @@ cyanMessage = formattedMessage [Colourista.cyan]
9395
9496
<<https://user-images.githubusercontent.com/4276606/80867598-dbd99000-8c8c-11ea-9fac-81a1a606d8d8.png Success message>>
9597
-}
96-
successMessage :: Text -> IO ()
98+
successMessage :: HasColourMode => Text -> IO ()
9799
successMessage t = greenMessage $ "" <> t
98100
{-# INLINE successMessage #-}
99101

100102
{- | Similar to 'blueMessage', but add unicode indicator.
101103
102104
<<https://user-images.githubusercontent.com/4276606/80867597-db40f980-8c8c-11ea-9775-e8a3c4a7aaa2.png Information message>>
103105
-}
104-
infoMessage :: Text -> IO ()
106+
infoMessage :: HasColourMode => Text -> IO ()
105107
infoMessage t = blueMessage $ "" <> t
106108
{-# INLINE infoMessage #-}
107109

108110
{- | Similar to 'cyanMessage', but add unicode indicator.
109111
110112
<<https://user-images.githubusercontent.com/4276606/80867596-db40f980-8c8c-11ea-8131-9c7cba32a4fd.png Skip message>>
111113
-}
112-
skipMessage :: Text -> IO ()
114+
skipMessage :: HasColourMode => Text -> IO ()
113115
skipMessage t = cyanMessage $ "" <> t
114116
{-# INLINE skipMessage #-}
115117

116118
{- | Similar to 'yellowMessage', but add unicode indicator.
117119
118120
<<https://user-images.githubusercontent.com/4276606/80867594-daa86300-8c8c-11ea-9c6a-a42b634a1e4b.png Warning message>>
119121
-}
120-
warningMessage :: Text -> IO ()
122+
warningMessage :: HasColourMode => Text -> IO ()
121123
warningMessage t = yellowMessage $ "" <> t
122124
{-# INLINE warningMessage #-}
123125

124126
{- | Similar to 'redMessage', but add unicode indicator.
125127
126128
<<https://user-images.githubusercontent.com/4276606/80867592-da0fcc80-8c8c-11ea-90e0-42aae8770c18.png Error message>>
127129
-}
128-
errorMessage :: Text -> IO ()
130+
errorMessage :: HasColourMode => Text -> IO ()
129131
errorMessage t = redMessage $ " \128721 " <> t
130132
{-# INLINE errorMessage #-}
131133

@@ -134,12 +136,12 @@ errorMessage t = redMessage $ " \128721 " <> t
134136
----------------------------------------------------------------------------
135137

136138
-- | Print 'Text' emphasized with 'Colourista.bold'.
137-
boldMessage :: Text -> IO ()
139+
boldMessage :: HasColourMode => Text -> IO ()
138140
boldMessage = formattedMessage [Colourista.bold]
139141
{-# INLINE boldMessage #-}
140142

141143
-- | Print 'Text' emphasized with 'Colourista.italic'.
142-
italicMessage :: Text -> IO ()
144+
italicMessage :: HasColourMode => Text -> IO ()
143145
italicMessage = formattedMessage [Colourista.italic]
144146
{-# INLINE italicMessage #-}
145147

@@ -153,6 +155,6 @@ list, no formatting is applied.
153155
154156
![formattedMessage-example](https://user-images.githubusercontent.com/4276606/74608898-e6987600-50dc-11ea-9a93-bda701fd3c43.png)
155157
-}
156-
formattedMessage :: [Text] -> Text -> IO ()
158+
formattedMessage :: HasColourMode => [Text] -> Text -> IO ()
157159
formattedMessage formatting = TIO.putStrLn . Colourista.formatWith formatting
158160
{-# INLINE formattedMessage #-}

src/Colourista/Mode.hs

+108
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
{-# OPTIONS_GHC -fno-warn-orphans #-}
2+
3+
{-# LANGUAGE DataKinds #-}
4+
{-# LANGUAGE FlexibleInstances #-}
5+
{-# LANGUAGE ImplicitParams #-}
6+
{-# LANGUAGE MultiParamTypeClasses #-}
7+
8+
{- |
9+
Copyright: (c) 2020 Kowainik
10+
SPDX-License-Identifier: MPL-2.0
11+
Maintainer: Kowainik <[email protected]>
12+
13+
The 'ColourMode' data type that allows disabling and enabling of
14+
colouring. Implemented using the [Implicit Parameters](https://downloads.haskell.org/ghc/latest/docs/html/users_guide/glasgow_exts.html#implicit-parameters)
15+
GHC feature.
16+
17+
By default, all formatting and printing functions in @colourista@
18+
print with colour. However, you control this behaviour by adding the
19+
@HasColourMode@ constraint to your functions and setting the value of
20+
the implicit @?colourMode@ variable.
21+
22+
@since 0.2.0.0
23+
-}
24+
25+
module Colourista.Mode
26+
( ColourMode (..)
27+
, HasColourMode
28+
, withColourMode
29+
, handleColourMode
30+
) where
31+
32+
import System.IO (Handle)
33+
import System.Console.ANSI (hSupportsANSIWithoutEmulation)
34+
import Data.String (IsString)
35+
36+
import GHC.Classes (IP (..))
37+
38+
39+
{- | Data type that tells whether the colouring is enabled or
40+
disabled. It's used with the @-XImplicitParams@ GHC extension.
41+
42+
@since 0.2.0.0
43+
-}
44+
data ColourMode
45+
= DisableColour
46+
| EnableColour
47+
deriving stock (Show, Eq, Enum, Bounded)
48+
49+
{- | Magic instance to set the value of the implicit variable
50+
@?colourMode@ to 'EnableColour' by default. Equivalent to the
51+
following code:
52+
53+
@
54+
?colourMode = 'EnableColour'
55+
@
56+
57+
However, you still can override @?colourMode@ with any possible value.
58+
59+
@since 0.2.0.0
60+
-}
61+
instance IP "colourMode" ColourMode where
62+
ip = EnableColour
63+
64+
{- | Constraint that stores 'ColourMode' as an implicit parameter.
65+
66+
@since 0.2.0.0
67+
-}
68+
type HasColourMode = (?colourMode :: ColourMode)
69+
70+
{- | Helper function for writing custom formatter. The function takes
71+
'ColourMode' from the implicit parameter context and either returns a
72+
given string or an empty string.
73+
74+
@since 0.2.0.0
75+
-}
76+
withColourMode :: (HasColourMode, IsString str) => str -> str
77+
withColourMode str = case ?colourMode of
78+
EnableColour -> str
79+
DisableColour -> ""
80+
{-# INLINE withColourMode #-}
81+
82+
{- | Returns 'ColourMode' of a 'Handle'. You can use this function on
83+
output 'Handle's to find out whether they support colouring or
84+
now. Use this function like this to check whether you can print with
85+
colour to terminal:
86+
87+
@
88+
'handleColourMode' 'System.IO.stdout'
89+
@
90+
91+
Typical usage can look like this:
92+
93+
@
94+
main :: IO ()
95+
main = do
96+
colourMode <- 'handleColourMode' 'System.IO.stdout'
97+
let ?colourMode = fromMaybe 'DisableColour'
98+
'Colourista.IO.successMessage' "Success!"
99+
@
100+
101+
@since 0.2.0.0
102+
-}
103+
handleColourMode :: Handle -> IO (Maybe ColourMode)
104+
handleColourMode handle = do
105+
supportsANSI <- hSupportsANSIWithoutEmulation handle
106+
pure $ fmap
107+
(\supportsColour -> if supportsColour then EnableColour else DisableColour)
108+
supportsANSI

0 commit comments

Comments
 (0)