rainbow-0.34.2.2: Print text to terminal with colors and effects

Safe HaskellNone
LanguageHaskell2010

Rainbow

Contents

Description

Rainbow handles colors and special effects for text. The basic building block of Rainbow is the Chunk. The Chunk contains both text and formatting information such as colors, bold, underlining, etc. Chunk is an instance of IsString so you can create a Chunk using the OverloadedStrings extension. Such a chunk has the given text and has no formatting.

When printed, each Chunk starts off with a clean slate, so if you want special formatting such as any color, bold, etc, then you must specify it for every Chunk. The appearance of one Chunk does not affect the appearance of the next Chunk. This makes it easy to reason about how a particular Chunk will look.

Rainbow supports 256-color terminals. You have full freedom to specify different attributes and colors for 8 and 256 color terminals; for instance, you can have text appear red on an 8-color terminal but blue on a 256-color terminal.

Here are some basic examples:

ghci> import Rainbow
ghci> import Data.Function ((&))
ghci> :set -XOverloadedStrings
ghci> putChunkLn $ "Some blue text" & fore blue
ghci> putChunkLn $ "Blue on red background"
              & fore blue & back red
ghci> putChunkLn $ "Blue on red, foreground bold"
               & fore blue & back red & bold

You can also specify output for 256-color terminals. To use these examples, be sure your TERM environment variable is set to something that supports 256 colors (like xterm-256color) before you start GHCi.

ghci> putChunkLn $ "Blue on 8, bright green on 256" &
   fore (blue <> brightGreen)

ghci> putChunkLn $ "Blue on 8, red on 256" &
   fore (blue <> only256 red)

Each Chunk affects the formatting only of that Chunk. So to print things in different colors, make more than one Chunk:

ghci> putChunksLn
   [ "Roses" & fore red
   , "Violets" & fore blue ]

Most of the above examples use putChunkLn, but that function may be inefficient if you are printing many Chunks. For greater efficiency use functions under the heading "Converting multiple Chunk to ByteString", including putChunksLn and putChunks.

The functions in this module, Rainbow, will likely be enough for most uses, but for more flexibility you can use Rainbow.Types. Use of Rainbow.Types will require some familiarity with the lens library.

Synopsis

Chunk

data Chunk Source #

A chunk is some textual data coupled with a description of what color the text is, attributes like whether it is bold or underlined, etc. The chunk knows what foreground and background colors and what attributes to use for both an 8 color terminal and a 256 color terminal.

Instances
Eq Chunk Source # 
Instance details

Defined in Rainbow.Types

Methods

(==) :: Chunk -> Chunk -> Bool #

(/=) :: Chunk -> Chunk -> Bool #

Ord Chunk Source # 
Instance details

Defined in Rainbow.Types

Methods

compare :: Chunk -> Chunk -> Ordering #

(<) :: Chunk -> Chunk -> Bool #

(<=) :: Chunk -> Chunk -> Bool #

(>) :: Chunk -> Chunk -> Bool #

(>=) :: Chunk -> Chunk -> Bool #

max :: Chunk -> Chunk -> Chunk #

min :: Chunk -> Chunk -> Chunk #

Show Chunk Source # 
Instance details

Defined in Rainbow.Types

Methods

showsPrec :: Int -> Chunk -> ShowS #

show :: Chunk -> String #

showList :: [Chunk] -> ShowS #

IsString Chunk Source #

Creates a Chunk with no formatting and with the given text.

Instance details

Defined in Rainbow.Types

Methods

fromString :: String -> Chunk #

Generic Chunk Source # 
Instance details

Defined in Rainbow.Types

Associated Types

type Rep Chunk :: Type -> Type #

Methods

from :: Chunk -> Rep Chunk x #

to :: Rep Chunk x -> Chunk #

Semigroup Chunk Source #

Uses the underlying Semigroup instances for both the underlying Scheme and the underlying Text.

Instance details

Defined in Rainbow.Types

Methods

(<>) :: Chunk -> Chunk -> Chunk #

sconcat :: NonEmpty Chunk -> Chunk #

stimes :: Integral b => b -> Chunk -> Chunk #

Monoid Chunk Source #

Uses the underlying Monoid instances for the Scheme and for the underlying Text. Therefore mempty will have no formatting, no colors, and no text.

Instance details

Defined in Rainbow.Types

Methods

mempty :: Chunk #

mappend :: Chunk -> Chunk -> Chunk #

mconcat :: [Chunk] -> Chunk #

type Rep Chunk Source # 
Instance details

Defined in Rainbow.Types

type Rep Chunk = D1 (MetaData "Chunk" "Rainbow.Types" "rainbow-0.34.2.2-1ah5PZE6w84FK2I3qiFqVN" False) (C1 (MetaCons "Chunk" PrefixI True) (S1 (MetaSel (Just "_scheme") NoSourceUnpackedness NoSourceStrictness DecidedLazy) (Rec0 Scheme) :*: S1 (MetaSel (Just "_yarn") NoSourceUnpackedness NoSourceStrictness DecidedLazy) (Rec0 Text)))

