1 {-# LANGUAGE ConstrainedClassMethods #-}
2 {-# LANGUAGE DefaultSignatures #-}
4 {-# LANGUAGE DeriveDataTypeable #-}
6 {-# LANGUAGE DeriveGeneric #-}
7 {-# LANGUAGE FlexibleContexts #-}
8 {-# LANGUAGE FlexibleInstances #-}
9 {-# LANGUAGE FunctionalDependencies #-}
10 {-# LANGUAGE GADTs #-}
11 {-# LANGUAGE MultiParamTypeClasses #-}
12 {-# LANGUAGE OverloadedStrings #-}
13 {-# LANGUAGE Rank2Types #-}
14 {-# LANGUAGE ScopedTypeVariables #-}
15 {-# LANGUAGE StandaloneDeriving #-}
16 {-# LANGUAGE TypeOperators #-}
17 {-# LANGUAGE UndecidableInstances #-}
19 {- | Module implements the default methods for Tabulate
20 All examples listed in the document need the following language pragmas
21 and following modules imported
24 {#- LANGUAGE MultiParamTypeClasses}
25 {#- LANGUAGE DeriveGeneric}
26 {#- LANGUAGE DeriveDataTypeable}
28 import qualified GHC.Generics as G
32 module Literate.Table where
34 --import Data.Generics.Aliases
36 import Data.Bool (Bool)
38 import Data.Foldable (foldMap)
39 import Data.Function (($), (.))
40 import Data.Functor (Functor, fmap, (<$>))
42 import Data.List ((++))
43 import Data.List qualified as List
44 import Data.Map qualified as Map
46 import Data.String (IsString (..), String)
49 import GHC.Err (undefined)
50 import GHC.Float (Float)
51 import GHC.Generics as G
52 import GHC.Num (Integer, (+), (-))
54 import Literate.Box (Alignment (AlignTopLeft))
55 import Literate.Box qualified as B
58 import Prelude (Double, error)
60 -- | * Class 'GRecordMeta'
61 class GRecordMeta f where
62 toTree :: f a -> [Tree String]
64 instance GRecordMeta U1 where
66 instance (GRecordMeta a, GRecordMeta b) => GRecordMeta (a :*: b) where
67 toTree (x :*: y) = toTree x ++ toTree y
68 instance (GRecordMeta a, GRecordMeta b) => GRecordMeta (a :+: b) where
69 toTree (G.L1 x) = toTree x
70 toTree (G.R1 x) = toTree x
71 instance (GRecordMeta a, Selector s) => GRecordMeta (M1 S s a) where
72 toTree a = [Node (selName a) $ toTree (unM1 a)]
73 instance (GRecordMeta a, Constructor c) => GRecordMeta (M1 C c a) where
74 -- we don't want to build node for constructor
75 toTree a = [Node (conName a) $ toTree (unM1 a)]
77 --toTree a = toTree (unM1 a)
78 instance (GRecordMeta a) => GRecordMeta (M1 D c a) where
79 toTree (M1 x) = toTree x
80 instance (CellValueFormatter a, Data a, RecordMeta a) => GRecordMeta (K1 i a) where
81 --toTree x = [Node (show (unK1 x)) (toTree' $ unK1 x)]
82 toTree x = toTree' $ unK1 x
84 {- | Use this flag to expand a Record Type as a table when
85 nested inside another record.
89 {- | Use this flag to not expand a Record type as a table when
90 nested inside another record. The 'Show' instance of the nested record
91 is used by default without expanding. This means that the fields of the
92 nested record are not displayed as separate headers.
94 data DoNotExpandWhenNested
96 {- | Class instance that needs to be instantiated for each
97 record that needs to be printed using printTable
101 data Stock = Stock {price:: Double, name:: String} derive (Show, G.Generic, Data)
102 instance Tabulate S 'ExpandWhenNested'
105 If 'S' is embedded inside another `Record` type and should be
106 displayed in regular Record Syntax, then
110 instance Tabulate S 'DoNotExpandWhenNested'
113 class Tabulate a flag | a -> flag
115 --instance TypeCast flag HFalse => Tabulate a flag
116 instance {-# OVERLAPPABLE #-} (flag ~ DoNotExpandWhenNested) => Tabulate a flag
118 class RecordMeta a where
119 toTree' :: a -> [Tree String]
120 instance (Tabulate a flag, RecordMeta' flag a) => RecordMeta a where
121 toTree' = toTree'' (undefined :: proxy flag)
123 class RecordMeta' flag a where
124 toTree'' :: proxy flag -> a -> [Tree String]
125 instance (G.Generic a, GRecordMeta (Rep a)) => RecordMeta' ExpandWhenNested a where
126 toTree'' _ a = toTree (G.from a)
127 instance (CellValueFormatter a) => RecordMeta' DoNotExpandWhenNested a where
128 toTree'' _ a = [Node (ppFormatter a) []]
130 {- | Class that implements formatting using printf.
131 Default instances for String, Char, Int, Integer, Double and Float
132 are provided. For types that are not an instance of this class
135 class CellValueFormatter a where
136 -- Function that can be implemented by each instance
137 ppFormatter :: a -> String
138 -- Future support for this signature will be added
139 --ppFormatterWithStyle :: TablizeValueFormat -> a -> String
141 -- Default instance of function for types that do
142 -- do not have their own instance
143 default ppFormatter :: (Show a) => a -> String
144 ppFormatter x = show x
146 instance CellValueFormatter Integer where
147 ppFormatter x = printf "%d" x
148 instance CellValueFormatter Int where
149 ppFormatter x = printf "%d" x
150 instance CellValueFormatter Float where
151 ppFormatter x = printf "%g" x
152 instance CellValueFormatter String where
153 ppFormatter x = printf "%s" x
154 instance CellValueFormatter Double where
155 ppFormatter x = printf "%g" x
156 instance CellValueFormatter Bool
157 instance (Show a, CellValueFormatter a) => CellValueFormatter (Maybe a)
159 gen_renderTableWithFlds :: [DisplayFld t] -> [t] -> B.Box
160 gen_renderTableWithFlds flds recs = results
162 col_wise_values = fmap (\(DFld f) -> fmap (ppFormatter . f) recs) flds
163 vertical_boxes = fmap (B.vsep 0 B.AlignTopLeft) $ fmap (fmap fromString) col_wise_values
164 results = B.hsep 5 B.AlignTopLeft vertical_boxes
166 class Boxable b where
167 printTable :: (G.Generic a, GRecordMeta (Rep a)) => b a -> IO ()
169 --printTableWithStyle :: (Data a, G.Generic a, GTabulate(Rep a)) => TablizeValueFormat -> b a -> IO ()
171 renderTable :: (G.Generic a, GRecordMeta (Rep a)) => b a -> B.Box
172 printTableWithFlds :: [DisplayFld t] -> b t -> IO ()
173 renderTableWithFlds :: [DisplayFld t] -> b t -> B.Box
175 -- | Instance methods to render or print a list of records in a tabular format.
176 instance Boxable [] where
177 printTable m = B.printBox $ ppRecords m
179 renderTable m = ppRecords m
180 printTableWithFlds flds recs = B.printBox $ renderTableWithFlds flds recs
181 renderTableWithFlds = gen_renderTableWithFlds
184 instance Boxable V.Vector where
185 -- | Prints a "Vector" as a table. Called by "printTable".
186 -- | Need not be called directly
187 printTable m = B.printBox $ renderTable m --TODO: switch this to Vector
188 renderTable m = ppRecords $ V.toList m
190 -- | Print a "Vector" of records as a table with the selected fields.
191 -- Called by "printTableWithFlds".
192 printTableWithFlds flds recs = B.printBox $ renderTableWithFlds flds $ V.toList recs
193 renderTableWithFlds flds recs = gen_renderTableWithFlds flds $ V.toList recs
196 instance (CellValueFormatter k) => Boxable (Map.Map k) where
197 printTable m = B.printBox $ renderTable m
198 renderTable m = ppRecordsWithIndex m
199 printTableWithFlds flds recs = B.printBox $ renderTableWithFlds flds recs
201 renderTableWithFlds flds recs = results
203 data_cols = renderTableWithFlds flds $ Map.elems recs
204 index_cols = B.vsep 0 B.AlignTopLeft $ fmap (fromString . ppFormatter) $ Map.keys recs
205 vertical_cols = B.hsep 5 B.AlignTopLeft [index_cols, data_cols]
206 results = vertical_cols
208 -- Pretty Print the records as a table. Handles both records inside
210 ppRecords :: (GRecordMeta (Rep a), G.Generic a) => [a] -> B.Box
211 ppRecords recs = B.punctuateH AlignTopLeft "|" $ createHeaderDataBoxes recs
213 -- Pretty Print the records as a table. Handles records contained in a Map.
214 -- Functions also prints the keys as the index of the table.
215 ppRecordsWithIndex :: (CellValueFormatter k, GRecordMeta (Rep a), G.Generic a) => Map.Map k a -> B.Box
216 ppRecordsWithIndex recs = B.punctuateH AlignTopLeft "|" $ index_box : data_boxes
218 data_boxes = createHeaderDataBoxes $ Map.elems recs
219 index_box = createIndexBoxes recs
221 -- What follows are helper functions to build the B.Box structure to print as table.
223 -- Internal helper functions for building the Tree.
225 -- Build the list of paths from the root to every leaf.
226 -- >>> constructPath $ Node "" $ recsToTrees ([R00{r00Int = 42, r00String = "foo"}]::[R00])kk
227 -- [["","root","R00","r00Int","42"],["","root","R00","r00String","foo"]]
228 constructPath :: Tree a -> [[a]]
229 constructPath (Node r []) = [[r]]
230 constructPath (Node r f) = [r : x | x <- List.concatMap constructPath f]
232 -- Fill paths with a "-" so that all paths have the
234 -- >>> fillPath ([["1", "2", "3", "4"], ["5"]]::[[String]])
235 -- [["2","3","4"],["-","-","-"]]
236 fillPath :: IsString a => [[a]] -> [[a]]
237 fillPath paths = [xs | x : xs <- new_paths]
239 depth = List.maximum $ List.length <$> paths
240 diff = (\p -> depth - List.length p) <$> paths
241 new_paths = (\(p, d) -> p ++ List.replicate d "-") <$> List.zip paths diff
243 -- Count the number of fields in the passed structure.
244 -- The no of leaves is the sum of all fields across all nested
245 -- records in the passed structure.
246 countLeaves :: Tree a -> Tree (Int, a)
247 countLeaves (Node r f) = case f of
252 let count_leaves = fmap countLeaves x
253 level_count = List.foldr (\(Node (c, a) _) b -> c + b) 0 count_leaves
254 in Node (level_count, r) count_leaves
256 -- Trims the tree of records and return just the
257 -- leaves of the record
258 -- >>> error $ drawTree $ trimTree $ Node "" $ recsToTrees ([R00{r00Int = 42, r00String = "foo"}]::[R00])
269 trimTree :: Tree t -> Tree t
270 trimTree (Node r f) = trimLeaves r f
272 -- Helper function called by trimTree.
273 trimLeaves :: t -> [Tree t] -> Tree t
274 trimLeaves r f = Node r (go f)
277 let result = goo <$> f
282 _ -> Just $ trimLeaves r' f'
285 -- Get all the leaves from the record.
286 -- Returns all leaves across the record structure.
287 getLeaves :: (CellValueFormatter a) => Tree a -> [String]
288 getLeaves (Node r f) = case f of
289 [] -> [ppFormatter r]
290 _ -> foldMap getLeaves f
292 {- | >>> y recsToTrees ([R00{r00Int = 42, r00String = "foo"}]::[R00])
313 recsToTrees :: (Functor f, GRecordMeta (Rep a), Generic a) => f a -> f (Tree String)
314 recsToTrees = fmap (\a -> Node "root" $ toTree $ G.from a)
316 getHeaderDepth :: IsString a => [Tree a] -> Int
317 getHeaderDepth = List.length . List.head . fillPath . constructPath . trimTree . List.head
319 --createIndexBoxes :: Map.Map a a -> B.Box
320 createIndexBoxes :: (GRecordMeta (Rep a), Generic a, CellValueFormatter k) => Map.Map k a -> B.Box
321 createIndexBoxes recs = index_box
323 rec_trees = recsToTrees $ Map.elems recs
324 header_depth = getHeaderDepth rec_trees
325 index_col = List.replicate header_depth "-" ++ List.map ppFormatter (Map.keys recs)
326 index_box = B.vsep 0 B.AlignTopLeft $ List.map fromString index_col
328 createHeaderDataBoxes :: (GRecordMeta (Rep a), Generic a) => [a] -> [B.Box]
329 createHeaderDataBoxes recs = vertical_boxes
331 rec_trees = recsToTrees recs
332 header_boxes = createHeaderCols rec_trees
333 data_boxes = createDataBoxes rec_trees
334 vertical_boxes = (\(a, b) -> B.vsep 0 B.AlignTopLeft [a, b]) <$> List.zip header_boxes data_boxes
336 -- >>> error $ B.render $ B.hcat AlignTopLeft $ createHeaderCols $ recsToTrees ([R00{r00Int = 42, r00String = "foo"}]::[R00])
339 createHeaderCols :: [Tree String] -> [B.Box]
340 createHeaderCols rec_trees =
347 createBoxedHeaders :: [[String]] -> [B.Box]
348 createBoxedHeaders paths = B.vsep 0 B.AlignTopLeft . (fromString <$>) <$> paths
350 -- >>> error $ B.render $ B.hcat AlignTopLeft $ createDataBoxes $ recsToTrees ([R00{r00Int = 42, r00String = "foo"}]::[R00])
352 -- >>> error $ B.render $ B.hcat AlignTopLeft $ createDataBoxes $ recsToTrees ([getR01]::[R01])
353 -- Jack-Jack100.101021Just 10.101
354 createDataBoxes :: CellValueFormatter a => [Tree a] -> [B.Box]
355 createDataBoxes rec_trees = vertical_boxes
357 horizontal_boxes = (fromString <$>) <$> (getLeaves <$> rec_trees)
358 vertical_boxes = B.vsep 0 B.AlignTopLeft <$> List.transpose horizontal_boxes
362 data T = C1 {aInt :: Double, aString :: String} deriving (Data, Typeable, Show, G.Generic)
363 data T1 = C2 {t1 :: T, bInt :: Double, bString :: String} deriving (Data, Typeable, Show, G.Generic)
365 c1 = C1 1000 "record_c1fdsafaf"
366 c2 = C2 c1 100.12121 "record_c2"
367 c3 = C2 c1 1001.12111 "record_c2fdsafdsafsafdsafasfa"
368 c4 = C2 c1 22222.12121 "r"
370 instance Tabulate T ExpandWhenNested
371 instance Tabulate T1 ExpandWhenNested
372 instance CellValueFormatter T
374 data R2 = R2 {a :: Maybe Integer} deriving (G.Generic, Show)
375 data R3 = R3 {r31 :: Maybe Integer, r32 :: String} deriving (G.Generic, Show)
376 tr = Node "root" (toTree . G.from $ c2)
377 r2 = Node "root" (toTree . G.from $ (R2 (Just 10)))
378 r3 = Node "root" (toTree . G.from $ (R3 (Just 10) "r3_string"))
380 -- | Used with 'printTableWithFlds'
381 data DisplayFld a = forall s. CellValueFormatter s => DFld (a -> s)
383 -- printTableWithFlds2 :: [DisplayFld t] -> V.Vector t -> IO ()
384 -- printTableWithFlds2 flds recs = B.printBox $ printTableWithFlds flds $ V.toList recs
386 -- printTableWithFlds3 :: (CellValueFormatter k) => [DisplayFld t] -> Map.Map k t -> String
387 -- printTableWithFlds3 flds recs = results
389 -- data_cols = renderTableWithFlds flds $ Map.elems recs
390 -- index_cols = B.vsep 0 B.AlignTopLeft $ fmap (fromString . ppFormatter) $ Map.keys recs
391 -- vertical_cols = B.hsep 5 B.AlignTopLeft [index_cols, data_cols]
392 -- results = B.render vertical_cols
397 , r00String :: String
401 , r00_String :: String
403 deriving (Show, Generic, Data)
404 instance CellValueFormatter R00
406 -- R0 has to derive from Data, since
409 { test_string :: String
410 , test_integer :: Integer
411 , test_float :: Float
412 , test_DOUBLE :: Maybe Double
414 deriving (Data, Show, G.Generic)
415 instance CellValueFormatter R01
417 data R02 = R02 {r2_r00 :: R00, r2_r01 :: R01}
418 deriving (Show, G.Generic, Data)
419 instance CellValueFormatter R02
421 data R03 = R03 {r3_id :: Int, r3_r02 :: R02}
422 deriving (Show, G.Generic, Data)
424 instance Tabulate R01 ExpandWhenNested
425 instance Tabulate R02 ExpandWhenNested
426 instance Tabulate R03 ExpandWhenNested
430 { test_string = "Jack-Jack"
432 , test_DOUBLE = Just 10.101
433 , test_float = 0.101021
436 getR02 = R02{r2_r00 = R00_ 100 "foo", r2_r01 = getR01}
438 getR03 = R03{r3_id = 200, r3_r02 = getR02}
440 recordsList = List.replicate 2 $ getR03
443 -- [R03 {r3_id = 20, nested_r02 = R02 {r2_id = 10, nested_r = R01 {test_string = "Jack-Jack", test_integer = 10, test_float = 0.101021, test_double = 10.101}}}
444 -- ,R03 {r3_id = 20, nested_r02 = R02 {r2_id = 10, nested_r = R01 {test_string = "Jack-Jack", test_integer = 10, test_float = 0.101021, test_double = 10.101}}}]
446 -- >>> B.rows $ renderTable recordsList
449 -- >>> B.cols $ renderTable recordsList
452 -- >>> error $ B.render $ ppRecords recordsList
453 -- R03 |R03 |R03 |R03 |R03 |R03
454 -- r3_id r3_r02 r3_r02 r3_r02 r3_r02 r3_r02
455 -- - R02 R02 R02 R02 R02
456 -- - r2_r00 r2_r01 r2_r01 r2_r01 r2_r01
457 -- - - R01 R01 R01 R01
458 -- - - test_string test_integer test_float test_DOUBLE
459 -- 200 R00_ {r00_Int = 100, r00_String = "foo"} Jack-Jack 10 0.101021 Just 10.101
460 -- 200 R00_ {r00_Int = 100, r00_String = "foo"} Jack-Jack 10 0.101021 Just 10.101
466 , ("key3", getR03{r3_id = 32})
469 -- >>> B.rows $ renderTable recordsMap
472 -- >>> B.cols $ renderTable recordsMap
475 -- >>> error $ B.render $ ppRecordsWithIndex recordsMap
476 -- - R03 R03 R03 R03 R03 R03
477 -- - r3_id r3_r02 r3_r02 r3_r02 r3_r02 r3_r02
478 -- - - R02 R02 R02 R02 R02
479 -- - - r2_r00 r2_r01 r2_r01 r2_r01 r2_r01
480 -- - - - R01 R01 R01 R01
481 -- - - - test_string test_integer test_float test_DOUBLE
482 -- key1 200 R00_ {r00_Int = 100, r00_String = "foo"} Jack-Jack 10 0.101021 Just 10.101
483 -- key2 200 R00_ {r00_Int = 100, r00_String = "foo"} Jack-Jack 10 0.101021 Just 10.101
484 -- key3 32 R00_ {r00_Int = 100, r00_String = "foo"} Jack-Jack 10 0.101021 Just 10.101