Here is a generalized table generator. It calculates the column widths to fit the widest row. The ColDesc
type allows you to specify, for each column, the title alignment, the title string, the data alignment, and a function to format the data.
import Data.List (transpose, intercalate)
-- a type for records
data T = T { make :: String
, model :: String
, years :: [Int] }
deriving Show
-- a type for fill functions
type Filler = Int -> String -> String
-- a type for describing table columns
data ColDesc t = ColDesc { colTitleFill :: Filler
, colTitle :: String
, colValueFill :: Filler
, colValue :: t -> String
}
-- test data
test =
[ T "foo" "avenger" [1990, 1992]
, T "bar" "eagle" [1980, 1982, 1983]
]
-- functions that fill a string (s) to a given width (n) by adding pad
-- character (c) to align left, right, or center
fillLeft c n s = s ++ replicate (n - length s) c
fillRight c n s = replicate (n - length s) c ++ s
fillCenter c n s = replicate l c ++ s ++ replicate r c
where x = n - length s
l = x `div` 2
r = x - l
-- functions that fill with spaces
left = fillLeft ' '
right = fillRight ' '
center = fillCenter ' '
-- converts a list of items into a table according to a list
-- of column descriptors
showTable :: [ColDesc t] -> [t] -> String
showTable cs ts =
let header = map colTitle cs
rows = [[colValue c t | c <- cs] | t <- ts]
widths = [maximum $ map length col | col <- transpose $ header : rows]
separator = intercalate "-+-" [replicate width '-' | width <- widths]
fillCols fill cols = intercalate " | " [fill c width col | (c, width, col) <- zip3 cs widths cols]
in
unlines $ fillCols colTitleFill header : separator : map (fillCols colValueFill) rows
Running:
putStrLn $ showTable [ ColDesc center "Make" left make
, ColDesc center "Model" left model
, ColDesc center "Year" right (intercalate ", " . map show . years)
] test
Results in:
Make | Model | Year
-----+---------+-----------------
foo | avenger | 1990, 1992
bar | eagle | 1980, 1982, 1983