chunk :: Text -> Chunk Source #

Creates a Chunk with no formatting and with the given text. A Chunk is also an instance of IsString so you can create them with the OverloadedStrings extension. Such a Chunk has the text of the string and no formatting.

Formatting, all terminals

These combinators affect the way a Chunk is displayed on both 8- and 256-color terminals.

bold :: Chunk -> Chunk Source #

Bold. What actually happens when you use Bold is going to depend on your terminal. For example, xterm allows you actually use a bold font for bold, if you have one. Otherwise, it might simulate bold by using overstriking. Another possibility is that your terminal might use a different color to indicate bold. For more details (at least for xterm), look at xterm (1) and search for boldColors.

If your terminal uses a different color for bold, this allows an 8-color terminal to really have 16 colors.

Colors

data Radiant Source #

Stores colors that may affect 8-color terminals, 256-color terminals, both, or neither.

Instances
Eq Radiant Source # 
Instance details

Defined in Rainbow.Types

Methods

(==) :: Radiant -> Radiant -> Bool #

(/=) :: Radiant -> Radiant -> Bool #

Ord Radiant Source # 
Instance details

Defined in Rainbow.Types

Show Radiant Source # 
Instance details

Defined in Rainbow.Types

Generic Radiant Source # 
Instance details

Defined in Rainbow.Types

Associated Types

type Rep Radiant :: Type -> Type #

Methods

from :: Radiant -> Rep Radiant x #

to :: Rep Radiant x -> Radiant #

Semigroup Radiant Source # 
Instance details

Defined in Rainbow.Types

Monoid Radiant Source #

Uses the underlying Monoid instance for the Colors. Thus the last non-Nothing Color is used. This can be useful to specify one color for 8-color terminals and a different color for 256-color terminals.

Instance details

Defined in Rainbow.Types

type Rep Radiant Source # 
Instance details

Defined in Rainbow.Types

type Rep Radiant = D1 (MetaData "Radiant" "Rainbow.Types" "rainbow-0.34.2.2-1ah5PZE6w84FK2I3qiFqVN" False) (C1 (MetaCons "Radiant" PrefixI True) (S1 (MetaSel (Just "_color8") NoSourceUnpackedness NoSourceStrictness DecidedLazy) (Rec0 (Color Enum8)) :*: S1 (MetaSel (Just "_color256") NoSourceUnpackedness NoSourceStrictness DecidedLazy) (Rec0 (Color Word8))))

fore :: Radiant -> Chunk -> Chunk Source #

Change the foreground color. Whether this affects 8-color terminals, 256-color terminals, or both depends on the Radiant.

back :: Radiant -> Chunk -> Chunk Source #

Change the background color. Whether this affects 8-color terminals, 256-color terminals, or both depends on the Radiant.

Colors, all terminals

These Radiant affect the way a Chunk is displayed on both 8- and 256-color terminals.

Colors, 256-color terminals only

These Radiant affect 256-color terminals only.

color256 :: Word8 -> Radiant Source #

A Radiant for any of the 256 colors available. Supply the color number. Exactly which color you'll get for a given number is dependent on the terminal; though there seem to be common defaults, often the user can configure this however she likes. The resulting Radiant will affect 256-color terminals only.

only256 :: Radiant -> Radiant Source #

Ensures that a Radiant affects only a 256-color terminal. For instance, to make text that is blue on an 8-color terminal but red on a 256-color terminal:

putChunkLn $ "Blue on 8, red on 256" &
   fore (blue <> only256 red)

Converting multiple Chunk to ByteString

To print a Chunk, you need to convert it to some ByteStrings.

All these functions convert the Text to UTF-8 ByteStrings. Many of these functions return a difference list. Learn You a Haskell for Great Good has a great explanation of difference lists:

http://learnyouahaskell.com/for-a-few-monads-more

If you don't want to learn about difference lists, just stick with using chunksToByteStrings and use byteStringMakerFromEnvironment if you want to use the highest number of colors possible, or, to manually specify the number of colors, use chunksToByteStrings with toByteStringsColors0, toByteStringsColors8, or toByteStringsColors256 as the first argument. chunksToByteStrings has an example.

For output to handles or to standard output, just use hPutChunks or putChunks.

byteStringMakerFromEnvironment :: IO (Chunk -> [ByteString] -> [ByteString]) Source #

Uses setupTermFromEnv to obtain the terminal's color capability. If this says there are at least 256 colors are available, returns toByteStringsColors256. Otherwise, if there are at least 8 colors available, returns toByteStringsColors8. Otherwise, returns toByteStringsColors0.

If the terminfo database could not be read (that is, if SetupTermError is returned), then return toByteStringsColors0.

byteStringMakerFromHandle :: Handle -> IO (Chunk -> [ByteString] -> [ByteString]) Source #

Like byteStringMakerFromEnvironment but also consults a provided Handle. If the Handle is not a terminal, toByteStringsColors0 is returned. Otherwise, the value of byteStringMakerFromEnvironment is returned.

