diff --git a/LICENSE b/LICENSE
--- a/LICENSE
+++ b/LICENSE
@@ -1,4 +1,4 @@
-Copyright (c) 2013-2014, Omari Norman
+Copyright (c) 2013-2019 Omari Norman
 
 All rights reserved.
 
diff --git a/README.md b/README.md
new file mode 100644
--- /dev/null
+++ b/README.md
@@ -0,0 +1,20 @@
+# rainbow
+
+rainbow helps you build colorful output for both 8- and 256-color
+terminals.  It works only on Unix-like operating systems.  For more
+information, please see the documentation in the main Rainbow
+module.
+
+rainbow is on Github:
+
+http://www.github.com/massysett/rainbow
+
+and Hackage:
+
+http://hackage.haskell.org/package/rainbow
+
+and Stackage:
+
+https://www.stackage.org/package/rainbow
+
+rainbow is licensed under the BSD license; see the LICENSE file.
diff --git a/lib/Rainbow.hs b/lib/Rainbow.hs
--- a/lib/Rainbow.hs
+++ b/lib/Rainbow.hs
@@ -1,108 +1,89 @@
--- | Rainbow handles colors and special effects for text.
---
--- The building block of Rainbow is the 'Chunk'. Each 'Chunk' comes with
--- a 'TextSpec', which specifies how the text should look on 8-color
--- and on 256-color terminals. The 'Chunk' is a full specification; that
--- is, although 'Chunk's are typically printed one after the other, the
--- appearance of one 'Chunk' does not affect the appearance of the next
--- 'Chunk'.
---
--- 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.
---
--- A 'Chunk' is a 'Monoid', so you can combine them using
--- the usual monoid functions, including '<>'. You can
--- create a 'Chunk' with text using 'Data.String.fromString', but this
--- library is much more usable if you enable the OverloadedStrings GHC
--- extension:
---
--- > {-# LANGUAGE OverloadedStrings #-}
---
--- or, in GHCi:
+{-# LANGUAGE TemplateHaskell #-}
+{-# LANGUAGE DeriveFunctor #-}
+{-# LANGUAGE DeriveFoldable #-}
+{-# LANGUAGE DeriveTraversable #-}
+{-# LANGUAGE DeriveGeneric #-}
+{-# LANGUAGE DeriveDataTypeable #-}
+{-# LANGUAGE OverloadedStrings #-}
+{-# LANGUAGE RankNTypes #-}
+
+-- | Rainbow handles colors and special effects for text.  The basic
+-- building block of Rainbow is the 'Y.Chunk'.  The 'Y.Chunk' contains
+-- both text and formatting information such as colors, bold,
+-- underlining, etc.  'Y.Chunk' is an instance of
+-- 'Data.String.IsString' so you can create a 'Y.Chunk' using the
+-- @OverloadedStrings@ extension.  Such a chunk has the given text
+-- and has no formatting.
 --
--- >>> :set -XOverloadedStrings
+-- When printed, each 'Y.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 'Y.Chunk'.  The appearance of one
+-- 'Y.Chunk' does not affect the appearance of the next 'Y.Chunk'.
+-- This makes it easy to reason about how a particular 'Y.Chunk' will
+-- look.
 --
--- and all future examples assume you have enabled OverloadedStrings.
+-- 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:
 --
 -- @
--- 'putChunkLn' $ \"Some blue text\" \<> 'fore' 'blue'
--- 'putChunkLn' $ \"Blue on red background\"
---               \<> 'fore' 'blue' \<> 'back' 'red'
--- 'putChunkLn' $ \"Blue on red, foreground bold\"
---                \<> 'fore' 'blue' \<> 'back' 'red' \<> 'bold'
--- @
---
--- But what makes Rainbow a little more interesting is that 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.
---
--- @
--- 'putChunkLn' $ \"Blue on 8-color terminal, red on 256-color terminal\"
---                 \<> 'fore' 'blue8' \<> 'fore' ('to256' 'red8')
+-- ghci> import Rainbow
+-- ghci> import Data.Function ((&))
+-- ghci> :set -XOverloadedStrings
+-- ghci> 'T.putChunkLn' $ \"Some blue text\" '&' 'fore' 'blue'
+-- ghci> 'T.putChunkLn' $ \"Blue on red background\"
+--               '&' 'fore' 'blue' '&' 'back' 'red'
+-- ghci> 'T.putChunkLn' $ \"Blue on red, foreground bold\"
+--                '&' 'fore' 'blue' '&' 'back' 'red' '&' 'bold'
 -- @
 --
--- To get a 'Color256', which affects only 256-color terminals, there
--- are some definitions in the module such as 'brightRed'.  You may
--- also use 'Word8' literals, like this.  You need to specify the type
--- as it can't be inferred:
+-- 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.
 --
 -- @
--- 'putChunkLn' $ \"Pink on 256-color terminal only\"
---                \<> 'fore' (201 :: 'Word8')
--- @
---
--- If you 'mappend' multiple chunks that change the same property, the
--- rightmost one \"wins\":
+-- ghci> 'T.putChunkLn' $ \"Blue on 8, bright green on 256\" '&'
+--    'fore' ('blue' '<>' 'brightGreen')
 --
--- @
--- 'putChunkLn' $ \"This will be blue\" \<> 'fore' 'red' \<> 'fore' 'blue'
+-- ghci> 'T.putChunkLn' $ \"Blue on 8, red on 256" '&'
+--    'fore' ('blue' '<>' 'only256' 'red')
 -- @
 --
--- This property comes in handy if you want to specify a default color
--- for 8- and 256-color terminals, then a more specific shade for a
--- 256-color terminal:
+-- Each 'Y.Chunk' affects the formatting only of that 'Y.Chunk'.  So
+-- to print things in different colors, make more than one 'Y.Chunk':
 --
 -- @
--- 'putChunkLn' $ \"Red on 8-color, pink on 256-color\"
---                \<> 'fore' 'red' \<> 'fore' (201 :: 'Word8')
+-- ghci> 'T.putChunksLn'
+--    [ \"Roses\" '&' 'fore' 'red'
+--    , \"Violets\" '&' 'fore' 'blue' ]
 -- @
 --
--- However, if you use 'mappend' to add additional 'Chunk's that have
--- text, the text will be appended:
---
--- @
--- 'putChunkLn' $ 'fore' 'green' \<> \"You will see this text \"
---              \<> \"and this text too, but it will all be blue\"
---              \<> 'fore' 'blue'
--- @
+-- Most of the above examples use 'T.putChunkLn', but that function
+-- may be inefficient if you are printing many 'Y.Chunk's.  For
+-- greater efficiency use functions under the heading \"Converting
+-- multiple Chunk to ByteString\", including 'T.putChunksLn' and
+-- 'T.putChunks'.
 --
--- Although one chunk can have different colors on 8- and 256-color
--- terminals, it cannot have different colors on the same
--- terminal. That is, if you want to print some text in one color and
--- some text in another color, make two chunks.
+-- 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.
 
+
 module Rainbow
   (
-
-  -- * Chunks
-    Chunk
-  , chunkFromText
-  , chunkFromTexts
-  , chunkFromLazyText
-  , chunkFromLazyTexts
-
-  -- * Effects for both 8 and 256 color terminals
+  -- * Chunk
+    Y.Chunk
+  , Y.chunk
 
-  -- | These 'Chunk's affect both 8 and 256 color terminals:
-  --
-  -- @
-  -- 'putChunkLn' $ \"bold on 8 and 256 color terminals\" \<> 'bold'
-  -- @
+  -- * Formatting, all terminals
 
+  -- | These combinators affect the way a 'Y.Chunk' is displayed on
+  -- both 8- and 256-color terminals.
   , bold
   , faint
   , italic
@@ -112,51 +93,15 @@
   , invisible
   , strikeout
 
-  -- * Effects for 8-color terminals only
-
-  -- | These 'Chunk's affect 8-color terminals only.
-  --
-  -- @
-  -- 'putChunkLn' $ \"Bold on 8 color terminal only\" \<> 'bold8'
-  -- @
-
-  , bold8
-  , faint8
-  , italic8
-  , underline8
-  , blink8
-  , inverse8
-  , invisible8
-  , strikeout8
-
-  -- * Effects for 256-color terminals only
-
-  -- | These 'Chunk's affect 256-color terminals only.
-  --
-  -- @
-  -- 'putChunkLn' $ \"Underlined on 256-color terminal, \"
-  --              \<> \"bold on 8-color terminal\"
-  --              \<> 'underline256' \<> 'bold8'
-  -- @
-
-  , bold256
-  , faint256
-  , italic256
-  , underline256
-  , blink256
-  , inverse256
-  , invisible256
-  , strikeout256
-
   -- * Colors
+  , Y.Radiant
+  , fore
+  , back
 
-  -- ** Changing the foreground and background color
-  , Color(..)
+  -- * Colors, all terminals
 
-  -- ** Colors for both 8- and 256-color terminals
-  , Radiant(..)
-  , noColorRadiant
-  , both
+  -- | These 'Y.Radiant' affect the way a 'Y.Chunk' is displayed on
+  -- both 8- and 256-color terminals.
   , black
   , red
   , green
@@ -166,23 +111,9 @@
   , cyan
   , white
 
-
-  -- ** Colors for 8-color terminals only
-  , Enum8(..)
-  , Color8(..)
-  , noColor8
-  , black8
-  , red8
-  , green8
-  , yellow8
-  , blue8
-  , magenta8
-  , cyan8
-  , white8
+  -- * Colors, 256-color terminals only
 
-  -- ** Colors for 256-color terminals only
-  , Color256(..)
-  , noColor256
+  -- | These 'Y.Radiant' affect 256-color terminals only.
   , grey
   , brightRed
   , brightGreen
@@ -191,11 +122,12 @@
   , brightMagenta
   , brightCyan
   , brightWhite
-  , to256
+  , color256
+  , only256
 
-  -- * Converting 'Chunk' to 'Data.ByteString.ByteString'
+  -- * Converting multiple 'Y.Chunk' to 'Data.ByteString.ByteString'
 
-  -- | To print a 'Chunk', you need to convert it to some
+  -- | To print a 'Y.Chunk', you need to convert it to some
   -- 'Data.ByteString.ByteString's.
   --
   -- All these functions convert the 'Data.Text' to UTF-8
@@ -206,70 +138,166 @@
   -- 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
+  -- with using 'T.chunksToByteStrings' and use
+  -- 'T.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.
-  , byteStringMakerFromEnvironment
-  , byteStringMakerFromHandle
-  , toByteStringsColors0
-  , toByteStringsColors8
-  , toByteStringsColors256
-  , chunksToByteStrings
+  -- colors, use 'T.chunksToByteStrings' with 'T.toByteStringsColors0',
+  -- 'T.toByteStringsColors8', or 'T.toByteStringsColors256' as the first
+  -- argument.  'T.chunksToByteStrings' has an example.
+  --
+  -- For output to handles or to standard output, just use
+  -- 'T.hPutChunks' or 'T.putChunks'.
+  , T.toByteStringsColors0
+  , T.toByteStringsColors8
+  , T.toByteStringsColors256
+  , T.byteStringMakerFromEnvironment
+  , T.byteStringMakerFromHandle
+  , T.chunksToByteStrings
 
+  -- * Writing multiple 'Y.Chunk' to a handle or to standard output
+  , T.putChunks
+  , T.hPutChunks
+  , T.putChunksLn
+  , T.hPutChunksLn
+
   -- * 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
+  -- when printing large numbers of 'Y.Chunk', but they are handy for
   -- throwaway uses like experimenting in GHCi.
-  , putChunk
-  , putChunkLn
-
-  -- * Re-exports
-  -- $reexports
-  -- | * "Data.Monoid" re-exports '<>' and 'mempty'
-  --
-  -- 
-  , module Data.Monoid
-  , module Data.ByteString
-  , module Data.Text
-  , module Data.Word
+  , T.putChunk
+  , T.putChunkLn
 
   -- * Notes on terminals
   -- $termNotes
 
   ) where
 
-import Rainbow.Types
-import Rainbow.Colors
-import Rainbow.Translate
-  ( byteStringMakerFromEnvironment
-  , byteStringMakerFromHandle
-  , toByteStringsColors0
-  , toByteStringsColors8
-  , toByteStringsColors256
-  , chunksToByteStrings
-  , putChunk
-  , putChunkLn
-  )
-import Data.Monoid (Monoid, (<>), mempty)
-import Data.Text (Text)
-import Data.ByteString (ByteString)
+import qualified Rainbow.Translate as T
+import qualified Rainbow.Types as Y
 import Data.Word (Word8)
+import Data.Function ((&))
+import qualified Control.Lens as Lens
+import Control.Lens ((.~))
+import Data.Monoid (Monoid(mempty))
 
-{- $reexports
+formatBoth :: Lens.Setter' Y.Format Bool -> Y.Chunk -> Y.Chunk
+formatBoth get c = c & Y.scheme . Y.style8 . Y.format . get .~ True
+  & Y.scheme . Y.style256 . Y.format . get .~ True
 
-   * "Data.Monoid" re-exports 'Monoid', '<>' and 'mempty'
+-- | 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.
+bold :: Y.Chunk -> Y.Chunk
+bold = formatBoth Y.bold
 
-   * "Data.ByteString" re-exports 'ByteString'
+faint :: Y.Chunk -> Y.Chunk
+faint = formatBoth Y.faint
 
-   * "Data.Text" re-exports 'Text'
+italic :: Y.Chunk -> Y.Chunk
+italic = formatBoth Y.italic
 
-   * "Data.Word" re-exports 'Word8'
--}
+underline :: Y.Chunk -> Y.Chunk
+underline = formatBoth Y.underline
 
+blink :: Y.Chunk -> Y.Chunk
+blink = formatBoth Y.blink
+
+inverse :: Y.Chunk -> Y.Chunk
+inverse = formatBoth Y.inverse
+
+invisible :: Y.Chunk -> Y.Chunk
+invisible = formatBoth Y.invisible
+
+strikeout :: Y.Chunk -> Y.Chunk
+strikeout = formatBoth Y.strikeout
+
+-- | Change the foreground color.  Whether this affects 8-color
+-- terminals, 256-color terminals, or both depends on the 'Y.Radiant'.
+fore :: Y.Radiant -> Y.Chunk -> Y.Chunk
+fore (Y.Radiant c8 c256) c = c & Y.scheme . Y.style8 . Y.fore .~ c8
+  & Y.scheme . Y.style256 . Y.fore .~ c256
+
+-- | Change the background color.  Whether this affects 8-color
+-- terminals, 256-color terminals, or both depends on the 'Y.Radiant'.
+back :: Y.Radiant -> Y.Chunk -> Y.Chunk
+back (Y.Radiant c8 c256) c = c & Y.scheme . Y.style8 . Y.back .~ c8
+  & Y.scheme . Y.style256 . Y.back .~ c256
+
+-- | Ensures that a 'Y.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:
+--
+-- @
+-- 'T.putChunkLn' $ \"Blue on 8, red on 256\" &
+--    'fore' ('blue' <> 'only256' 'red')
+-- @
+only256 :: Y.Radiant -> Y.Radiant
+only256 (Y.Radiant _ c256) = Y.Radiant mempty c256
+
+black :: Y.Radiant
+black = Y.Radiant (Y.Color (Just Y.E0)) (Y.Color (Just 0))
+
+red :: Y.Radiant
+red = Y.Radiant (Y.Color (Just Y.E1)) (Y.Color (Just 1))
+
+green :: Y.Radiant
+green = Y.Radiant (Y.Color (Just Y.E2)) (Y.Color (Just 2))
+
+yellow :: Y.Radiant
+yellow = Y.Radiant (Y.Color (Just Y.E3)) (Y.Color (Just 3))
+
+blue :: Y.Radiant
+blue = Y.Radiant (Y.Color (Just Y.E4)) (Y.Color (Just 4))
+
+magenta :: Y.Radiant
+magenta = Y.Radiant (Y.Color (Just Y.E5)) (Y.Color (Just 5))
+
+cyan :: Y.Radiant
+cyan = Y.Radiant (Y.Color (Just Y.E6)) (Y.Color (Just 6))
+
+white :: Y.Radiant
+white = Y.Radiant (Y.Color (Just Y.E7)) (Y.Color (Just 7))
+
+grey :: Y.Radiant
+grey = color256 8
+
+brightRed :: Y.Radiant
+brightRed = color256 9
+
+brightGreen :: Y.Radiant
+brightGreen = color256 10
+
+brightYellow :: Y.Radiant
+brightYellow = color256 11
+
+brightBlue :: Y.Radiant
+brightBlue = color256 12
+
+brightMagenta :: Y.Radiant
+brightMagenta = color256 13
+
+brightCyan :: Y.Radiant
+brightCyan = color256 14
+
+brightWhite :: Y.Radiant
+brightWhite = color256 15
+
+-- | A 'Y.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 'Y.Radiant' will affect 256-color terminals only.
+color256 :: Word8 -> Y.Radiant
+color256 x = Y.Radiant (Y.Color Nothing) (Y.Color (Just x))
+
+
 {- $termNotes
 
 Earlier versions of Rainbow used the Haskell terminfo library for
@@ -315,14 +343,10 @@
 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.
+situations just use 'T.toByteStringsColors0'.
 
-Now, to figure out how many colors the terminal supports, Rainbow
-simply uses the @tput@ program.  This removes the dependency on
-Terminfo altogether.
+I also decided to standardize on UTF-8 for the 'Data.Text.Text'
+output.  These days that seems reasonable.
 
 Apparently it's difficult to get ISO 6429 support on Microsoft
 Windows.  Oh well.
diff --git a/lib/Rainbow/Colors.hs b/lib/Rainbow/Colors.hs
deleted file mode 100644
--- a/lib/Rainbow/Colors.hs
+++ /dev/null
@@ -1,190 +0,0 @@
-{-# LANGUAGE DeriveGeneric, DeriveDataTypeable #-}
--- | Ordinarily you should not need this module; typically you will
--- just import "Rainbow", which re-exports the most useful things from
--- this module.  This module also contains data constructors that
--- "Rainbow" does not re-export.
-module Rainbow.Colors where
-
-import Data.Maybe (fromMaybe)
-import Rainbow.Types
-import Data.Monoid
-import Data.Word (Word8)
-import GHC.Generics
-import Data.Typeable
-
--- * 8 color
-
--- | Resets the color (foreground or background, as appropriate) to
--- the default for your terminal.  Usually you will not need this, as
--- each 'Chunk' starts out with the terminal's default colors.
-noColor8 :: Color8
-noColor8 = Color8 Nothing
-
-black8 :: Color8
-black8 = Color8 (Just E0)
-
-red8 :: Color8
-red8 = Color8 (Just E1)
-
-green8 :: Color8
-green8 = Color8 (Just E2)
-
-yellow8 :: Color8
-yellow8 = Color8 (Just E3)
-
-blue8 :: Color8
-blue8 = Color8 (Just E4)
-
-magenta8 :: Color8
-magenta8 = Color8 (Just E5)
-
-cyan8 :: Color8
-cyan8 = Color8 (Just E6)
-
-white8 :: Color8
-white8 = Color8 (Just E7)
-
--- * 256 color
-
--- | Resets the color (foreground or background, as appropriate) to
--- the default for your terminal.  Usually you will not need this, as
--- each 'Chunk' starts out with the terminal's default colors.
-noColor256 :: Color256
-noColor256 = Color256 Nothing
-
-grey :: Color256
-grey = Color256 (Just 8)
-
-brightRed :: Color256
-brightRed = Color256 (Just 9)
-
-brightGreen :: Color256
-brightGreen = Color256 (Just 10)
-
-brightYellow :: Color256
-brightYellow = Color256 (Just 11)
-
-brightBlue :: Color256
-brightBlue = Color256 (Just 12)
-
-brightMagenta :: Color256
-brightMagenta = Color256 (Just 13)
-
-brightCyan :: Color256
-brightCyan = Color256 (Just 14)
-
-brightWhite :: Color256
-brightWhite = Color256 (Just 15)
-
--- * Both 8- and 256-color terminals
-
--- | A 'Radiant' affects both 8- and 256-color terminals.  (It does
--- /not/ necessarily affect both the foreground and background;
--- whether it affects the foreground, background, or both depends upon
--- the context in which it is used.)
-data Radiant = Radiant
-  { rad8 :: Color8
-  , rad256 :: Maybe Color256
-  -- ^ If 'Nothing', use the 'rad8' color on 256-color terminals.
-  } deriving (Eq, Ord, Show, Generic, Typeable)
-
--- | A Radiant with the same color for both 8- and 256-color
--- terminals.
-both :: Color8 -> Radiant
-both c8 = Radiant c8 Nothing
-
--- | A Radiant that uses the terminal's default colors for both 8- and
--- 256-color terminals.
-noColorRadiant :: Radiant
-noColorRadiant = both noColor8
-
-black :: Radiant
-black = both black8
-
-red :: Radiant
-red = both red8
-
-green :: Radiant
-green = both green8
-
-yellow :: Radiant
-yellow = both yellow8
-
-blue :: Radiant
-blue = both blue8
-
-magenta :: Radiant
-magenta = both magenta8
-
-cyan :: Radiant
-cyan = both cyan8
-
-white :: Radiant
-white = both white8
-
--- | Changing colors.  Instances of this class affect the background
--- or the foreground color.  For example, to get a 'Chunk' that
--- changes the background to red, use @'back' 'red'@; for the
--- foreground, use @'fore' 'red'@.  Whether 8-color or 256-color
--- terminals (or both) are affected depends on the particular
--- instance.
---
--- Because 'Word8' is an instance of 'Color', you can use literals to
--- affect the color of 256-color terminals.  For example, if you have
--- a 256 color terminal:
---
--- > putChunkLn $ "muddy yellow background" <> back (100 :: Word8)
---
--- This example would not affect an 8-color terminal, as the 'Word8'
--- instance affects 256-color terminals only.
-
-class Color a where
-  -- | Create a 'Chunk' that affects the background color only.
-  back :: a -> Chunk
-
-  -- | Create a 'Chunk' that affects the foreground color only.
-  fore :: a -> Chunk
-
-instance Color Color8 where
-  back c = Chunk ts []
-    where
-      ts = TextSpec s8 mempty
-      s8 = Style8 mempty b8 mempty
-      b8 = Last (Just c)
-
-  fore c = Chunk ts []
-    where
-      ts = TextSpec s8 mempty
-      s8 = Style8 f8 mempty mempty
-      f8 = Last (Just c)
-
-instance Color Color256 where
-  back c = Chunk ts []
-    where
-      ts = TextSpec mempty s256
-      s256 = Style256 mempty b256 mempty
-      b256 = Last (Just c)
-
-  fore c = Chunk ts []
-    where
-      ts = TextSpec mempty s256
-      s256 = Style256 f256 mempty mempty
-      f256 = Last (Just c)
-
--- | Affects the foreground and background of both 8- and 256-color
--- terminals.
-instance Color Radiant where
-  fore (Radiant c8 mc256) = fore c8
-    <> fore (fromMaybe (to256 c8) mc256)
-  back (Radiant c8 mc256) = back c8
-    <> back (fromMaybe (to256 c8) mc256)
-
--- | Affects the foreground and background of 8-color terminals.
-instance Color Enum8 where
-  back = back . Color8 . Just
-  fore = fore . Color8 . Just
-
--- | Affects the foreground and background of 256-color terminals.
-instance Color Word8 where
-  back = back . Color256 . Just
-  fore = fore . Color256 . Just
diff --git a/lib/Rainbow/Translate.hs b/lib/Rainbow/Translate.hs
--- a/lib/Rainbow/Translate.hs
+++ b/lib/Rainbow/Translate.hs
@@ -1,24 +1,23 @@
-{-# LANGUAGE OverloadedStrings #-}
+{-# LANGUAGE FlexibleInstances #-}
+
+-- | This module contains functions that convert a 'T.Chunk' into
+-- 'ByteString's.  Ordinarily everything you need from this module is
+-- exported from "Rainbow".
 module Rainbow.Translate where
 
-import Data.Monoid
-import qualified Data.ByteString.Char8 as BS8
-import qualified Data.ByteString as BS
+import Control.Exception (try)
 import Data.ByteString (ByteString)
-import Data.Word
 import Data.List (intersperse)
-import Rainbow.Types
-  ( Color8(..), Enum8(..), Color256(..),
-    StyleCommon(..), Style8(..), Style256(..), TextSpec)
+import Data.Text (Text)
+import Data.Word (Word8)
+import qualified Data.ByteString as BS
+import qualified Data.ByteString.Char8 as BS8
+import qualified Data.Text.Encoding as X
 import qualified Rainbow.Types as T
-import Data.Text.Encoding
-import System.Process
-import Text.Read
-import System.Exit
-import Control.Monad
-import Control.Exception
+import qualified System.Console.Terminfo as Terminfo
 import qualified System.IO as IO
 
+
 single :: Char -> [ByteString] -> [ByteString]
 single c = ((BS8.singleton c):)
 
@@ -32,7 +31,9 @@
 sgr sq = csi . sq . single 'm'
 
 params :: Show a => [a] -> [ByteString] -> [ByteString]
-params cs = ((intersperse ";" . map (BS8.pack . show) $ cs) ++)
+params cs = ((intersperse semi . map (BS8.pack . show) $ cs) ++)
+  where
+    semi = BS8.singleton ';'
 
 sgrSingle :: Word -> [ByteString] -> [ByteString]
 sgrSingle w = sgr $ params [w]
@@ -134,44 +135,30 @@
 back256 :: Word8 -> [ByteString] -> [ByteString]
 back256 c = sgr $ params [48,5,c]
 
-foreColor8 :: Color8 -> [ByteString] -> [ByteString]
-foreColor8 (Color8 maym8) = case maym8 of
-  Nothing -> id
-  Just m8 -> case m8 of
-    E0 -> foreBlack
-    E1 -> foreRed
-    E2 -> foreGreen
-    E3 -> foreYellow
-    E4 -> foreBlue
-    E5 -> foreMagenta
-    E6 -> foreCyan
-    E7 -> foreWhite
-
-backColor8 :: Color8 -> [ByteString] -> [ByteString]
-backColor8 (Color8 maym8) = case maym8 of
-  Nothing -> id
-  Just m8 -> case m8 of
-    E0 -> backBlack
-    E1 -> backRed
-    E2 -> backGreen
-    E3 -> backYellow
-    E4 -> backBlue
-    E5 -> backMagenta
-    E6 -> backCyan
-    E7 -> backWhite
-
-foreColor256 :: Color256 -> [ByteString] -> [ByteString]
-foreColor256 (Color256 mayW8) = case mayW8 of
-  Nothing -> id
-  Just w8 -> fore256 w8
+foreColor8 :: T.Enum8 -> [ByteString] -> [ByteString]
+foreColor8 e8 = case e8 of
+  T.E0 -> foreBlack
+  T.E1 -> foreRed
+  T.E2 -> foreGreen
+  T.E3 -> foreYellow
+  T.E4 -> foreBlue
+  T.E5 -> foreMagenta
+  T.E6 -> foreCyan
+  T.E7 -> foreWhite
 
-backColor256 :: Color256 -> [ByteString] -> [ByteString]
-backColor256 (Color256 mayW8) = case mayW8 of
-  Nothing -> id
-  Just w8 -> back256 w8
+backColor8 :: T.Enum8 -> [ByteString] -> [ByteString]
+backColor8 e8 = case e8 of
+  T.E0 -> backBlack
+  T.E1 -> backRed
+  T.E2 -> backGreen
+  T.E3 -> backYellow
+  T.E4 -> backBlue
+  T.E5 -> backMagenta
+  T.E6 -> backCyan
+  T.E7 -> backWhite
 
-styleCommon :: StyleCommon -> [ByteString] -> [ByteString]
-styleCommon (StyleCommon bld fnt ita und bli ivr isb stk)
+renderFormat :: T.Format -> [ByteString] -> [ByteString]
+renderFormat (T.Format bld fnt ita und bli ivr isb stk)
   = effect bold bld
   . effect faint fnt
   . effect italic ita
@@ -181,90 +168,84 @@
   . effect invisible isb
   . effect strikeout stk
   where
-    effect on = maybe id (\x -> if x then on else id)
-      . getLast
+    effect on x = if x then on else id
 
-style8 :: Style8 -> [ByteString] -> [ByteString]
-style8 (Style8 f8 b8 sc)
-  = effect foreColor8 f8
-  . effect backColor8 b8
-  . styleCommon sc
+renderStyle8 :: T.Style T.Enum8 -> [ByteString] -> [ByteString]
+renderStyle8 (T.Style fore back format)
+  = effect foreColor8 fore
+  . effect backColor8 back
+  . renderFormat format
   where
-    effect on = maybe id on . getLast
+      effect on (T.Color may) = maybe id on may
 
-style256 :: Style256 -> [ByteString] -> [ByteString]
-style256 (Style256 f256 b256 sc)
-  = effect foreColor256 f256
-  . effect backColor256 b256
-  . styleCommon sc
+renderStyle256 :: T.Style Word8 -> [ByteString] -> [ByteString]
+renderStyle256 (T.Style fore back format)
+  = effect fore256 fore
+  . effect back256 back
+  . renderFormat format
   where
-    effect on = maybe id on . getLast
-
-textSpec8 :: TextSpec -> [ByteString] -> [ByteString]
-textSpec8 = style8 . T.style8
+    effect on (T.Color may) = maybe id on may
 
-textSpec256 :: TextSpec -> [ByteString] -> [ByteString]
-textSpec256 = style256 . T.style256
+render :: Text -> [ByteString] -> [ByteString]
+render x = (X.encodeUtf8 x :)
 
--- | Convert a 'T.Chunk' to a list of 'ByteString'; do not show any
--- colors.  When applied to a 'T.Chunk', this function returns a
--- difference list.
-toByteStringsColors0 :: T.Chunk -> [ByteString] -> [ByteString]
-toByteStringsColors0 c = ((map encodeUtf8 . T.chunkTexts $ c) ++)
+toByteStringsColors0
+  :: T.Chunk
+  -> [ByteString]
+  -> [ByteString]
+toByteStringsColors0 (T.Chunk _ yn) = render yn
 
--- | Convert a 'T.Chunk' to a list of 'ByteString'; show eight
--- colors.  When applied to a 'T.Chunk', this function returns a
--- difference list.
-toByteStringsColors8 :: T.Chunk -> [ByteString] -> [ByteString]
-toByteStringsColors8 c
+toByteStringsColors8
+  :: T.Chunk
+  -> [ByteString]
+  -> [ByteString]
+toByteStringsColors8 (T.Chunk (T.Scheme s8 _) yn)
   = normalDefault
-  . textSpec8 (T.chunkTextSpec c)
-  . ((map encodeUtf8 . T.chunkTexts $ c) ++)
+  . renderStyle8 s8
+  . render yn
   . normalDefault
 
--- | Convert a 'T.Chunk' to a list of 'ByteString'; show 256
--- colors.  When applied to a 'T.Chunk', this function returns a
--- difference list.
-toByteStringsColors256 :: T.Chunk -> [ByteString] -> [ByteString]
-toByteStringsColors256 c
+toByteStringsColors256
+  :: T.Chunk
+  -> [ByteString]
+  -> [ByteString]
+toByteStringsColors256 (T.Chunk (T.Scheme _ s256) yn)
   = normalDefault
-  . textSpec256 (T.chunkTextSpec c)
-  . ((map encodeUtf8 . T.chunkTexts $ c) ++)
+  . renderStyle256 s256
+  . render yn
   . normalDefault
 
 
--- | Spawns a subprocess to read the output of @tput colors@.  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'.
+-- | Uses 'Terminfo.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 any IO exceptions arise during this process, they are discarded
--- and 'toByteStringsColors0' is returned.
+-- If the terminfo database could not be read (that is, if
+-- 'System.Console.Terminfo.Base.SetupTermError' is returned), then return
+-- 'toByteStringsColors0'.
 byteStringMakerFromEnvironment
   :: IO (T.Chunk -> [ByteString] -> [ByteString])
-byteStringMakerFromEnvironment
-  = catcher (fmap f $ readProcessWithExitCode "tput" ["colors"] "")
+byteStringMakerFromEnvironment = fmap g (try Terminfo.setupTermFromEnv)
   where
-    f (code, stdOut, _) = maybe toByteStringsColors0 id $ do
-      case code of
-        ExitFailure _ -> mzero
-        _ -> return ()
-      numColors <- readMaybe stdOut
-      return $ numColorsToFunc numColors
-    numColorsToFunc i
-      | i >= (256 :: Int) = toByteStringsColors256
-      | i >= 8 = toByteStringsColors8
-      | otherwise = toByteStringsColors0
-
-    catcher act = fmap g (try act)
+    g (Left e) = toByteStringsColors0
       where
-        g (Left e) = toByteStringsColors0
-          where _types = e :: IOException
-        g (Right good) = good
+        -- Previously this caught all IOException.  Now it catches only SetupTermError.
+        -- See
+        -- https://github.com/commercialhaskell/stackage/issues/4994
+        -- Hopefully this will fix this Stackage bug.
+        _types = e :: Terminfo.SetupTermError
+    g (Right terminal) =
+      case Terminfo.getCapability terminal (Terminfo.tiGetNum "colors") of
+        Nothing -> toByteStringsColors0
+        Just c
+          | c >= 256 -> toByteStringsColors256
+          | c >= 8 -> toByteStringsColors8
+          | otherwise -> toByteStringsColors0
 
 -- | Like 'byteStringMakerFromEnvironment' but also consults a
--- provided 'Handle'.  If the 'Handle' is not a terminal,
+-- provided 'IO.Handle'.  If the 'IO.Handle' is not a terminal,
 -- 'toByteStringsColors0' is returned.  Otherwise, the value of
 -- 'byteStringMakerFromEnvironment' is returned.
 byteStringMakerFromHandle
@@ -283,14 +264,14 @@
 -- So, for example, to print a bunch of chunks to standard output
 -- using 256 colors:
 --
--- > {-# LANGUAGE OverloadedStrings #-}
 -- > module PrintMyChunks where
 -- >
 -- > import qualified Data.ByteString as BS
 -- > import Rainbow
 -- >
--- > myChunks :: [Chunk]
--- > myChunks = [ "Roses" <> fore red, "\n", "Violets" <> fore blue, "\n" ]
+-- > myChunks :: [Chunk String]
+-- > myChunks = [ chunk "Roses" & fore red, chunk "\n",
+-- >              chunk "Violets" & fore blue, chunk "\n" ]
 -- >
 -- > myPrintedChunks :: IO ()
 -- > myPrintedChunks = mapM_ BS.putStr
@@ -314,25 +295,61 @@
   -> [ByteString]
 chunksToByteStrings mk = ($ []) . foldr (.) id . map mk
 
--- Quick and dirty I/O functions
+-- | Writes a list of chunks to the given 'IO.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 'IO.Handle'.
+hPutChunks :: IO.Handle -> [T.Chunk] -> IO ()
+hPutChunks h cks = do
+  maker <- byteStringMakerFromEnvironment
+  let bsList = chunksToByteStrings maker cks
+  mapM_ (BS.hPut h) bsList
 
--- | Writes a 'Chunk' to standard output.  Spawns a child process to
--- read the output of @tput colors@ to determine how many colors to
--- use, for every single chunk.  Therefore, this is not going to win
--- any speed awards.  You are better off using 'chunksToByteStrings'
--- and the functions in "Data.ByteString" to print your 'Chunk's if
--- you are printing a lot of them.
+-- | Writes a list of chunks to the given 'IO.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 'IO.Handle'.
+hPutChunksLn :: IO.Handle -> [T.Chunk] -> IO ()
+hPutChunksLn h cks = do
+  hPutChunks h cks
+  IO.hPutStr h "\n"
+
+-- | 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.
+putChunks :: [T.Chunk] -> IO ()
+putChunks = hPutChunks IO.stdout
+
+-- | 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.
+putChunksLn :: [T.Chunk] -> IO ()
+putChunksLn cks = do
+  putChunks cks
+  IO.putStr "\n"
+
+-- | Writes a 'T.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 'T.Chunk's if you are printing a lot of them.
 putChunk :: T.Chunk -> IO ()
 putChunk ck = do
   mkr <- byteStringMakerFromEnvironment
   mapM_ BS.putStr . chunksToByteStrings mkr $ [ck]
 
--- | Writes a 'Chunk' to standard output, and appends a newline.
--- Spawns a child process to read the output of @tput colors@ to
--- determine how many colors to use, for every single chunk.
--- Therefore, this is not going to win any speed awards.  You are
--- better off using 'chunksToByteStrings' and the functions in
--- "Data.ByteString" to print your 'Chunk's if you are printing a lot
--- of them.
+-- | Writes a 'T.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 'T.Chunk's if you are printing a lot of them.
 putChunkLn :: T.Chunk -> IO ()
 putChunkLn ck = putChunk ck >> putStrLn ""
diff --git a/lib/Rainbow/Types.hs b/lib/Rainbow/Types.hs
--- a/lib/Rainbow/Types.hs
+++ b/lib/Rainbow/Types.hs
@@ -1,36 +1,46 @@
-{-# LANGUAGE DeriveGeneric, DeriveDataTypeable #-}
-
--- | The innards of Rainbow.  Ordinarily you should not need this
--- module; instead, just import "Rainbow", which
--- re-exports the most useful names from this module.
+{-# LANGUAGE DeriveGeneric, DeriveDataTypeable, DeriveFunctor,
+             DeriveTraversable, DeriveFoldable, TemplateHaskell #-}
 
+-- | All the main types in Rainbow.  Using this module you can specify
+-- that you want different formatting for 8- and 256-color terminals.
+-- Many of the names in this module conflict with the names in
+-- "Rainbow", so it's probably best to @import@ this module
+-- @qualified@.
 module Rainbow.Types where
 
 -- # Imports
 
-import qualified Data.String as Str
-import Data.Monoid
+import Control.Lens (makeLenses)
+import Data.String (IsString(..))
 import Data.Text (Text)
 import qualified Data.Text as X
-import qualified Data.Text.Lazy as XL
+import Data.Traversable ()
+import Data.Typeable (Typeable)
 import Data.Word (Word8)
-import GHC.Generics
-import Data.Typeable
-
--- For Background8, Background256, Foreground8, Foreground256: the
--- Last wraps a Maybe (Terminfo Color). If the inner Maybe is Nothing,
--- use the default color.
-
-type Background8 = Last Color8
-type Background256 = Last Color256
-type Foreground8 = Last Color8
-type Foreground256 = Last Color256
+import GHC.Generics (Generic)
 
 --
 -- Colors
 --
 
--- | A simple enumeration for eight values.
+-- | A color; a 'Nothing' value means that the terminal's default
+-- color is used.  The type of the 'Maybe' generally will be an
+-- 'Enum8' to represent one of 8 colors, or a 'Word8' to represent one
+-- of 256 colors.
+newtype Color a = Color (Maybe a)
+  deriving (Eq, Show, Ord, Generic, Typeable, Functor, Foldable,
+            Traversable)
+
+instance Semigroup (Color a) where
+  Color x <> Color y = case y of
+    Just a -> Color (Just a)
+    _ -> Color x
+
+-- | Takes the last non-Nothing Color.  'mempty' is no color.
+instance Monoid (Color a) where
+  mempty = Color Nothing
+
+-- | A simple enumeration for eight values.  Represents eight colors.
 data Enum8
   = E0
   | E1
@@ -53,104 +63,120 @@
   E6 -> 6
   E7 -> 7
 
--- | Color for an 8-color terminal.  Does not affect 256-color
--- terminals.
+black :: Enum8
+black = E0
 
-newtype Color8 = Color8
-  { unColor8 :: Maybe Enum8
-  -- ^ Nothing indicates to use the default color for the terminal;
-  -- otherwise, use the corresponding Terminfo 'T.Color'.
-  } deriving (Eq, Ord, Show, Generic, Typeable)
+red :: Enum8
+red = E1
 
--- | Color for an 256-color terminal.  Does not affect 8-color
--- terminals.
+green :: Enum8
+green = E2
 
-newtype Color256 = Color256
-  { unColor256 :: Maybe Word8
-  -- ^ Nothing indicates to use the default color for the terminal;
-  -- otherwise, use the corresponding Terminfo 'T.Color'.
-  } deriving (Eq, Ord, Show, Generic, Typeable)
+yellow :: Enum8
+yellow = E3
 
--- | Any color for an 8-color terminal can also be used in a
--- 256-color terminal.
-to256 :: Color8 -> Color256
-to256 (Color8 mayE) = Color256 $ fmap enum8toWord8 mayE
+blue :: Enum8
+blue = E4
 
+magenta :: Enum8
+magenta = E5
+
+cyan :: Enum8
+cyan = E6
+
+white :: Enum8
+white = E7
+
+grey :: Word8
+grey = 8
+
+brightRed :: Word8
+brightRed = 9
+
+brightGreen :: Word8
+brightGreen = 10
+
+brightYellow :: Word8
+brightYellow = 11
+
+brightBlue :: Word8
+brightBlue = 12
+
+brightMagenta :: Word8
+brightMagenta = 13
+
+brightCyan :: Word8
+brightCyan = 14
+
+brightWhite :: Word8
+brightWhite = 15
+
 --
 -- Styles
 --
 
--- | Style elements that apply in both 8 and 256 color
--- terminals. However, the elements are described separately for 8 and
--- 256 color terminals, so that the text appearance can change
--- depending on how many colors a terminal has.
-data StyleCommon = StyleCommon
-  { scBold :: Last Bool
-  , scFaint :: Last Bool
-  , scItalic :: Last Bool
-  , scUnderline :: Last Bool
-  , scBlink :: Last Bool
-  , scInverse :: Last Bool
-  , scInvisible :: Last Bool
-  , scStrikeout :: Last Bool
+-- | Text formatting such as bold, italic, etc.
+data Format = Format
+  { _bold :: Bool
+  , _faint :: Bool
+  , _italic :: Bool
+  , _underline :: Bool
+  , _blink :: Bool
+  , _inverse :: Bool
+  , _invisible :: Bool
+  , _strikeout :: Bool
   } deriving (Show, Eq, Ord, Generic, Typeable)
 
-
-instance Monoid StyleCommon where
-  mempty = StyleCommon (Last Nothing) (Last Nothing)
-                       (Last Nothing) (Last Nothing)
-                       (Last Nothing) (Last Nothing)
-                       (Last Nothing) (Last Nothing)
-  mappend (StyleCommon x1 x2 x3 x4 x5 x6 x7 x8)
-          (StyleCommon y1 y2 y3 y4 y5 y6 y7 y8)
-    = StyleCommon (x1 <> y1) (x2 <> y2) (x3 <> y3) (x4 <> y4)
-                  (x5 <> y5) (x6 <> y6) (x7 <> y7) (x8 <> y8)
+makeLenses ''Format
 
--- | Describes text appearance (foreground and background colors, as
--- well as other attributes such as bold) for an 8 color terminal.
-data Style8 = Style8
-  { foreground8 :: Foreground8
-  , background8 :: Background8
-  , common8 :: StyleCommon
-  } deriving (Show, Eq, Ord, Generic, Typeable)
+instance Semigroup Format where
+  (Format x0 x1 x2 x3 x4 x5 x6 x7) <> (Format y0 y1 y2 y3 y4 y5 y6 y7)
+    = Format (x0 || y0) (x1 || y1) (x2 || y2) (x3 || y3) (x4 || y4)
+             (x5 || y5) (x6 || y6) (x7 || y7)
 
+-- | For each field, the resulting field is True if either field is
+-- True.  For 'mempty', every field is False.
+instance Monoid Format where
+  mempty = Format False False False False False False False False
 
-instance Monoid Style8 where
-  mappend (Style8 fx bx cx) (Style8 fy by cy)
-    = Style8 (fx <> fy) (bx <> by) (cx <> cy)
-  mempty = Style8 mempty mempty mempty
+-- | The foreground and background color, and the 'Format'.  This
+-- represents all colors and formatting attributes for either an 8- or
+-- 256-color terminal.
+data Style a = Style
+  { _fore :: Color a
+  , _back :: Color a
+  , _format :: Format
+  } deriving (Show, Eq, Ord, Generic, Typeable, Functor, Foldable,
+              Traversable)
 
--- | Describes text appearance (foreground and background colors, as
--- well as other attributes such as bold) for a 256 color terminal.
-data Style256 = Style256
-  { foreground256 :: Foreground256
-  , background256 :: Background256
-  , common256 :: StyleCommon
-  } deriving (Show, Eq, Ord, Generic, Typeable)
+makeLenses ''Style
 
+instance Semigroup (Style a) where
+  (Style x0 x1 x2) <> (Style y0 y1 y2)
+    = Style (x0 <> y0) (x1 <> y1) (x2 <> y2)
 
-instance Monoid Style256 where
-  mappend (Style256 fx bx cx) (Style256 fy by cy)
-    = Style256 (fx <> fy) (bx <> by) (cx <> cy)
-  mempty = Style256 mempty mempty mempty
+-- | Uses the underlying 'Monoid' instances for 'Color' and 'Format'.
+instance Monoid (Style a) where
+  mempty = Style mempty mempty mempty
 
 --
--- TextSpec
+-- Scheme
 --
 
--- | The TextSpec bundles together the styles for the 8 and 256 color
--- terminals, so that the text can be portrayed on any terminal.
-data TextSpec = TextSpec
-  { style8 :: Style8
-  , style256 :: Style256
-  } deriving (Show, Eq, Ord, Generic, Typeable)
+-- | Holds the 'Style' for both 8- and 256-color terminals.
+data Scheme = Scheme
+  { _style8 :: Style Enum8
+  , _style256 :: Style Word8
+  } deriving (Eq, Ord, Show, Generic, Typeable)
 
+makeLenses ''Scheme
 
-instance Monoid TextSpec where
-  mappend (TextSpec x1 x2) (TextSpec y1 y2)
-    = TextSpec (x1 <> y1) (x2 <> y2)
-  mempty = TextSpec mempty mempty
+instance Semigroup Scheme where
+  (Scheme x0 x1) <> (Scheme y0 y1) = Scheme (x0 <> y0) (x1 <> y1)
 
+instance Monoid Scheme where
+  mempty = Scheme mempty mempty
+
 --
 -- Chunks
 --
@@ -162,421 +188,52 @@
 -- a 256 color terminal.
 
 data Chunk = Chunk
-  { chunkTextSpec :: TextSpec
-    -- ^ Specifies all the effects (such as bold, underlining,
-    -- colors, etc) that apply to this chunk, with different effects for
-    -- 8 and 256 color terminals; and
-    --
-  , chunkTexts :: [Text]
-    -- The text that is in this chunk.  When printing the
-    -- 'Chunk', first all colors and effects on the terminal are reset.
-    -- Then, the effects in the 'TextSpec' are applied, and then the
-    -- 'Text's are printed by encoding them to UTF-8.  Then, all the
-    -- colors and effects on the terminal are again reset.
-    --
-    -- Each text in this list is a strict 'X.Text'; though there is no
-    -- provision for lazy 'X.Text', you can get the same effect as a lazy
-    -- 'X.Text' by using a list of strict 'X.Text'.  'chunkFromLazyText'
-    -- 'Text' by using a list of strict 'and 'chunkFromLazyTexts' do
-    -- 'Text' by using a list of strict 'this for you.
+  { _scheme :: Scheme
+  , _yarn :: Text
   } deriving (Eq, Show, Ord, Generic, Typeable)
-  
 
-instance Str.IsString Chunk where
-  fromString s = Chunk mempty [(X.pack s)]
-
--- | Creates a 'Chunk' from a strict 'X.Text' with default colors
--- and no special effects.
-chunkFromText :: X.Text -> Chunk
-chunkFromText = Chunk mempty . (:[])
-
--- | Creates a 'Chunk' from a list of strict 'X.Text' with default
--- colors and no special effects.
-chunkFromTexts :: [X.Text] -> Chunk
-chunkFromTexts = Chunk mempty
-
--- | Creates a 'Chunk' from a lazy 'XL.Text' with default colors and
--- no special effects.
-chunkFromLazyText :: XL.Text -> Chunk
-chunkFromLazyText = Chunk mempty . XL.toChunks
+-- | Uses the underlying 'Semigroup' instances for both the
+-- underlying 'Scheme' and the underlying 'Text'.
+instance Semigroup Chunk where
+  (Chunk x0 x1) <> (Chunk y0 y1)
+    = Chunk (x0 <> y0) (x1 <> y1)
 
--- | Creates a 'Chunk' from a list of lazy 'XL.Text' with default
--- colors and no special effects.
-chunkFromLazyTexts :: [XL.Text] -> Chunk
-chunkFromLazyTexts = Chunk mempty . concatMap XL.toChunks
+-- | Creates a 'Chunk' with no formatting and with the given text.
+instance IsString Chunk where
+  fromString = chunk . X.pack
 
+-- | 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 Monoid Chunk where
   mempty = Chunk mempty mempty
-  mappend (Chunk s1 t1) (Chunk s2 t2) = Chunk (s1 <> s2) (t1 <> t2)
 
-
--- 8-color effects
-
-bold8 :: Chunk
-bold8 = x {
-  chunkTextSpec = (chunkTextSpec x) {
-    style8 = (style8 (chunkTextSpec x)) {
-      common8 = (common8 (style8 (chunkTextSpec x))) {
-        scBold = Last (Just True) }}}}
-  where
-    x = mempty
-
-bold8off :: Chunk
-bold8off = x {
-  chunkTextSpec = (chunkTextSpec x) {
-    style8 = (style8 (chunkTextSpec x)) {
-      common8 = (common8 (style8 (chunkTextSpec x))) {
-        scBold = Last (Just False) }}}}
-  where
-    x = mempty
-
-faint8 :: Chunk
-faint8 = x {
-  chunkTextSpec = (chunkTextSpec x) {
-    style8 = (style8 (chunkTextSpec x)) {
-      common8 = (common8 (style8 (chunkTextSpec x))) {
-        scFaint = Last (Just True) }}}}
-  where
-    x = mempty
-
-faint8off :: Chunk
-faint8off = x {
-  chunkTextSpec = (chunkTextSpec x) {
-    style8 = (style8 (chunkTextSpec x)) {
-      common8 = (common8 (style8 (chunkTextSpec x))) {
-        scFaint = Last (Just False) }}}}
-  where
-    x = mempty
-
-italic8 :: Chunk
-italic8 = x {
-  chunkTextSpec = (chunkTextSpec x) {
-    style8 = (style8 (chunkTextSpec x)) {
-      common8 = (common8 (style8 (chunkTextSpec x))) {
-        scItalic = Last (Just True) }}}}
-  where
-    x = mempty
-
-italic8off :: Chunk
-italic8off = x {
-  chunkTextSpec = (chunkTextSpec x) {
-    style8 = (style8 (chunkTextSpec x)) {
-      common8 = (common8 (style8 (chunkTextSpec x))) {
-        scItalic = Last (Just False) }}}}
-  where
-    x = mempty
-
-
-underline8 :: Chunk
-underline8 = x {
-  chunkTextSpec = (chunkTextSpec x) {
-    style8 = (style8 (chunkTextSpec x)) {
-      common8 = (common8 (style8 (chunkTextSpec x))) {
-        scUnderline = Last (Just True) }}}}
-  where
-    x = mempty
-
-
-underline8off :: Chunk
-underline8off = x {
-  chunkTextSpec = (chunkTextSpec x) {
-    style8 = (style8 (chunkTextSpec x)) {
-      common8 = (common8 (style8 (chunkTextSpec x))) {
-        scUnderline = Last (Just False) }}}}
-  where
-    x = mempty
-
-blink8 :: Chunk
-blink8 = x {
-  chunkTextSpec = (chunkTextSpec x) {
-    style8 = (style8 (chunkTextSpec x)) {
-      common8 = (common8 (style8 (chunkTextSpec x))) {
-        scBlink = Last (Just True) }}}}
-  where
-    x = mempty
-
-blink8off :: Chunk
-blink8off = x {
-  chunkTextSpec = (chunkTextSpec x) {
-    style8 = (style8 (chunkTextSpec x)) {
-      common8 = (common8 (style8 (chunkTextSpec x))) {
-        scBlink = Last (Just False) }}}}
-  where
-    x = mempty
-
-
-inverse8 :: Chunk
-inverse8 = x {
-  chunkTextSpec = (chunkTextSpec x) {
-    style8 = (style8 (chunkTextSpec x)) {
-      common8 = (common8 (style8 (chunkTextSpec x))) {
-        scInverse = Last (Just True) }}}}
-  where
-    x = mempty
-
-inverse8off :: Chunk
-inverse8off = x {
-  chunkTextSpec = (chunkTextSpec x) {
-    style8 = (style8 (chunkTextSpec x)) {
-      common8 = (common8 (style8 (chunkTextSpec x))) {
-        scInverse = Last (Just False) }}}}
-  where
-    x = mempty
-
-
-invisible8 :: Chunk
-invisible8 = x {
-  chunkTextSpec = (chunkTextSpec x) {
-    style8 = (style8 (chunkTextSpec x)) {
-      common8 = (common8 (style8 (chunkTextSpec x))) {
-        scInvisible = Last (Just True) }}}}
-  where
-    x = mempty
-
-invisible8off :: Chunk
-invisible8off = x {
-  chunkTextSpec = (chunkTextSpec x) {
-    style8 = (style8 (chunkTextSpec x)) {
-      common8 = (common8 (style8 (chunkTextSpec x))) {
-        scInvisible = Last (Just False) }}}}
-  where
-    x = mempty
-
-
-strikeout8 :: Chunk
-strikeout8 = x {
-  chunkTextSpec = (chunkTextSpec x) {
-    style8 = (style8 (chunkTextSpec x)) {
-      common8 = (common8 (style8 (chunkTextSpec x))) {
-        scStrikeout = Last (Just True) }}}}
-  where
-    x = mempty
-
-strikeout8off :: Chunk
-strikeout8off = x {
-  chunkTextSpec = (chunkTextSpec x) {
-    style8 = (style8 (chunkTextSpec x)) {
-      common8 = (common8 (style8 (chunkTextSpec x))) {
-        scStrikeout = Last (Just False) }}}}
-  where
-    x = mempty
-
-
--- 256 color effects
-
-bold256 :: Chunk
-bold256 = x {
-  chunkTextSpec = (chunkTextSpec x) {
-    style256 = (style256 (chunkTextSpec x)) {
-      common256 = (common256 (style256 (chunkTextSpec x))) {
-        scBold = Last (Just True) }}}}
-  where
-    x = mempty
-
-bold256off :: Chunk
-bold256off = x {
-  chunkTextSpec = (chunkTextSpec x) {
-    style256 = (style256 (chunkTextSpec x)) {
-      common256 = (common256 (style256 (chunkTextSpec x))) {
-        scBold = Last (Just False) }}}}
-  where
-    x = mempty
-
-
-faint256 :: Chunk
-faint256 = x {
-  chunkTextSpec = (chunkTextSpec x) {
-    style256 = (style256 (chunkTextSpec x)) {
-      common256 = (common256 (style256 (chunkTextSpec x))) {
-        scFaint = Last (Just True) }}}}
-  where
-    x = mempty
-
-faint256off :: Chunk
-faint256off = x {
-  chunkTextSpec = (chunkTextSpec x) {
-    style256 = (style256 (chunkTextSpec x)) {
-      common256 = (common256 (style256 (chunkTextSpec x))) {
-        scFaint = Last (Just False) }}}}
-  where
-    x = mempty
-
-
-italic256 :: Chunk
-italic256 = x {
-  chunkTextSpec = (chunkTextSpec x) {
-    style256 = (style256 (chunkTextSpec x)) {
-      common256 = (common256 (style256 (chunkTextSpec x))) {
-        scItalic = Last (Just True) }}}}
-  where
-    x = mempty
-
-italic256off :: Chunk
-italic256off = x {
-  chunkTextSpec = (chunkTextSpec x) {
-    style256 = (style256 (chunkTextSpec x)) {
-      common256 = (common256 (style256 (chunkTextSpec x))) {
-        scItalic = Last (Just False) }}}}
-  where
-    x = mempty
-
-
-underline256 :: Chunk
-underline256 = x {
-  chunkTextSpec = (chunkTextSpec x) {
-    style256 = (style256 (chunkTextSpec x)) {
-      common256 = (common256 (style256 (chunkTextSpec x))) {
-        scUnderline = Last (Just True) }}}}
-  where
-    x = mempty
-
-
-underline256off :: Chunk
-underline256off = x {
-  chunkTextSpec = (chunkTextSpec x) {
-    style256 = (style256 (chunkTextSpec x)) {
-      common256 = (common256 (style256 (chunkTextSpec x))) {
-        scUnderline = Last (Just False) }}}}
-  where
-    x = mempty
-
-blink256 :: Chunk
-blink256 = x {
-  chunkTextSpec = (chunkTextSpec x) {
-    style256 = (style256 (chunkTextSpec x)) {
-      common256 = (common256 (style256 (chunkTextSpec x))) {
-        scBlink = Last (Just True) }}}}
-  where
-    x = mempty
-
-
-blink256off :: Chunk
-blink256off = x {
-  chunkTextSpec = (chunkTextSpec x) {
-    style256 = (style256 (chunkTextSpec x)) {
-      common256 = (common256 (style256 (chunkTextSpec x))) {
-        scBlink = Last (Just False) }}}}
-  where
-    x = mempty
-
-
-inverse256 :: Chunk
-inverse256 = x {
-  chunkTextSpec = (chunkTextSpec x) {
-    style256 = (style256 (chunkTextSpec x)) {
-      common256 = (common256 (style256 (chunkTextSpec x))) {
-        scInverse = Last (Just True) }}}}
-  where
-    x = mempty
-
-inverse256off :: Chunk
-inverse256off = x {
-  chunkTextSpec = (chunkTextSpec x) {
-    style256 = (style256 (chunkTextSpec x)) {
-      common256 = (common256 (style256 (chunkTextSpec x))) {
-        scInverse = Last (Just False) }}}}
-  where
-    x = mempty
-
-
-invisible256 :: Chunk
-invisible256 = x {
-  chunkTextSpec = (chunkTextSpec x) {
-    style256 = (style256 (chunkTextSpec x)) {
-      common256 = (common256 (style256 (chunkTextSpec x))) {
-        scInvisible = Last (Just True) }}}}
-  where
-    x = mempty
-
-invisible256off :: Chunk
-invisible256off = x {
-  chunkTextSpec = (chunkTextSpec x) {
-    style256 = (style256 (chunkTextSpec x)) {
-      common256 = (common256 (style256 (chunkTextSpec x))) {
-        scInvisible = Last (Just False) }}}}
-  where
-    x = mempty
-
-
-strikeout256 :: Chunk
-strikeout256 = x {
-  chunkTextSpec = (chunkTextSpec x) {
-    style256 = (style256 (chunkTextSpec x)) {
-      common256 = (common256 (style256 (chunkTextSpec x))) {
-        scStrikeout = Last (Just True) }}}}
-  where
-    x = mempty
-
-strikeout256off :: Chunk
-strikeout256off = x {
-  chunkTextSpec = (chunkTextSpec x) {
-    style256 = (style256 (chunkTextSpec x)) {
-      common256 = (common256 (style256 (chunkTextSpec x))) {
-        scStrikeout = Last (Just False) }}}}
-  where
-    x = mempty
-
-
+-- | Creates a 'Chunk' with no formatting and with the given text.
+-- A 'Chunk' is also an instance of 'Data.String.IsString' so you
+-- can create them with the @OverloadedStrings@ extension.  Such a
+-- 'Chunk' has the text of the string and no formatting.
+chunk :: Text -> Chunk
+chunk = Chunk mempty
 
---
--- All
---
+makeLenses ''Chunk
 
 
--- | 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.
-bold :: Chunk
-bold = bold8 <> bold256
-
-boldOff :: Chunk
-boldOff = bold8off <> bold256off
-
-faint :: Chunk
-faint = faint8 <> faint256
-
-faintOff :: Chunk
-faintOff = faint8off <> faint256off
-
-italic :: Chunk
-italic = italic8 <> italic256
-
-italicOff :: Chunk
-italicOff = italic8off <> italic256off
-
-underline :: Chunk
-underline = underline8 <> underline256
-
-underlineOff :: Chunk
-underlineOff = underline8off <> underline256off
-
-blink :: Chunk
-blink = blink8 <> blink256
-
-blinkOff :: Chunk
-blinkOff = blink8off <> blink256off
-
-inverse :: Chunk
-inverse = inverse8 <> inverse256
-
-inverseOff :: Chunk
-inverseOff = inverse8off <> inverse256off
-
-invisible :: Chunk
-invisible = invisible8 <> invisible256
+-- | Stores colors that may affect 8-color terminals, 256-color
+-- terminals, both, or neither.
+data Radiant = Radiant
+  { _color8 :: Color Enum8
+  , _color256 :: Color Word8
+  } deriving (Eq, Ord, Show, Typeable, Generic)
 
-invisibleOff :: Chunk
-invisibleOff = invisible8off <> invisible256off
+instance Semigroup Radiant where
+  (Radiant x0 x1) <> (Radiant y0 y1) = Radiant (x0 <> y0) (x1 <> y1)
 
-strikeout :: Chunk
-strikeout = strikeout8 <> strikeout256
+-- | Uses the underlying 'Monoid' instance for the 'Color's.  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 Monoid Radiant where
+  mempty = Radiant mempty mempty
 
-strikeoutOff :: Chunk
-strikeoutOff = strikeout8off <> strikeout256off
+makeLenses ''Radiant
 
diff --git a/rainbow.cabal b/rainbow.cabal
--- a/rainbow.cabal
+++ b/rainbow.cabal
@@ -1,152 +1,95 @@
--- This Cabal file generated using the Cartel library.
--- Cartel is available at:
--- http://www.github.com/massysett/cartel
---
--- Script name used to generate: genCabal.hs
--- Generated on: 2015-03-27 14:33:18.544254 EDT
--- Cartel library version: 0.14.2.0
+cabal-version: 1.12
 
-name: rainbow
-version: 0.22.0.2
-cabal-version: >= 1.18
-license: BSD3
-license-file: LICENSE
-build-type: Simple
-copyright: Copyright 2013-2015 Omari Norman
-author: Omari Norman
-maintainer: omari@smileystation.com
-stability: Experimental
-homepage: https://www.github.com/massysett/rainbow
-bug-reports: https://www.github.com/massysett/rainbow/issues
-synopsis: Print text to terminal with colors and effects
-description:
-  rainbow helps you print Text chunks to a terminal with colors and effects
-  such as bold, underlining, etc. You pair each Text with a description
-  of how it should appear. Rainbow works with both 8-color and 256-color
-  terminals.
-category: System
-tested-with:
-  GHC == 7.8.2
-  GHC == 7.10.1
+-- This file has been generated from package.yaml by hpack version 0.31.2.
+--
+-- see: https://github.com/sol/hpack
+--
+-- hash: 837a60ee2540f28dc14f871885d041b7f60038586de137f1f3e495154992b4fe
 
-Library
-  exposed-modules:
-    Rainbow
-    Rainbow.Colors
-    Rainbow.Translate
-    Rainbow.Types
-  default-language: Haskell2010
-  ghc-options:
-    -Wall
-  build-depends:
-      base >= 4.7.0.0 && < 4.9.0.0
-    , text >= 0.11.2.0 && < 1.3.0.0
-    , bytestring >= 0.10 && < 0.11
-    , process >= 1.2 && < 1.3
-  hs-source-dirs:
-    lib
+name:           rainbow
+version:        0.34.2.2
+synopsis:       Print text to terminal with colors and effects
+description:    Please see README.md
+category:       System
+stability:      Experimental
+homepage:       https://www.github.com/massysett/rainbow
+bug-reports:    https://www.github.com/massysett/rainbow/issues
+author:         Omari Norman
+maintainer:     omari@smileystation.com
+copyright:      Copyright 2013-2019 Omari Norman
+license:        BSD3
+license-file:   LICENSE
+build-type:     Simple
+extra-source-files:
+    README.md
+    stack.yaml
+x-curation:     uncurated
 
 source-repository head
   type: git
-  location: https://github.com/massysett/rainbow.git
+  location: https://github.com/massysett/rainbow
 
-Test-Suite rainbow-instances
-  type: exitcode-stdio-1.0
-  main-is: rainbow-instances.hs
+library
+  exposed-modules:
+      Rainbow
+      Rainbow.Translate
+      Rainbow.Types
   other-modules:
-    Rainbow.QuickCheck
-    Rainbow
-    Rainbow.Colors
-    Rainbow.Translate
-    Rainbow.Types
+      Paths_rainbow
   hs-source-dirs:
-    tests
+      lib
+  other-extensions: TemplateHaskell
+  ghc-options: -Wall
   build-depends:
-      QuickCheck >= 2.7 && < 2.9
+      base >=4.11 && <5
+    , bytestring
+    , lens
+    , terminfo
+    , text
   default-language: Haskell2010
-  ghc-options:
-    -Wall
-  build-depends:
-      base >= 4.7.0.0 && < 4.9.0.0
-    , text >= 0.11.2.0 && < 1.3.0.0
-    , bytestring >= 0.10 && < 0.11
-    , process >= 1.2 && < 1.3
-  hs-source-dirs:
-    lib
 
-Executable test8color
-  main-is: test8color.hs
-  if flag(visual)
-    buildable: True
-    other-modules:
+test-suite rainbow-instances
+  type: exitcode-stdio-1.0
+  main-is: rainbow-instances.hs
+  other-modules:
       Rainbow
-      Rainbow.Colors
       Rainbow.Translate
       Rainbow.Types
-    hs-source-dirs:
-      tests
-    default-language: Haskell2010
-    ghc-options:
-      -Wall
-    build-depends:
-        base >= 4.7.0.0 && < 4.9.0.0
-      , text >= 0.11.2.0 && < 1.3.0.0
-      , bytestring >= 0.10 && < 0.11
-      , process >= 1.2 && < 1.3
-    hs-source-dirs:
+      Rainbow.QuickCheck
+      Paths_rainbow
+  hs-source-dirs:
       lib
-  else
-    buildable: False
-
-Executable test256color
-  main-is: test256color.hs
-  if flag(visual)
-    buildable: True
-    other-modules:
-      Rainbow
-      Rainbow.Colors
-      Rainbow.Translate
-      Rainbow.Types
-    hs-source-dirs:
       tests
-    default-language: Haskell2010
-    ghc-options:
-      -Wall
-    build-depends:
-        base >= 4.7.0.0 && < 4.9.0.0
-      , text >= 0.11.2.0 && < 1.3.0.0
-      , bytestring >= 0.10 && < 0.11
-      , process >= 1.2 && < 1.3
-    hs-source-dirs:
-      lib
-  else
-    buildable: False
+  other-extensions: TemplateHaskell
+  ghc-options: -Wall
+  build-depends:
+      QuickCheck
+    , base >=4.11 && <5
+    , bytestring
+    , lens
+    , terminfo
+    , text
+  default-language: Haskell2010
 
-Executable colorTest
-  main-is: colorTest.hs
-  if flag(visual)
-    buildable: True
-    other-modules:
+test-suite rainbow-visual
+  type: exitcode-stdio-1.0
+  main-is: rainbow-visual.hs
+  other-modules:
       Rainbow
-      Rainbow.Colors
       Rainbow.Translate
       Rainbow.Types
-    hs-source-dirs:
-      tests
-    default-language: Haskell2010
-    ghc-options:
-      -Wall
-    build-depends:
-        base >= 4.7.0.0 && < 4.9.0.0
-      , text >= 0.11.2.0 && < 1.3.0.0
-      , bytestring >= 0.10 && < 0.11
-      , process >= 1.2 && < 1.3
-    hs-source-dirs:
+      Rainbow.QuickCheck
+      Paths_rainbow
+  hs-source-dirs:
       lib
-  else
-    buildable: False
-
-Flag visual
-  description: builds visual tests
-  default: False
-  manual: True
+      tests
+  other-extensions: TemplateHaskell
+  ghc-options: -Wall
+  build-depends:
+      QuickCheck
+    , base >=4.11 && <5
+    , bytestring
+    , lens
+    , terminfo
+    , text
+  default-language: Haskell2010
diff --git a/stack.yaml b/stack.yaml
new file mode 100644
--- /dev/null
+++ b/stack.yaml
@@ -0,0 +1,32 @@
+# For more information, see: https://github.com/commercialhaskell/stack/blob/release/doc/yaml_configuration.md
+
+# Specifies the GHC version and set of packages available (e.g., lts-3.5, nightly-2015-09-21, ghc-7.10.2)
+resolver: lts-14.14
+
+# Local packages, usually specified by relative directory name
+packages:
+- '.'
+
+# Packages to be pulled from upstream that are not in the resolver (e.g., acme-missiles-0.3)
+extra-deps: []
+
+# Override default flag values for local packages and extra-deps
+flags: {}
+
+# Extra package databases containing global packages
+extra-package-dbs: []
+
+# Control whether we use the GHC we find on the path
+# system-ghc: true
+
+# Require a specific version of stack, using version ranges
+# require-stack-version: -any # Default
+# require-stack-version: >= 0.1.4.0
+
+# Override the architecture used by stack, especially useful on Windows
+# arch: i386
+# arch: x86_64
+
+# Extra directories used by stack for building
+# extra-include-dirs: [/path/to/dir]
+# extra-lib-dirs: [/path/to/dir]
diff --git a/tests/Rainbow/QuickCheck.hs b/tests/Rainbow/QuickCheck.hs
--- a/tests/Rainbow/QuickCheck.hs
+++ b/tests/Rainbow/QuickCheck.hs
@@ -13,14 +13,27 @@
 
 module Rainbow.QuickCheck where
 
-import Control.Applicative
-import Test.QuickCheck
-import Rainbow.Colors
-import Rainbow.Types
-import Data.Monoid
-import Control.Monad
 import qualified Data.Text as X
+import Data.Typeable
+import Rainbow.Types
+import Test.QuickCheck
 
+instance Arbitrary X.Text where
+  arbitrary = fmap X.pack $ listOf genChar
+    where
+      genChar = elements ['a'..'z']
+  shrink = fmap X.pack . shrink . X.unpack
+
+instance CoArbitrary X.Text where
+  coarbitrary = coarbitrary . X.unpack
+
+instance (Typeable a, Arbitrary a) => Arbitrary (Color a) where
+  arbitrary = Color <$> arbitrary
+  shrink = genericShrink
+
+instance CoArbitrary a => CoArbitrary (Color a) where
+  coarbitrary (Color a) = coarbitrary a
+
 varInt :: Int -> Gen b -> Gen b
 varInt = variant
 
@@ -39,51 +52,15 @@
     E6 -> varInt 6
     E7 -> varInt 7
 
-instance Arbitrary Color8 where
-  arbitrary = fmap Color8 arbitrary
-  shrink = genericShrink
-
-instance CoArbitrary Color8 where
-  coarbitrary (Color8 Nothing) = varInt 0
-  coarbitrary (Color8 (Just e)) = varInt 1 . coarbitrary e
-
-instance Arbitrary Color256 where
-  arbitrary = fmap Color256 arbitrary
-  shrink = genericShrink
-
-instance CoArbitrary Color256 where
-  coarbitrary (Color256 Nothing) = varInt 0
-  coarbitrary (Color256 (Just w)) = varInt 1 . coarbitrary w
-
-instance Arbitrary (Last Color8) where
-  arbitrary = fmap Last arbitrary
-
-instance CoArbitrary (Last Color8) where
-  coarbitrary (Last Nothing) = varInt 0
-  coarbitrary (Last (Just c)) = varInt 1 . coarbitrary c
-
-instance Arbitrary (Last Color256) where
-  arbitrary = fmap Last arbitrary
-
-instance CoArbitrary (Last Color256) where
-  coarbitrary (Last Nothing) = varInt 0
-  coarbitrary (Last (Just c)) = varInt 1 . coarbitrary c
-
-instance Arbitrary (Last Bool) where
-  arbitrary = fmap Last arbitrary
-
-instance CoArbitrary (Last Bool) where
-  coarbitrary (Last Nothing) = varInt 0
-  coarbitrary (Last (Just b)) = varInt 1 . coarbitrary b
-
-instance Arbitrary StyleCommon where
+instance Arbitrary Format where
   arbitrary
-    = StyleCommon <$> g <*> g <*> g <*> g <*> g <*> g <*> g <*> g
+    = Format <$> g <*> g <*> g <*> g <*> g <*> g <*> g <*> g
     where
-      g = fmap Last arbitrary
+      g = arbitrary
+  shrink = genericShrink
 
-instance CoArbitrary StyleCommon where
-  coarbitrary (StyleCommon x0 x1 x2 x3 x4 x5 x6 x7)
+instance CoArbitrary Format where
+  coarbitrary (Format x0 x1 x2 x3 x4 x5 x6 x7)
     = coarbitrary x0
     . coarbitrary x1
     . coarbitrary x2
@@ -92,53 +69,38 @@
     . coarbitrary x5
     . coarbitrary x6
     . coarbitrary x7
-    
 
-instance Arbitrary Style256 where
-  arbitrary = liftM3 Style256 arbitrary arbitrary arbitrary
-
-instance CoArbitrary Style256 where
-  coarbitrary (Style256 x0 x1 x2)
-    = coarbitrary x0
-    . coarbitrary x1
-    . coarbitrary x2
-
-instance Arbitrary Style8 where
-  arbitrary = liftM3 Style8 arbitrary arbitrary arbitrary
+instance (Arbitrary a, Typeable a) => Arbitrary (Style a) where
+  arbitrary = Style <$> arbitrary <*> arbitrary <*> arbitrary
+  shrink = genericShrink
 
-instance CoArbitrary Style8 where
-  coarbitrary (Style8 x0 x1 x2)
-    = coarbitrary x0
-    . coarbitrary x1
-    . coarbitrary x2
+instance CoArbitrary a => CoArbitrary (Style a) where
+  coarbitrary (Style a b c)
+    = coarbitrary a
+    . coarbitrary b
+    . coarbitrary c
 
-instance Arbitrary TextSpec where
-  arbitrary = liftM2 TextSpec arbitrary arbitrary
+instance Arbitrary Scheme where
+  arbitrary = Scheme <$> arbitrary <*> arbitrary
   shrink = genericShrink
 
-instance CoArbitrary TextSpec where
-  coarbitrary (TextSpec x0 x1)
-    = coarbitrary x0
-    . coarbitrary x1
+instance CoArbitrary Scheme where
+  coarbitrary (Scheme a b) = coarbitrary a . coarbitrary b
 
-instance Arbitrary X.Text where
-  arbitrary = fmap X.pack arbitrary
-  shrink = map X.pack . shrink . X.unpack
 
 instance Arbitrary Chunk where
-  arbitrary = liftM2 Chunk arbitrary
-    (listOf (fmap X.pack (listOf (elements ['a'..'z']))))
+  arbitrary = Chunk <$> arbitrary <*> arbitrary
   shrink = genericShrink
 
 instance CoArbitrary Chunk where
-  coarbitrary (Chunk ts txts) = coarbitrary ts
-    . coarbitrary (map X.unpack txts)
+  coarbitrary (Chunk a b)
+    = coarbitrary a
+    . coarbitrary b
 
 instance Arbitrary Radiant where
-  arbitrary = liftM2 Radiant arbitrary arbitrary
+  arbitrary = Radiant <$> arbitrary <*> arbitrary
   shrink = genericShrink
 
 instance CoArbitrary Radiant where
-  coarbitrary (Radiant x0 x1)
-    = coarbitrary x0
-    . coarbitrary x1
+  coarbitrary (Radiant a b) = coarbitrary a . coarbitrary b
+
diff --git a/tests/colorTest.hs b/tests/colorTest.hs
deleted file mode 100644
--- a/tests/colorTest.hs
+++ /dev/null
@@ -1,75 +0,0 @@
-{-# LANGUAGE OverloadedStrings #-}
-module Main where
-
-import Rainbow
-import qualified Data.Text as X
-import qualified Data.ByteString as BS
-
-colors8 :: [(Text, Color8)]
-colors8 =
-  [ ("(no color)", noColor8)
-  , ("black", black8)
-  , ("red", red8)
-  , ("green", green8)
-  , ("yellow", yellow8)
-  , ("blue", blue8)
-  , ("magenta", magenta8)
-  , ("cyan", cyan8)
-  , ("white", white8)
-  ]
-
-colors256 :: [(Text, Color256)]
-colors256
-  = ("(no color)", Color256 Nothing) : map mkColor [minBound..maxBound]
-  where
-    mkColor w = (X.pack . show $ w, Color256 . Just $ w)
-
-colorChunks8ByForeground :: [Chunk]
-colorChunks8ByForeground = do
-  (fgColorName, fgColor) <- colors8
-  (bgColorName, bgColor) <- colors8
-  let lbl = "foreground " <> fgColorName <> " background " <> bgColorName
-  return $ chunkFromText lbl <> fore fgColor <> back bgColor <> "\n"
-
-colorChunks8ByBackground :: [Chunk]
-colorChunks8ByBackground = do
-  (bgColorName, bgColor) <- colors8
-  (fgColorName, fgColor) <- colors8
-  let lbl = "background " <> bgColorName <> " foreground " <> fgColorName
-  return $ chunkFromText lbl <> fore fgColor <> back bgColor <> "\n"
-
-colorChunks256ByForeground :: [Chunk]
-colorChunks256ByForeground = do
-  (fgColorName, fgColor) <- colors256
-  (bgColorName, bgColor) <- colors256
-  let lbl = "foreground " <> fgColorName <> " background " <> bgColorName
-  return $ chunkFromText lbl <> fore fgColor <> back bgColor <> "\n"
-
-colorChunks256ByBackground :: [Chunk]
-colorChunks256ByBackground = do
-  (bgColorName, bgColor) <- colors256
-  (fgColorName, fgColor) <- colors256
-  let lbl = "background " <> bgColorName <> " foreground " <> fgColorName
-  return $ chunkFromText lbl <> fore fgColor <> back bgColor <> "\n"
-
-sep :: String -> IO ()
-sep s = do
-  putStrLn ""
-  putStrLn ""
-  putStrLn $ replicate 40 '='
-  putStrLn s
-
-main :: IO ()
-main = do
-  sep "8 Colors - sorted by foreground color"
-  mapM_ BS.putStr . chunksToByteStrings toByteStringsColors8
-    $ colorChunks8ByForeground
-  sep "8 Colors - sorted by background color"
-  mapM_ BS.putStr . chunksToByteStrings toByteStringsColors8
-    $ colorChunks8ByBackground
-  sep "256 Colors - sorted by foreground color"
-  mapM_ BS.putStr . chunksToByteStrings toByteStringsColors256
-    $ colorChunks256ByForeground
-  sep "256 Colors - sorted by background color"
-  mapM_ BS.putStr . chunksToByteStrings toByteStringsColors256
-    $ colorChunks256ByBackground
diff --git a/tests/rainbow-visual.hs b/tests/rainbow-visual.hs
new file mode 100644
--- /dev/null
+++ b/tests/rainbow-visual.hs
@@ -0,0 +1,18 @@
+{-# LANGUAGE OverloadedStrings #-}
+module Main where
+
+import Rainbow
+import Data.Function ((&))
+
+main :: IO ()
+main = do
+  putChunksLn
+    [ "Red fore, green back " & fore red & back green
+    , "Blue fore, magenta back" & fore blue & back magenta ]
+  putChunksLn
+    [ "Red, bold fore " & fore red & bold
+    , "Red, underlined fore " & fore red & underline
+    , "Cyan, italic fore" & fore cyan & italic
+    ]
+  putChunksLn
+    [ "Light grey background on 256-color" & back (color256 254) ]
diff --git a/tests/test256color.hs b/tests/test256color.hs
deleted file mode 100644
--- a/tests/test256color.hs
+++ /dev/null
@@ -1,58 +0,0 @@
-{-# LANGUAGE OverloadedStrings #-}
-module Main where
-
-import Control.Arrow (second)
-import Rainbow
-import qualified Data.Text as X
-import qualified Data.ByteString as BS
-
-effects :: [(Text, Chunk)]
-effects =
-  [ ("bold", bold256)
-  , ("faint", faint256)
-  , ("italic", italic256)
-  , ("underline", underline256)
-  , ("blink", blink256)
-  , ("inverse", inverse256)
-  , ("invisible", invisible256)
-  , ("strikeout", strikeout256)
-  ]
-
-colors :: [(Text, Color256)]
-colors = ("(no color)", Color256 Nothing) : map mkColor [minBound..maxBound]
-  where
-    mkColor w = (X.pack . show $ w, Color256 . Just $ w)
-
-maybeEffects :: [(Text, Maybe Chunk)]
-maybeEffects = ("(no effect)", Nothing)
-  : map (second Just) effects
-
-{-
--- From
--- http://stackoverflow.com/a/22577148/1017252
-combinations :: Int -> [a] -> [[a]]
-combinations k xs = combinations' (length xs) k xs
-  where combinations' n k' l@(y:ys)
-          | k' == 0   = [[]]
-          | k' >= n   = [l]
-          | null l    = []
-          | otherwise = map (y :) (combinations' (n - 1) (k' - 1) ys)
-                            ++ combinations' (n - 1) k' ys 
--}
-
-colorsAndEffects :: [Chunk]
-colorsAndEffects = do
-  (fgColorName, fgColor) <- colors
-  (bgColorName, bgColor) <- colors
-  (effectName, mayEffect) <- maybeEffects
-  let lbl = "foreground " <> fgColorName <> " background " <> bgColorName
-          <> " effect " <> effectName
-  return $ chunkFromText lbl <>  fore fgColor
-         <>  back bgColor
-         <> maybe mempty id mayEffect
-         <> "\n"
-
-main :: IO ()
-main = do
-  mapM_ BS.putStr . chunksToByteStrings toByteStringsColors256
-    $ colorsAndEffects
diff --git a/tests/test8color.hs b/tests/test8color.hs
deleted file mode 100644
--- a/tests/test8color.hs
+++ /dev/null
@@ -1,65 +0,0 @@
-{-# LANGUAGE OverloadedStrings #-}
-module Main where
-
-import Control.Arrow (second)
-import Rainbow
-import qualified Data.ByteString as BS
-
-effects :: [(Text, Chunk)]
-effects =
-  [ ("bold", bold8)
-  , ("faint", faint8)
-  , ("italic", italic8)
-  , ("underline", underline8)
-  , ("blink", blink8)
-  , ("inverse", inverse8)
-  , ("invisible", invisible8)
-  , ("strikeout", strikeout8)
-  ]
-
-colors :: [(Text, Color8)]
-colors =
-  [ ("(no color)", noColor8)
-  , ("black", black8)
-  , ("red", red8)
-  , ("green", green8)
-  , ("yellow", yellow8)
-  , ("blue", blue8)
-  , ("magenta", magenta8)
-  , ("cyan", cyan8)
-  , ("white", white8)
-  ]
-
-maybeEffects :: [(Text, Maybe Chunk)]
-maybeEffects = ("(no effect)", Nothing)
-  : map (second Just) effects
-
-{-
--- From
--- http://stackoverflow.com/a/22577148/1017252
-combinations :: Int -> [a] -> [[a]]
-combinations k xs = combinations' (length xs) k xs
-  where combinations' n k' l@(y:ys)
-          | k' == 0   = [[]]
-          | k' >= n   = [l]
-          | null l    = []
-          | otherwise = map (y :) (combinations' (n - 1) (k' - 1) ys)
-                            ++ combinations' (n - 1) k' ys 
--}
-
-colorsAndEffects :: [Chunk]
-colorsAndEffects = do
-  (fgColorName, fgColor) <- colors
-  (bgColorName, bgColor) <- colors
-  (effectName, mayEffect) <- maybeEffects
-  let lbl = "foreground " <> fgColorName <> " background " <> bgColorName
-          <> " effect " <> effectName
-  return $ chunkFromText lbl <> fore fgColor
-         <> back bgColor
-         <> maybe mempty id mayEffect
-         <> chunkFromText "\n"
-
-main :: IO ()
-main = do
-  mapM_ BS.putStr . chunksToByteStrings toByteStringsColors8
-    $ colorsAndEffects
