packages feed

table-layout-1.0.0.2: src/Text/Layout/Table/Style.hs

-- | This module provides predefined styles, combinators to modify them,
-- abstract style descriptions, and combinators for quickly turning them into
-- styles.
--
-- The following resource may be useful for constructing your own primitive
-- styles: <https://en.wikipedia.org/wiki/Box-drawing_character>.

{-# LANGUAGE RecordWildCards #-}

module Text.Layout.Table.Style
    ( -- * Pre-Constructed Table Styles
      -- ** ASCII
      -- These styles use only ASCII characters.
      asciiS
    , asciiRoundS
    , asciiDoubleS

      -- ** Unicode
    , unicodeS
    , unicodeBoldHeaderS
    , unicodeRoundS
    , unicodeBoldS
    , unicodeBoldStripedS
    , unicodeDoubleFrameS

      -- * Combinators
    , withoutBorders
    , withoutTopBorder
    , withoutBottomBorder
    , withoutLeftBorder
    , withoutRightBorder
    , withRoundCorners
    , inheritStyle
    , inheritStyleHeaderGroup

      -- * Construct Table Styles from an Abstract Specification
    , asciiTableStyleFromSpec
    , roundedAsciiTableStyleFromSpec
    , unicodeTableStyleFromSpec
    , tableStyleFromSpec

      -- ** Construct an Abstract Specifiction
    , TableStyleSpec(..)
    , simpleTableStyleSpec
    , setTableStyleSpecSeparator

      -- * Low-Level Styling Facility
    , TableStyle(..)
    ) where

import Text.Layout.Table.LineStyle

-- | Specifies the different letters to construct the non-content structure of a
-- table.
--
-- This is quite low-level and difficult to construct by hand. If you want to
-- construct your own, you may wish to use the higher-level interface provided
-- by (in increasing order of detail):
--
--   1. 'simpleTableStyleSpec'
--   2. 'TableStyleSpec'
--   3. 'unicodeTableStyleFromSpec'
--   4. 'asciiTableStyleFromSpec'
--   5. 'tableStyleFromSpec'
data TableStyle rowSep colSep
    = TableStyle
    -- Within the column header but not the row header (11 cases)
    { headerSepH     :: String
    , headerSepLC    :: String
    , headerSepRC    :: String
    , headerSepC     :: colSep -> colSep -> String
    , headerTopH     :: String
    , headerTopL     :: String
    , headerTopR     :: String
    , headerTopC     :: colSep -> String
    , headerL        :: String
    , headerR        :: String
    , headerC        :: colSep -> String
    -- Within the row header but not the column header (11 cases)
    , rowHeaderSepV  :: String
    , rowHeaderSepTC :: String
    , rowHeaderSepBC :: String
    , rowHeaderSepC  :: rowSep -> rowSep -> String
    , rowHeaderLeftV :: String
    , rowHeaderLeftT :: String
    , rowHeaderLeftB :: String
    , rowHeaderLeftC :: rowSep -> String
    , rowHeaderT     :: String
    , rowHeaderB     :: String
    , rowHeaderC     :: rowSep -> String
    -- Within the intersection of the row and column headers (8 cases)
    , bothHeadersTL  :: String
    , bothHeadersTR  :: String
    , bothHeadersBL  :: String
    , bothHeadersBR  :: String
    , bothHeadersL   :: String
    , bothHeadersR   :: String
    , bothHeadersT   :: String
    , bothHeadersB   :: String
    -- Main body of the table, in neither the row or column headers (15 cases)
    , groupL         :: String
    , groupR         :: String
    , groupC         :: colSep -> String
    , groupSepH      :: rowSep -> String
    , groupSepC      :: rowSep -> colSep -> String
    , groupSepLC     :: rowSep -> String
    , groupSepRC     :: rowSep -> String
    , groupTopC      :: colSep -> String
    , groupTopL      :: String
    , groupTopR      :: String
    , groupTopH      :: String
    , groupBottomC   :: colSep -> String
    , groupBottomL   :: String
    , groupBottomR   :: String
    , groupBottomH   :: String
    }

-- | Inherit from a 'TableStyle' through a pair of functions.
inheritStyle :: (c -> a)        -- ^ The function to transform the row labels.
             -> (d -> b)        -- ^ The function to transform the column labels.
             -> TableStyle a b  -- ^ The 'TableStyle' to inherit from.
             -> TableStyle c d
inheritStyle f g = inheritStyleHeaderGroup f f g g

-- | Inherit from a 'TableStyle' using a triple of functions, specifying the
-- correspondence for row separators, column heading separators, and column separators.
inheritStyleHeaderGroup :: (c -> a)        -- ^ The function to transform the row labels in the header.
                        -> (c -> a)        -- ^ The function to transform the row labels in the body.
                        -> (d -> b)        -- ^ The function to transform the column labels in the header.
                        -> (d -> b)        -- ^ The function to transform the column labels in the body.
                        -> TableStyle a b  -- ^ The 'TableStyle' to inherit from.
                        -> TableStyle c d
inheritStyleHeaderGroup rowHead row colHead col ts =
    ts { headerSepC     = \a b -> headerSepC ts (colHead a) (col b)
       , headerTopC     = headerTopC     ts . colHead
       , headerC        = headerC        ts . colHead
       , rowHeaderSepC  = \a b -> rowHeaderSepC ts (rowHead a) (rowHead b)
       , rowHeaderLeftC = rowHeaderLeftC ts . rowHead
       , rowHeaderC     = rowHeaderC     ts . rowHead
       , groupC         = groupC         ts . col
       , groupSepH      = groupSepH      ts . row
       , groupSepC      = \a b -> groupSepC ts (row a) (col b)
       , groupSepLC     = groupSepLC     ts . row
       , groupSepRC     = groupSepRC     ts . row
       , groupTopC      = groupTopC      ts . col
       , groupBottomC   = groupBottomC   ts . col
       }

-- | Remove the top, bottom, left, and right borders from a 'TableStyle'.
withoutBorders :: TableStyle a b -> TableStyle a b
withoutBorders = withoutTopBorder . withoutBottomBorder . withoutLeftBorder . withoutRightBorder

-- | Remove the top border from a 'TableStyle'.
withoutTopBorder :: TableStyle a b -> TableStyle a b
withoutTopBorder ts = ts { headerTopH = "", headerTopL = "", headerTopR = "", headerTopC = const ""
                         , rowHeaderLeftT = "", rowHeaderT = "", rowHeaderSepTC = ""
                         , bothHeadersTL = "", bothHeadersTR = "", bothHeadersT = ""
                         , groupTopC = const "", groupTopL = "", groupTopR = "", groupTopH = ""
                         }

-- | Remove the bottom border from a 'TableStyle'.
withoutBottomBorder :: TableStyle a b -> TableStyle a b
withoutBottomBorder ts = ts { rowHeaderLeftB = "", rowHeaderB = "", rowHeaderSepBC = ""
                            , groupBottomC = const "", groupBottomL = "", groupBottomR = "", groupBottomH = "" }

-- | Remove the left border from a 'TableStyle'.
withoutLeftBorder :: TableStyle a b -> TableStyle a b
withoutLeftBorder ts = ts { headerSepLC = "", headerTopL = "", headerL = ""
                          , rowHeaderLeftV = "", rowHeaderLeftT = "", rowHeaderLeftB = "", rowHeaderLeftC = const ""
                          , bothHeadersTL = "", bothHeadersBL = "", bothHeadersL = ""
                          , groupL = "", groupSepLC = const "", groupTopL = "", groupBottomL = ""
                          }

-- | Remove the right border from a 'TableStyle'.
withoutRightBorder :: TableStyle a b -> TableStyle a b
withoutRightBorder ts = ts { headerSepRC = "", headerTopR = "", headerR = ""
                           , groupR = "", groupSepRC = const "", groupTopR = "", groupBottomR = ""
                           }

-- | Modify a 'TableStyle' to use Unicode rounded corners.
withRoundCorners :: TableStyle a b -> TableStyle a b
withRoundCorners ts = ts { headerTopL     = "╭"
                         , rowHeaderLeftT = "╭"
                         , bothHeadersTL  = "╭"
                         , groupTopL      = "╭"
                         , headerTopR     = "╮"
                         , groupTopR      = "╮"
                         , rowHeaderLeftB = "╰"
                         , groupBottomL   = "╰"
                         , groupBottomR   = "╯"
                         }

-- | A short-hand specification for generating Unicode table styles, by
-- specifying the line type of each of the main lines.
data TableStyleSpec
    = TableStyleSpec
    { headerSep         :: LineStyle
    , headerTop         :: LineStyle
    , headerLeft        :: LineStyle
    , headerRight       :: LineStyle
    , rowHeaderSep      :: LineStyle
    , rowHeaderLeft     :: LineStyle
    , rowHeaderTop      :: LineStyle
    , rowHeaderBottom   :: LineStyle
    , bothHeadersTop    :: LineStyle
    , bothHeadersBottom :: LineStyle
    , bothHeadersLeft   :: LineStyle
    , bothHeadersRight  :: LineStyle
    , groupLeft         :: LineStyle
    , groupRight        :: LineStyle
    , groupTop          :: LineStyle
    , groupBottom       :: LineStyle
    }

-- | Constructs a simple 'TableStyleSpec' which uses the given 'LineStyle's in
-- the headers and group, respectively.
simpleTableStyleSpec :: LineStyle -> LineStyle -> TableStyleSpec
simpleTableStyleSpec headerStyle groupStyle
    = TableStyleSpec
    { headerSep         = headerStyle
    , headerTop         = headerStyle
    , headerLeft        = headerStyle
    , headerRight       = headerStyle
    , rowHeaderSep      = headerStyle
    , rowHeaderLeft     = headerStyle
    , rowHeaderTop      = headerStyle
    , rowHeaderBottom   = headerStyle
    , bothHeadersTop    = headerStyle
    , bothHeadersBottom = headerStyle
    , bothHeadersLeft   = headerStyle
    , bothHeadersRight  = headerStyle
    , groupLeft         = groupStyle
    , groupRight        = groupStyle
    , groupTop          = groupStyle
    , groupBottom       = groupStyle
    }

-- Generate an ASCII 'TableStyle' from a 'TableStyleSpec' using pluses for joins.
asciiTableStyleFromSpec :: TableStyleSpec -> TableStyle LineStyle LineStyle
asciiTableStyleFromSpec = tableStyleFromSpec asciiHorizontal asciiVertical asciiJoinString4

-- Generate an ASCII 'TableStyle' from a 'TableStyleSpec' using rounded joins.
roundedAsciiTableStyleFromSpec :: TableStyleSpec -> TableStyle LineStyle LineStyle
roundedAsciiTableStyleFromSpec = tableStyleFromSpec asciiHorizontal asciiVertical roundedAsciiJoinString4

-- Generate a unicode 'TableStyle' from a 'TableStyleSpec'.
unicodeTableStyleFromSpec :: TableStyleSpec -> TableStyle LineStyle LineStyle
unicodeTableStyleFromSpec = tableStyleFromSpec unicodeHorizontal unicodeVertical unicodeJoinString4

-- | Generate a 'TableStyle from a given 'TableStyleSpec', along with functions
-- to construct horizontal and vertical lines and joins.
-- The function for constructing join strings takes its arguments in the order
-- west, east, north, south.
tableStyleFromSpec :: (LineStyle -> String) -> (LineStyle -> String)
                   -> (LineStyle -> LineStyle -> LineStyle -> LineStyle -> String)
                   -> TableStyleSpec
                   -> TableStyle LineStyle LineStyle
tableStyleFromSpec hString vString joinString TableStyleSpec { .. }
    = TableStyle
    { headerSepH     = hString headerSep
    , headerSepLC    = joinString NoLine headerSep headerLeft groupLeft
    , headerSepRC    = joinString headerSep NoLine headerRight groupRight
    , headerSepC     = joinString headerSep headerSep
    , headerTopH     = hString headerTop
    , headerTopL     = joinString NoLine headerTop NoLine headerLeft
    , headerTopR     = joinString headerTop NoLine NoLine headerRight
    , headerTopC     = joinString headerTop headerTop NoLine
    , headerL        = vString headerLeft
    , headerR        = vString headerRight
    , headerC        = vString
    , rowHeaderSepV  = vString rowHeaderSep
    , rowHeaderSepTC = joinString rowHeaderTop groupTop NoLine rowHeaderSep
    , rowHeaderSepBC = joinString rowHeaderBottom groupBottom rowHeaderSep NoLine
    , rowHeaderSepC  = \h g -> joinString h g rowHeaderSep rowHeaderSep
    , rowHeaderLeftV = vString rowHeaderLeft
    , rowHeaderLeftT = joinString NoLine rowHeaderTop NoLine rowHeaderLeft
    , rowHeaderLeftB = joinString NoLine rowHeaderBottom rowHeaderLeft NoLine
    , rowHeaderLeftC = \h -> joinString NoLine h rowHeaderLeft rowHeaderLeft
    , rowHeaderT     = hString rowHeaderTop
    , rowHeaderB     = hString rowHeaderBottom
    , rowHeaderC     = hString
    , bothHeadersTL  = joinString NoLine bothHeadersTop NoLine bothHeadersLeft
    , bothHeadersTR  = joinString bothHeadersTop headerTop NoLine bothHeadersRight
    , bothHeadersBL  = joinString NoLine bothHeadersBottom bothHeadersLeft rowHeaderLeft
    , bothHeadersBR  = joinString bothHeadersBottom headerSep bothHeadersRight rowHeaderSep
    , bothHeadersL   = vString bothHeadersLeft
    , bothHeadersR   = vString bothHeadersRight
    , bothHeadersT   = hString bothHeadersTop
    , bothHeadersB   = hString bothHeadersBottom
    , groupL         = vString groupLeft
    , groupR         = vString groupRight
    , groupC         = vString
    , groupSepH      = hString
    , groupSepC      = \h v -> joinString h h v v
    , groupSepLC     = \h -> joinString NoLine h groupLeft groupLeft
    , groupSepRC     = \h -> joinString h NoLine groupRight groupRight
    , groupTopC      = joinString groupTop groupTop NoLine
    , groupTopL      = joinString NoLine groupTop NoLine groupLeft
    , groupTopR      = joinString groupTop NoLine NoLine groupRight
    , groupTopH      = hString groupTop
    , groupBottomC   = \v -> joinString groupBottom groupBottom v NoLine
    , groupBottomL   = joinString NoLine groupBottom groupLeft NoLine
    , groupBottomR   = joinString groupBottom NoLine groupRight NoLine
    , groupBottomH   = hString groupBottom
    }

-- | Modify a 'TableStyleSpec' to use the given 'LineStyle' for header separators.
setTableStyleSpecSeparator :: LineStyle -> TableStyleSpec -> TableStyleSpec
setTableStyleSpecSeparator sep spec =
    spec { headerSep = sep, rowHeaderSep = sep, bothHeadersBottom = sep, bothHeadersRight = sep }

-- | My usual ASCII table style.
asciiRoundS :: TableStyle LineStyle LineStyle
asciiRoundS = tableStyleFromSpec asciiHorizontal asciiVertical roundedAsciiJoinString4 $
    simpleTableStyleSpec SingleLine SingleLine

-- | Uses lines and plus for joints.
asciiS :: TableStyle LineStyle LineStyle
asciiS = asciiTableStyleFromSpec $ simpleTableStyleSpec SingleLine SingleLine

-- | Like 'asciiS', but uses double lines and double pluses for borders.
asciiDoubleS :: TableStyle LineStyle LineStyle
asciiDoubleS = asciiTableStyleFromSpec $ simpleTableStyleSpec DoubleLine SingleLine

-- | Uses special unicode characters to draw clean thin boxes.
unicodeS :: TableStyle LineStyle LineStyle
unicodeS = unicodeTableStyleFromSpec . setTableStyleSpecSeparator DoubleLine $
    simpleTableStyleSpec SingleLine SingleLine

-- | Same as 'unicodeS' but uses bold headers.
unicodeBoldHeaderS :: TableStyle LineStyle LineStyle
unicodeBoldHeaderS = inheritStyleHeaderGroup makeLineBold id makeLineBold id .
    unicodeTableStyleFromSpec $ simpleTableStyleSpec HeavyLine SingleLine

-- | Like 'unicodeS' but with rounded edges.
unicodeRoundS :: TableStyle LineStyle LineStyle
unicodeRoundS = withRoundCorners unicodeS

-- | Uses bold lines.
unicodeBoldS :: TableStyle LineStyle LineStyle
unicodeBoldS = unicodeTableStyleFromSpec $ simpleTableStyleSpec HeavyLine HeavyLine

-- | Uses bold lines with the exception of group separators, which are striped.
unicodeBoldStripedS :: TableStyle LineStyle LineStyle
unicodeBoldStripedS = unicodeBoldS
                    { groupSepLC = const $ unicodeVertical HeavyLine
                    , groupSepRC = const $ unicodeVertical HeavyLine
                    , groupSepC  = const unicodeVertical
                    }

-- | Draw every line with a double frame.
unicodeDoubleFrameS :: TableStyle LineStyle LineStyle
unicodeDoubleFrameS = unicodeTableStyleFromSpec $ simpleTableStyleSpec DoubleLine DoubleLine