chunksToByteStrings Source #

Arguments

:: (Chunk -> [ByteString] -> [ByteString])

Function that converts Chunk to ByteString. This function, when applied to a Chunk, returns a difference list.

-> [Chunk] 
-> [ByteString] 

Convert a list of Chunk to a list of ByteString. The length of the returned list may be longer than the length of the input list.

So, for example, to print a bunch of chunks to standard output using 256 colors:

module PrintMyChunks where

import qualified Data.ByteString as BS
import Rainbow

myChunks :: [Chunk String]
myChunks = [ chunk "Roses" & fore red, chunk "\n",
             chunk "Violets" & fore blue, chunk "\n" ]

myPrintedChunks :: IO ()
myPrintedChunks = mapM_ BS.putStr
                . chunksToByteStrings toByteStringsColors256
                $ myChunks

To use the highest number of colors that this terminal supports:

myPrintedChunks' :: IO ()
myPrintedChunks' = do
  printer <- byteStringMakerFromEnvironment
  mapM_ BS.putStr
    . chunksToByteStrings printer
    $ myChunks

Writing multiple Chunk to a handle or to standard output

putChunks :: [Chunk] -> IO () Source #

Writes a list of chunks to standard output.

First uses byteStringMakerFromEnvironment to determine how many colors to use. Then creates a list of ByteString using chunksToByteStrings and then writes them to standard output.

hPutChunks :: Handle -> [Chunk] -> IO () Source #

Writes a list of chunks to the given Handle.

First uses byteStringMakerFromEnvironment to determine how many colors to use. Then creates a list of ByteString using chunksToByteStrings and then writes them to the given Handle.

putChunksLn :: [Chunk] -> IO () Source #

Writes a list of chunks to standard output, followed by a newline.

First uses byteStringMakerFromEnvironment to determine how many colors to use. Then creates a list of ByteString using chunksToByteStrings and then writes them to standard output.

hPutChunksLn :: Handle -> [Chunk] -> IO () Source #

Writes a list of chunks to the given Handle, followed by a newline character.

First uses byteStringMakerFromEnvironment to determine how many colors to use. Then creates a list of ByteString using chunksToByteStrings and then writes them to the given Handle.

Quick and dirty functions for IO

For efficiency reasons you probably don't want to use these when printing large numbers of Chunk, but they are handy for throwaway uses like experimenting in GHCi.

putChunk :: Chunk -> IO () Source #

Writes a Chunk to standard output. Uses byteStringMakerFromEnvironment each time you apply it, so this might be inefficient. You are better off using chunksToByteStrings and the functions in Data.ByteString to print your Chunks if you are printing a lot of them.

putChunkLn :: Chunk -> IO () Source #

Writes a Chunk to standard output, and appends a newline. Uses byteStringMakerFromEnvironment each time you apply it, so this might be inefficient. You are better off using chunksToByteStrings and the functions in Data.ByteString to print your Chunks if you are printing a lot of them.

Notes on terminals

Earlier versions of Rainbow used the Haskell terminfo library for dealing with the terminal. Terminfo is available at

https://hackage.haskell.org/package/terminfo

Terminfo, in turn, uses the UNIX terminfo library. The biggest advantage of using Terminfo is that it is compatible with a huge variety of terminals. Many of these terminals are hardware models that are gathering dust in an IBM warehouse somewhere, but even modern software terminals might have quirks. Terminfo covers all those.

The disadvantage is that using Terminfo requires you to perform IO whenever you need to format output for the terminal. Your only choice when using Terminfo is to send output directly to the terminal, or to a handle. This goes against typical Haskell practice, where we try to write pure code whenever possible.

Perhaps surprisingly, there are times where you may want to format output, but not immediately send it to the terminal. Maybe you want to send it to a file instead, or maybe you want to use a Haskell library like Pipes and stream it somewhere. Terminfo is a binding to a Unix library that is not designed for this sort of thing. The closest you could get using Terminfo would be to make a Handle that is backed by a in-memory buffer. There is a package for that sort of thing:

http://hackage.haskell.org/package/knob

but it seems like a nasty workaround. Or you can hijack stdout and send that somewhere--again, nasty workaround.

So I decided to stop using Terminfo. That means Rainbow no longer supports a menagerie of bizarre terminals. It instead just uses the standard ISO 6429 / ECMA 48 terminal codes. These are the same codes that are used by xterm, the OS X Terminal, the Linux console, or any other reasonably modern software terminal. Realistically they are the only terminals Rainbow would be used for.

The 256 color capability is not in ISO 6429, but it is widely supported.

Probably the most common so-called terminals in use today that do NOT support the ISO 6429 codes are those that are not really terminals. For instance, you might use an Emacs shell buffer. For those situations just use toByteStringsColors0.

I also decided to standardize on UTF-8 for the Text output. These days that seems reasonable.

Apparently it's difficult to get ISO 6429 support on Microsoft Windows. Oh well.