{-# LANGUAGE StandaloneDeriving #-} {-# LANGUAGE DeriveGeneric #-} -- Remove this {-# LANGUAGE DeriveDataTypeable #-} -- Remove this {-# LANGUAGE UndecidableInstances #-} {-# LANGUAGE GADTs #-} {-# LANGUAGE FunctionalDependencies #-} {-# LANGUAGE FlexibleInstances #-} {-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE Rank2Types #-} {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE DefaultSignatures #-} {-# LANGUAGE TypeOperators #-} {-# LANGUAGE ConstrainedClassMethods #-} {-# LANGUAGE MultiParamTypeClasses #-} {-#LANGUAGE ScopedTypeVariables #-} -- | Module implements the default methods for Tabulate -- All examples listed in the document need the following language pragmas -- and following modules imported -- -- @ -- {#- LANGUAGE MultiParamTypeClasses} -- {#- LANGUAGE DeriveGeneric} -- {#- LANGUAGE DeriveDataTypeable} -- -- import qualified GHC.Generics as G -- import Data.Data -- @ -- module Text.PrettyPrint.Tabulate ( Tabulate(..) , Boxable(..) , CellValueFormatter , ExpandWhenNested , DoNotExpandWhenNested , DisplayFld(..) ) where import Data.Maybe import Data.Data import Data.Tree import Data.Typeable import Data.Generics.Aliases import GHC.Generics as G import GHC.Show import qualified Data.Map as Map import qualified Text.PrettyPrint.Boxes as B import qualified Data.List as List import qualified Data.List as L import Text.Printf import qualified Data.Vector as V -- | Future change to support providing custom formatting functions data TablizeValueFormat = T {floatValueFormat::Maybe (Float -> String), stringValueFormat::Maybe (String -> String), integerValueFormat::Maybe (Integer -> String), intValueFormat::Maybe (Int -> String), doubleValueFormat::Maybe (Double -> String)} -- | Default TabulateValueFormat getDefaultTabulateValueFormat = T {floatValueFormat=Nothing, stringValueFormat=Nothing, integerValueFormat=Nothing, intValueFormat=Nothing, doubleValueFormat=Nothing} data Tag = Constr | Fields | Values deriving (Show) class GRecordMeta f where toTree:: f a -> [Tree String] instance GRecordMeta U1 where toTree U1 = [] instance (GRecordMeta (a), GRecordMeta (b)) => GRecordMeta (a :*: b) where toTree (x :*: y) = (toTree x) ++ (toTree y) instance (GRecordMeta (a), GRecordMeta (b)) => GRecordMeta (a :+: b) where toTree x = toTree x instance (GRecordMeta a, Selector s) => GRecordMeta (M1 S s a) where toTree a = [Node (selName a) $ toTree (unM1 a)] where instance (GRecordMeta a, Constructor c) => GRecordMeta (M1 C c a) where -- we don't want to build node for constructor --toTree a = [Node (conName a) $ toTree (unM1 a)] toTree a = toTree (unM1 a) instance (GRecordMeta a) => GRecordMeta (M1 D c a) where toTree (M1 x) = toTree x instance (CellValueFormatter a, Data a, RecordMeta a) => GRecordMeta (K1 i a) where --toTree x = [Node (show (unK1 x)) (toTree' $ unK1 x)] toTree x = toTree' $ unK1 x -- | Use this flag to expand a Record Type as a table when -- nested inside another record. data ExpandWhenNested -- | Use this flag to not expand a Record type as a table when -- nested inside another record. The 'Show' instance of the nested record -- is used by default without expanding. This means that the fields of the -- nested record are not displayed as separate headers. data DoNotExpandWhenNested -- | Class instance that needs to be instantiated for each -- record that needs to be printed using printTable -- -- @ -- -- data Stock = Stock {price:: Double, name:: String} derive (Show, G.Generic, Data) -- instance Tabulate S 'ExpandWhenNested' -- @ -- -- If 'S' is embedded inside another `Record` type and should be -- displayed in regular Record Syntax, then -- -- @ -- -- instance Tabulate S 'DoNotExpandWhenNested' -- @ -- class Tabulate a flag | a->flag where {} --instance TypeCast flag HFalse => Tabulate a flag instance {-# OVERLAPPABLE #-} (flag ~ DoNotExpandWhenNested) => Tabulate a flag class RecordMeta a where toTree':: a -> [Tree String] instance (Tabulate a flag, RecordMeta' flag a) => RecordMeta a where toTree' = toTree'' (undefined::proxy flag) class RecordMeta' flag a where toTree'':: proxy flag -> a -> [Tree String] instance (G.Generic a, GRecordMeta (Rep a)) => RecordMeta' ExpandWhenNested a where toTree'' _ a = toTree (G.from a) instance (CellValueFormatter a) => RecordMeta' DoNotExpandWhenNested a where toTree'' _ a = [Node (ppFormatter a) []] -- | Class that implements formatting using printf. -- Default instances for String, Char, Int, Integer, Double and Float -- are provided. For types that are not an instance of this class -- `show` is used. class CellValueFormatter a where -- Function that can be implemented by each instance ppFormatter :: a -> String -- Future support for this signature will be added ppFormatterWithStyle :: TablizeValueFormat -> a -> String -- Default instance of function for types that do -- do not have their own instance default ppFormatter :: (Show a) => a -> String ppFormatter x = show x -- Future support. default ppFormatterWithStyle :: (Show a) => TablizeValueFormat -> a -> String ppFormatterWithStyle _ x = "default_" ++ show x instance CellValueFormatter Integer where ppFormatter x = printf "%d" x ppFormatterWithStyle style x = case integerValueFormat style of Just f -> f x Nothing -> ppFormatter x instance CellValueFormatter Int where ppFormatter x = printf "%d" x ppFormatterWithStyle style x = case intValueFormat style of Just f -> f x Nothing -> ppFormatter x instance CellValueFormatter Float where ppFormatter x = printf "%14.7g" x ppFormatterWithStyle style x = case floatValueFormat style of Just f -> f x Nothing -> ppFormatter x instance CellValueFormatter String where ppFormatter x = printf "%s" x ppFormatterWithStyle style x = case stringValueFormat style of Just f -> f x Nothing -> ppFormatter x instance CellValueFormatter Double where ppFormatter x = printf "%14.7g" x ppFormatterWithStyle style x = case doubleValueFormat style of Just f -> f x Nothing -> ppFormatter x instance CellValueFormatter Bool instance (Show a, CellValueFormatter a) => CellValueFormatter (Maybe a) gen_renderTableWithFlds :: [DisplayFld t] -> [t] -> B.Box gen_renderTableWithFlds flds recs = results where col_wise_values = fmap (\(DFld f) -> fmap (ppFormatter .f) recs) flds vertical_boxes = fmap (B.vsep 0 B.top) $ fmap (fmap B.text) col_wise_values results = B.hsep 5 B.top vertical_boxes class Boxable b where -- toBox :: (Data a, G.Generic a, GRecordMeta(Rep a)) => b a -> [[B.Box]] -- toBoxWithStyle :: (Data a, G.Generic a, GTabulate(Rep a)) => TablizeValueFormat -> b a -> [[B.Box]] -- | Used to print a container of Records in a tabular format. -- -- @ -- -- data Stock = Stock {price:: Double, ticker:: String} deriving (Show, Data, G.Generic) -- instance Tabulate Stock DoNotExpandWhenNested -- -- this can be a Vector or Map -- let s = [Stock 10.0 "yahoo", Stock 12.0 "goog", Stock 10.0 "amz"] -- T.printTable s -- @ -- -- Nested records can also be printed in tabular format -- -- @ -- -- data FxCode = USD | EUR deriving (Show, Data, G.Generic) -- instance 'CellValueFormatter' FxCode -- -- data Price = Price {px:: Double, fxCode:: FxCode} deriving (Show, Data, G.Generic) -- instance 'Tabulate' Price 'ExpandWhenNested' -- -- since Price will be nested, it also needs an instance of -- -- CellValueFormatter -- instance CellValueFormatter Price -- -- data Stock = Stock {ticker:: String, price:: Price} deriving (Show, Data, G.Generic) -- instance Tabulate Stock DoNotExpandWhenNested -- -- -- this can be a Vector or Map -- let s = [Stock "yahoo" (Price 10.0 USD), Stock "ikea" (Price 11.0 EUR)] -- printTable s -- @ -- printTable :: (G.Generic a, GRecordMeta (Rep a)) => b a -> IO () --printTableWithStyle :: (Data a, G.Generic a, GTabulate(Rep a)) => TablizeValueFormat -> b a -> IO () -- | Similar to 'printTable' but rather than return IO (), returns a -- 'Box' object that can be printed later on, using 'printBox' renderTable :: (G.Generic a, GRecordMeta (Rep a)) => b a -> B.Box -- | Used for printing selected fields from Record types -- This is useful when Records have a large number of fields -- and only few fields need to be introspected at any time. -- -- Using the example provided under 'printTables', -- -- @ -- 'printTableWithFlds' [DFld (px . price), DFld ticker] s -- -- @ printTableWithFlds :: [DisplayFld t] -> b t -> IO () -- | Same as printTableWithFlds but returns a `Box` object, rather than -- returning an `IO ()`. renderTableWithFlds :: [DisplayFld t] -> b t -> B.Box -- | Instance methods to render or print a list of records in a tabular format. instance Boxable [] where -- | Used to print a list of Records in a tabular format. -- @ -- -- data Stock = Stock {price:: Double, ticker:: String} -- instance Tabulate S DoNotExpandWhenNested -- let s = [Stock 10.0 "yahoo", Stock 12.0 "goog", Stock 10.0 "amz"] -- T.printTable s -- -- @ printTable m = B.printBox $ ppRecords m renderTable m = ppRecords m -- | Print a "List" of records as a table with just the given fields. -- Called by "printTableWithFlds". printTableWithFlds flds recs = B.printBox $ renderTableWithFlds flds recs renderTableWithFlds = gen_renderTableWithFlds instance Boxable V.Vector where -- | Prints a "Vector" as a table. Called by "printTable". -- | Need not be called directly printTable m = B.printBox $ renderTable m --TODO: switch this to Vector renderTable m = ppRecords $ V.toList m -- | Print a "Vector" of records as a table with the selected fields. -- Called by "printTableWithFlds". printTableWithFlds flds recs = B.printBox $ renderTableWithFlds flds $ V.toList recs renderTableWithFlds flds recs = gen_renderTableWithFlds flds $ V.toList recs instance (CellValueFormatter k) => Boxable (Map.Map k) where -- | Prints a "Map" as a table. Called by "ppTable" -- | Need not be called directly printTable m = B.printBox $ renderTable m renderTable m = ppRecordsWithIndex m -- | Prints a "Map" as a table with the selected fields. Called by "printTable" -- | Need not be called directly printTableWithFlds flds recs = B.printBox $ renderTableWithFlds flds recs renderTableWithFlds flds recs = results where data_cols = renderTableWithFlds flds $ Map.elems recs index_cols = B.vsep 0 B.top $ fmap (B.text . ppFormatter) $ Map.keys recs vertical_cols = B.hsep 5 B.top [index_cols, data_cols] results = vertical_cols -- Pretty Print the reords as a table. Handles both records inside -- Lists and Vectors ppRecords :: (GRecordMeta (Rep a), G.Generic a) => [a] -> B.Box ppRecords recs = result where result = B.hsep 5 B.top $ createHeaderDataBoxes recs -- Pretty Print the records as a table. Handles records contained in a Map. -- Functions also prints the keys as the index of the table. ppRecordsWithIndex :: (CellValueFormatter k, GRecordMeta (Rep a), G.Generic a) => (Map.Map k a) -> B.Box ppRecordsWithIndex recs = result where data_boxes = createHeaderDataBoxes $ Map.elems recs index_box = createIndexBoxes recs result = B.hsep 5 B.top $ index_box:data_boxes -- What follows are helper functions to build the B.Box structure to print as table. -- Internal helper functions for building the Tree. -- Build the list of paths from the root to every leaf. constructPath :: Tree a -> [[a]] constructPath (Node r []) = [[r]] constructPath (Node r f) = [r:x | x <- (L.concatMap constructPath f)] -- Fill paths with a "-" so that all paths have the -- same length. fillPath paths = stripped_paths where depth = L.maximum $ L.map L.length paths diff = L.map (\p -> depth - (L.length p)) paths new_paths = L.map (\(p,d) -> p ++ L.replicate d "-") $ L.zip paths diff stripped_paths = [xs | x:xs <- new_paths] -- Count the number of fields in the passed structure. -- The no of leaves is the sum of all fields across all nested -- records in the passed structure. countLeaves :: Tree a -> Tree (Int, a) countLeaves (Node r f) = case f of [] -> Node (1, r) [] x -> countLeaves' x where countLeaves' x = let count_leaves = fmap countLeaves x level_count = Prelude.foldr (\(Node (c, a) _) b -> c + b) 0 count_leaves in Node (level_count, r) count_leaves -- Trims a the tree of records and return just the -- leaves of the record trimTree (Node r f) = trimLeaves r f -- Helper function called by trimTree. trimLeaves r f = Node r (trimLeaves' f) where trimLeaves' f = let result = fmap trimLeaves'' f where trimLeaves'' (Node r' f') = let result' = case f' of [] -> Nothing _ -> Just $ trimLeaves r' f' in result' in catMaybes result -- Get all the leaves from the record. Returns all leaves -- across the record structure. getLeaves :: (CellValueFormatter a) => Tree a -> [String] getLeaves (Node r f) = case f of [] -> [(ppFormatter r)] _ -> foldMap getLeaves f recsToTrees recs = fmap (\a -> Node "root" $ (toTree . G.from $ a)) $ recs getHeaderDepth rec_trees = header_depth where header_depth = L.length . L.head . fillPath . constructPath . trimTree . L.head $ rec_trees createBoxedHeaders :: [[String]] -> [B.Box] createBoxedHeaders paths = boxes where boxes = L.map wrapWithBox paths wrapWithBox p = B.vsep 0 B.top $ L.map B.text p --createHeaderCols :: [Tree String] -> [B.Box] createHeaderCols rec_trees = header_boxes where header_boxes = createBoxedHeaders . fillPath . constructPath . trimTree . L.head $ rec_trees --createDataBoxes :: [Tree a] -> [B.Box] createDataBoxes rec_trees = vertical_boxes where horizontal_boxes = fmap (fmap B.text) $ fmap getLeaves rec_trees vertical_boxes = fmap (B.vsep 0 B.top) $ L.transpose horizontal_boxes --createIndexBoxes :: Map.Map a a -> B.Box createIndexBoxes recs = index_box where rec_trees = recsToTrees $ Map.elems recs header_depth = getHeaderDepth rec_trees index_col = (L.replicate header_depth "-" ) ++ (L.map ppFormatter $ Map.keys recs) index_box = B.vsep 0 B.top $ L.map B.text index_col createHeaderDataBoxes recs = vertical_boxes where rec_trees = recsToTrees recs header_boxes = createHeaderCols rec_trees data_boxes = createDataBoxes rec_trees vertical_boxes = fmap (\(a, b) -> B.vsep 0 B.top $ [a, b]) $ L.zip header_boxes data_boxes -- testing data T = C1 { aInt::Double, aString::String} deriving (Data, Typeable, Show,G.Generic) data T1 = C2 { t1:: T, bInt::Double, bString::String} deriving (Data, Typeable, Show, G.Generic) c1 = C1 1000 "record_c1fdsafaf" c2 = C2 c1 100.12121 "record_c2" c3 = C2 c1 1001.12111 "record_c2fdsafdsafsafdsafasfa" c4 = C2 c1 22222.12121 "r" instance Tabulate T ExpandWhenNested instance Tabulate T1 ExpandWhenNested instance CellValueFormatter T data R2 = R2 {a::Maybe Integer} deriving (G.Generic, Show) data R3 = R3 {r31::Maybe Integer, r32::String} deriving (G.Generic, Show) tr = Node "root" (toTree . G.from $ c2) r2 = Node "root" (toTree . G.from $ (R2 (Just 10))) r3 = Node "root" (toTree . G.from $ (R3 (Just 10) "r3_string")) -- | Used with 'printTableWithFlds' data DisplayFld a = forall s. CellValueFormatter s => DFld (a->s) -- printTableWithFlds2 :: [DisplayFld t] -> V.Vector t -> IO () -- printTableWithFlds2 flds recs = B.printBox $ printTableWithFlds flds $ V.toList recs -- printTableWithFlds3 :: (CellValueFormatter k) => [DisplayFld t] -> Map.Map k t -> IO () -- printTableWithFlds3 flds recs = results where -- data_cols = printTableWithFlds flds $ Map.elems recs -- index_cols = B.vsep 0 B.top $ fmap (B.text . ppFormatter) $ Map.keys recs -- vertical_cols = B.hsep 5 B.top [index_cols, data_cols] -- results = B.printBox vertical_cols