table-layout-1.0.0.2: src/Text/Layout/Table/Primitives/ColumnModifier.hs
{-# LANGUAGE RankNTypes #-}
module Text.Layout.Table.Primitives.ColumnModifier where
import Control.Arrow ((&&&))
import Data.List
import Data.Semigroup (Max(..))
import Text.Layout.Table.Cell
import Text.Layout.Table.Primitives.AlignInfo
import Text.Layout.Table.Spec.AlignSpec
import Text.Layout.Table.Spec.ColSpec
import Text.Layout.Table.Spec.CutMark
import Text.Layout.Table.Spec.LenSpec
import Text.Layout.Table.Spec.OccSpec
import Text.Layout.Table.Spec.Position
import Text.Layout.Table.Spec.Util
import Text.Layout.Table.StringBuilder
-- | Specifies how a column should be modified. Values of this type are derived
-- in a traversal over the input columns by using 'deriveColModInfosFromGrid'.
-- Finally, 'columnModifier' will interpret them and apply the appropriate
-- modification function to the cells of the column.
data ColModInfo
= FillAligned OccSpec AlignInfo
| FillTo Int
| FitTo Int (Maybe (OccSpec, AlignInfo))
-- | Private show function.
showCMI :: ColModInfo -> String
showCMI cmi = case cmi of
FillAligned _ ai -> "FillAligned .. " ++ showAI ai
FillTo i -> "FillTo " ++ show i
FitTo i _ -> "FitTo " ++ show i ++ ".."
-- | Get the exact width of a 'ColModInfo' after applying it with
-- 'columnModifier'.
widthCMI :: ColModInfo -> Int
widthCMI cmi = case cmi of
FillAligned _ ai -> widthAI ai
FillTo maxLen -> maxLen
FitTo lim _ -> lim
-- | Remove alignment from a 'ColModInfo'. This is used to change alignment of
-- headers while using the combined width information.
unalignedCMI :: ColModInfo -> ColModInfo
unalignedCMI cmi = case cmi of
FillAligned _ ai -> FillTo $ widthAI ai
FitTo i _ -> FitTo i Nothing
_ -> cmi
-- | Ensures that the modification provides a minimum width but only if it is
-- not limited.
ensureWidthCMI :: Int -> Position H -> ColModInfo -> ColModInfo
ensureWidthCMI w pos cmi = case cmi of
FillAligned oS ai@(AlignInfo lw optRW)
->
let neededW = w - widthAI ai
in if neededW <= 0
then cmi
else FillAligned oS $ case pos of
Start -> case optRW of
Just rw -> AlignInfo lw $ Just (rw + neededW)
Nothing -> AlignInfo (lw + neededW) optRW
End -> AlignInfo (lw + neededW) optRW
Center -> case optRW of
Just _ -> let (q, r) = w `divMod` 2
-- Calculate a new distribution.
in AlignInfo q $ Just (q + r)
Nothing -> AlignInfo (lw + neededW) optRW
FillTo maxLen -> FillTo (max maxLen w)
_ -> cmi
-- | Ensures that the given 'String' will fit into the modified columns.
ensureWidthOfCMI :: Cell a => a -> Position H -> ColModInfo -> ColModInfo
ensureWidthOfCMI = ensureWidthCMI . visibleLength
-- | Fit titles of a header column into the derived 'ColModInfo'.
fitTitlesCMI :: Cell a => [a] -> [Position H] -> [ColModInfo] -> [ColModInfo]
fitTitlesCMI = zipWith3 ensureWidthOfCMI
-- | Generates a function which modifies a given cell according to
-- 'Text.Layout.Table.Position.Position', 'CutMark' and 'ColModInfo'. This is
-- used to modify a single cell of a column to bring all cells of a column to
-- the same width.
columnModifier
:: (Cell a, StringBuilder b)
=> Position H
-> CutMark
-> ColModInfo
-> (a -> b)
columnModifier pos cms colModInfo = buildCellMod cms . case colModInfo of
FillAligned oS ai -> align oS ai
FillTo maxLen -> pad pos maxLen
FitTo lim mT ->
maybe (trimOrPad pos cms lim) (uncurry $ alignFixed pos cms lim) mT
-- | Generate the 'AlignInfo' of a cell by using the 'OccSpec'.
deriveAlignInfo :: Cell a => OccSpec -> a -> AlignInfo
deriveAlignInfo occSpec = measureAlignment (predicate occSpec)
unpackColSpecs :: [ColSpec] -> [(LenSpec, AlignSpec)]
unpackColSpecs = fmap $ lenSpec &&& alignSpec
-- | Derive the 'ColModInfo' for each column of a list of rows by using the
-- corresponding specifications.
deriveColModInfosFromGridLA :: Cell a => [(LenSpec, AlignSpec)] -> [Row a] -> [ColModInfo]
deriveColModInfosFromGridLA specs = deriveColModInfosFromColumnsLA specs . transpose
-- | Derive the 'ColModInfo' for each column of a list of rows by using the
-- corresponding 'ColSpec'.
deriveColModInfosFromGrid :: Cell a => [ColSpec] -> [Row a] -> [ColModInfo]
deriveColModInfosFromGrid = deriveColModInfosFromGridLA . unpackColSpecs
-- | Derive the 'ColModInfo' of a single column by using the 'LenSpec' and the
-- 'AlignSpec'.
deriveColModInfoFromColumnLA :: (Foldable col, Cell a) => (LenSpec, AlignSpec) -> col a -> ColModInfo
deriveColModInfoFromColumnLA (lenS, alignS) = case alignS of
NoAlign -> let expandFun = FillTo
fixedFun i = const $ FitTo i Nothing
measureMaximumWidth = getMax . foldMap (Max . visibleLength)
lengthFun = id
in go expandFun fixedFun measureMaximumWidth lengthFun
AlignOcc oS -> let expandFun = FillAligned oS
fixedFun i = FitTo i . Just . (,) oS
measureMaximumWidth = foldMap $ deriveAlignInfo oS
lengthFun = widthAI
in go expandFun fixedFun measureMaximumWidth lengthFun
where
go :: forall a w col. (Cell a, Foldable col)
=> (w -> ColModInfo)
-> (Int -> w -> ColModInfo)
-> (col a -> w)
-> (w -> Int)
-> col a
-> ColModInfo
go expandFun fixedFun measureMaximumWidth lengthFun =
let expandBetween' i j widthInfo | lengthFun widthInfo > j = fixedFun j widthInfo
| lengthFun widthInfo < i = fixedFun i widthInfo
| otherwise = expandFun widthInfo
expandUntil' f i widthInfo = if f (lengthFun widthInfo <= i)
then expandFun widthInfo
else fixedFun i widthInfo
interpretLenSpec = case lenS of
Expand -> expandFun
Fixed i -> fixedFun i
ExpandUntil i -> expandUntil' id i
FixedUntil i -> expandUntil' not i
ExpandBetween i j -> expandBetween' i j
in interpretLenSpec . measureMaximumWidth
-- | Derive the 'ColModInfo' for each column of a list of columns by using the
-- corresponding specifications.
deriveColModInfosFromColumnsLA :: (Foldable col, Cell a) => [(LenSpec, AlignSpec)] -> [col a] -> [ColModInfo]
deriveColModInfosFromColumnsLA specs = zipWith ($) (fmap deriveColModInfoFromColumnLA specs)
-- | Derive the 'ColModInfo' for each column of a list of columns by using the
-- corresponding 'ColSpec'.
deriveColModInfosFromColumns :: (Foldable col, Cell a) => [ColSpec] -> [col a] -> [ColModInfo]
deriveColModInfosFromColumns = deriveColModInfosFromColumnsLA . unpackColSpecs
-- | Derive the 'ColModInfo' and generate functions without any intermediate
-- steps.
deriveColumnModifiers
:: (Cell a, StringBuilder b)
=> [ColSpec]
-> [Row a]
-> [a -> b]
deriveColumnModifiers specs tab =
zipWith (uncurry columnModifier) (map (position &&& cutMark) specs) cmis
where
cmis = deriveColModInfosFromGrid specs tab