pptable 0.2.0.0 → 0.3.0.0
raw patch · 6 files changed
+681/−343 lines, 6 filesdep +markdown-unlitdep ~basePVP ok
version bump matches the API change (PVP)
Dependencies added: markdown-unlit
Dependency ranges changed: base
API changes (from Hackage documentation)
- Text.PrettyPrint.Tabulate: instance (Text.PrettyPrint.Tabulate.GTabulate a, Text.PrettyPrint.Tabulate.GTabulate b) => Text.PrettyPrint.Tabulate.GTabulate (a GHC.Generics.:*: b)
- Text.PrettyPrint.Tabulate: instance (Text.PrettyPrint.Tabulate.GTabulate a, Text.PrettyPrint.Tabulate.GTabulate b) => Text.PrettyPrint.Tabulate.GTabulate (a GHC.Generics.:+: b)
- Text.PrettyPrint.Tabulate: instance GHC.Show.Show k => Text.PrettyPrint.Tabulate.Boxable (Data.Map.Base.Map k)
- Text.PrettyPrint.Tabulate: instance Text.PrettyPrint.Tabulate.CellValueFormatter a => Text.PrettyPrint.Tabulate.GTabulate (GHC.Generics.K1 i a)
- Text.PrettyPrint.Tabulate: instance Text.PrettyPrint.Tabulate.GTabulate GHC.Generics.U1
- Text.PrettyPrint.Tabulate: instance Text.PrettyPrint.Tabulate.GTabulate a => Text.PrettyPrint.Tabulate.GTabulate (GHC.Generics.M1 i c a)
- Text.PrettyPrint.Tabulate: ppTable :: (Tabulate a, Boxable f, Generic a, GTabulate (Rep a)) => f a -> IO ()
- Text.PrettyPrint.Tabulate: toBox :: (Boxable b, Data a, Generic a, GTabulate (Rep a)) => b a -> [[Box]]
+ Text.PrettyPrint.Tabulate: DFld :: (a -> s) -> DisplayFld a
+ Text.PrettyPrint.Tabulate: data DisplayFld a
+ Text.PrettyPrint.Tabulate: data DoNotExpandWhenNested
+ Text.PrettyPrint.Tabulate: data ExpandWhenNested
+ Text.PrettyPrint.Tabulate: instance (GHC.Generics.Generic a, Text.PrettyPrint.Tabulate.GRecordMeta (GHC.Generics.Rep a)) => Text.PrettyPrint.Tabulate.RecordMeta' Text.PrettyPrint.Tabulate.ExpandWhenNested a
+ Text.PrettyPrint.Tabulate: instance (GHC.Show.Show a, Text.PrettyPrint.Tabulate.CellValueFormatter a) => Text.PrettyPrint.Tabulate.CellValueFormatter (GHC.Base.Maybe a)
+ Text.PrettyPrint.Tabulate: instance (Text.PrettyPrint.Tabulate.CellValueFormatter a, Data.Data.Data a, Text.PrettyPrint.Tabulate.RecordMeta a) => Text.PrettyPrint.Tabulate.GRecordMeta (GHC.Generics.K1 i a)
+ Text.PrettyPrint.Tabulate: instance (Text.PrettyPrint.Tabulate.GRecordMeta a, GHC.Generics.Constructor c) => Text.PrettyPrint.Tabulate.GRecordMeta (GHC.Generics.M1 GHC.Generics.C c a)
+ Text.PrettyPrint.Tabulate: instance (Text.PrettyPrint.Tabulate.GRecordMeta a, GHC.Generics.Selector s) => Text.PrettyPrint.Tabulate.GRecordMeta (GHC.Generics.M1 GHC.Generics.S s a)
+ Text.PrettyPrint.Tabulate: instance (Text.PrettyPrint.Tabulate.GRecordMeta a, Text.PrettyPrint.Tabulate.GRecordMeta b) => Text.PrettyPrint.Tabulate.GRecordMeta (a GHC.Generics.:*: b)
+ Text.PrettyPrint.Tabulate: instance (Text.PrettyPrint.Tabulate.GRecordMeta a, Text.PrettyPrint.Tabulate.GRecordMeta b) => Text.PrettyPrint.Tabulate.GRecordMeta (a GHC.Generics.:+: b)
+ Text.PrettyPrint.Tabulate: instance (Text.PrettyPrint.Tabulate.Tabulate a flag, Text.PrettyPrint.Tabulate.RecordMeta' flag a) => Text.PrettyPrint.Tabulate.RecordMeta a
+ Text.PrettyPrint.Tabulate: instance Data.Data.Data Text.PrettyPrint.Tabulate.T
+ Text.PrettyPrint.Tabulate: instance Data.Data.Data Text.PrettyPrint.Tabulate.T1
+ Text.PrettyPrint.Tabulate: instance GHC.Generics.Generic Text.PrettyPrint.Tabulate.R2
+ Text.PrettyPrint.Tabulate: instance GHC.Generics.Generic Text.PrettyPrint.Tabulate.R3
+ Text.PrettyPrint.Tabulate: instance GHC.Generics.Generic Text.PrettyPrint.Tabulate.T
+ Text.PrettyPrint.Tabulate: instance GHC.Generics.Generic Text.PrettyPrint.Tabulate.T1
+ Text.PrettyPrint.Tabulate: instance GHC.Show.Show Text.PrettyPrint.Tabulate.R2
+ Text.PrettyPrint.Tabulate: instance GHC.Show.Show Text.PrettyPrint.Tabulate.R3
+ Text.PrettyPrint.Tabulate: instance GHC.Show.Show Text.PrettyPrint.Tabulate.T
+ Text.PrettyPrint.Tabulate: instance GHC.Show.Show Text.PrettyPrint.Tabulate.T1
+ Text.PrettyPrint.Tabulate: instance GHC.Show.Show Text.PrettyPrint.Tabulate.Tag
+ Text.PrettyPrint.Tabulate: instance Text.PrettyPrint.Tabulate.CellValueFormatter Text.PrettyPrint.Tabulate.T
+ Text.PrettyPrint.Tabulate: instance Text.PrettyPrint.Tabulate.CellValueFormatter a => Text.PrettyPrint.Tabulate.RecordMeta' Text.PrettyPrint.Tabulate.DoNotExpandWhenNested a
+ Text.PrettyPrint.Tabulate: instance Text.PrettyPrint.Tabulate.CellValueFormatter k => Text.PrettyPrint.Tabulate.Boxable (Data.Map.Internal.Map k)
+ Text.PrettyPrint.Tabulate: instance Text.PrettyPrint.Tabulate.GRecordMeta GHC.Generics.U1
+ Text.PrettyPrint.Tabulate: instance Text.PrettyPrint.Tabulate.GRecordMeta a => Text.PrettyPrint.Tabulate.GRecordMeta (GHC.Generics.M1 GHC.Generics.D c a)
+ Text.PrettyPrint.Tabulate: instance Text.PrettyPrint.Tabulate.Tabulate Text.PrettyPrint.Tabulate.T Text.PrettyPrint.Tabulate.ExpandWhenNested
+ Text.PrettyPrint.Tabulate: instance Text.PrettyPrint.Tabulate.Tabulate Text.PrettyPrint.Tabulate.T1 Text.PrettyPrint.Tabulate.ExpandWhenNested
+ Text.PrettyPrint.Tabulate: instance flag ~ Text.PrettyPrint.Tabulate.DoNotExpandWhenNested => Text.PrettyPrint.Tabulate.Tabulate a flag
+ Text.PrettyPrint.Tabulate: printTableWithFlds :: Boxable b => [DisplayFld t] -> b t -> IO ()
+ Text.PrettyPrint.Tabulate: renderTable :: (Boxable b, Generic a, GRecordMeta (Rep a)) => b a -> Box
+ Text.PrettyPrint.Tabulate: renderTableWithFlds :: Boxable b => [DisplayFld t] -> b t -> Box
- Text.PrettyPrint.Tabulate: class CellValueFormatter a where ppFormatter x = show x ppFormatterWithStyle _ x = "default_" ++ show x
+ Text.PrettyPrint.Tabulate: class CellValueFormatter a
- Text.PrettyPrint.Tabulate: class (Data a) => Tabulate a where ppTable x = printTable x
+ Text.PrettyPrint.Tabulate: class Tabulate a flag | a -> flag
- Text.PrettyPrint.Tabulate: printTable :: (Boxable b, Data a, Generic a, GTabulate (Rep a)) => b a -> IO ()
+ Text.PrettyPrint.Tabulate: printTable :: (Boxable b, Generic a, GRecordMeta (Rep a)) => b a -> IO ()
Files
- README.md +124/−40
- pptable.cabal +23/−5
- src/Text/PrettyPrint/Tabulate.hs +336/−97
- src/Text/PrettyPrint/Tabulate/Example.hs +0/−180
- test/README.lhs +151/−0
- test/Spec.hs +47/−21
README.md view
@@ -1,67 +1,151 @@ # README # -Text.PrettyPrint.Tabulate : Print any list, vector or map as a well-formatted readable table.+Textme.PrettyPrint.Tabulate : Print any list, vector or map of records as a well-formatted readable table. Nested record structures are printed with hierarchically arranged headers, thus clearly showing the nested structures. ### Text.PrettyPrint.Tabulate ### * This module provides simple functions used to print values in tabular format-* Version 0.2.0.0+* Version 0.3.0.0 * Contributions and Bug Reports welcome. Please use the Github issue tracker. +### Release Notes 0.3.0.0 (*This release has breaking changes*)++* This release has some breaking changes which was required to extend+ functionality. The extended functionality improves on previous+ release by printing nested records with hierarchical column headers++* All Records types need to update their `Tabulate` instances to provide a flag, namely `ExpandWhenNested` or `DoNotExpandWhenNested`.++* `ppTable` function has been renamed to `printTable` to make the function name more descriptive.++* New function called printTableWithFlds has been added.+ ### Example ### ``` haskell - -- ./Example.hs- :set -XDeriveGeneric- :set -XDeriveDataTypeable+ {-# LANGUAGE MultiParamTypeClasses#-}+ {-# LANGUAGE DeriveGeneric #-}+ {-# LANGUAGE DeriveDataTypeable #-} - import qualified GHC.Generics as G- import Data.Data+ import Text.PrettyPrint.Tabulate+ import qualified GHC.Generics as G+ import Data.Data - import qualified Text.PrettyPrint.Tabulate as T- import qualified Data.Map as Map- import qualified Data.List as List- import qualified Data.Vector as Vector+ import qualified Text.PrettyPrint.Tabulate as T + import qualified Data.Map as Map+ import qualified Data.List as List+ import qualified Data.Vector as Vector - data Stock = Stock {ticker::String, price::Double, marketCap::Double} deriving (Data, G.Generic)- instance T.Tabulate Stock+``` - let yahoo = Stock {ticker="YHOO", price=42.29101010, marketCap=40e9}- let google = Stock {ticker="GOOG", price=774.210101, marketCap=532.09e9}- let amazon = Stock {ticker="AMZN", price=799.161717, marketCap=378.86e9}+#### Instance Declaration Requirements #### +1. All records definitions should `derive` from `Data` and `Generic`+2. All field types that are record attributes should have an instance of `CellValueFormatter`. Default instances for standard types are already provided.+3. All records that need to be printed as a table need to be an instance of `Tabulate`.+4. Depending on if the nested record's fields have to be expanded, the+ Tabulate instance could either be - -- List of records- let tickers = [yahoo, google, amazon]+ `instance Tabulate Price ExpandWhenNested` or+ `instance Tabulate Price DoNotExpandWhenNested`. - -- The record type 'Stock' can also be in a Map- let tickers_map = Map.fromList [(10, yahoo), (100, google), (1000, amazon)]+``` haskell - -- Or in a Vector- let tickers_vector = Vector.fromList tickers+ data FxCode = USD | EUR | JPY deriving (Show, Data, G.Generic)+ instance T.CellValueFormatter FxCode - -- Print table from List- T.ppTable tickers- ticker price marketCap- YHOO 42.291010100 4.000000000e10- GOOG 774.210101000 5.320900000e11- AMZN 799.161717000 3.788600000e11+ -- This record type will be nested inside `Stock`+ data Price = Price {price::Double, fx_code::FxCode} deriving (Data, G.Generic, Show) - -- Print table from Map- T.ppTable tickers_map- Key ticker price marketCap- 10 YHOO 42.291010100 4.000000000e10- 100 GOOG 774.210101000 5.320900000e11- 1000 AMZN 799.161717000 3.788600000e11+ -- if we do not want the `Price` records to be expanded into their own fields+ -- then choose `T.DoNotExpandWhenNested`+ instance T.Tabulate Price T.ExpandWhenNested+ instance T.CellValueFormatter Price + data Stock = Stock {ticker::String, local_price::Price, marketCap::Double} deriving (+ Data, G.Generic, Show)+ instance T.Tabulate Stock T.ExpandWhenNested - -- Print table from Vector- T.ppTable tickers_vector- ticker price marketCap- YHOO 42.291010100 4.000000000e10- GOOG 774.210101000 5.320900000e11- AMZN 799.161717000 3.788600000e11+```+Once we have the records and required instances created, we can see how+the created records can be viewed in the tabular format.++``` haskell++ yahoo = Stock {ticker="YHOO", local_price=Price 42.29101010 USD, marketCap=40e9}+ google = Stock {ticker="GOOG", local_price=Price 774.210101 EUR, marketCap=532.09e9}+ amazon = Stock {ticker="AMZN", local_price=Price 799.161717 JPY, marketCap=378.86e9}++ tickers = [yahoo, google, amazon]+ tickers_vector = Vector.fromList tickers+ tickers_map:: Map.Map Integer Stock+ tickers_map = Map.fromList [(10, yahoo), (100, google), (1000, amazon)]++ printExamples:: IO ()+ printExamples = do+ putStrLn "Printing records in a list\n"+ T.printTable tickers++ putStrLn "\nPrinting records in a map with the index.\nNote the `key(s)` are printed as first columns"+ T.printTable tickers_map++ putStrLn "\nPrinting records in a vector\n"+ T.printTable tickers_vector++ -- Sometimes records may have too many fields. In those case, specific fields can+ -- be chosen to be printed. Currently, support for this functionality is+ -- minimal. The 'headers` are not printed. In the future, a function that+ -- can take header labels as a list will be provided.++ putStrLn "\nPrinting specific fields. Note, currently field names are not printed"+ T.printTableWithFlds [T.DFld (price . local_price), T.DFld ticker] tickers_map++ putStrLn "\nPrint nested record in a map, individually"+ T.printTable $ fmap local_price tickers_map++```++### Print the examples ###++``` haskell++ main:: IO ()+ main = do+ printExamples+```++### Output ###++``` haskell ignore+ Printing records in a list++ ticker local_price local_price marketCap+ - price fx_code -+ YHOO 42.2910101 USD 4.0000000e10+ GOOG 774.2101010 EUR 5.3209000e11+ AMZN 799.1617170 JPY 3.7886000e11++ Printing records in a map with the index (Note the `key` is printed as the first column)++ - ticker local_price local_price marketCap+ - - price fx_code -+ 10 YHOO 42.2910101 USD 4.0000000e10+ 100 GOOG 774.2101010 EUR 5.3209000e11+ 1000 AMZN 799.1617170 JPY 3.7886000e11++ Printing records in a vector++ ticker local_price local_price marketCap+ - price fx_code -+ YHOO 42.2910101 USD 4.0000000e10+ GOOG 774.2101010 EUR 5.3209000e11+ AMZN 799.1617170 JPY 3.7886000e11++ Printing specific fields. Note, currently field names are not printed+ 10 42.2910101 YHOO+ 100 774.2101010 GOOG+ 1000 799.1617170 AMZN ```
pptable.cabal view
@@ -1,7 +1,7 @@ name: pptable-version: 0.2.0.0+version: 0.3.0.0 synopsis: Pretty Print containers in a tabular format-description: Please see README.md+description: When you are faced with tens of records data types contained in a list or similar structure if becomes difficult to view all records during iterative development. This library provides a generic funciton to print any such record types in a tabular format that makes visualizing the data more pleasing. Please see README.md for examples of this. homepage: https://github.com/gdevanla/pptable#readme license: MIT license-file: LICENSE@@ -14,9 +14,9 @@ cabal-version: >=1.10 library- hs-source-dirs: src- exposed-modules: Text.PrettyPrint.Tabulate, Text.PrettyPrint.Tabulate.Example- build-depends: base >= 4.7 && < 5+ hs-source-dirs: src+ exposed-modules: Text.PrettyPrint.Tabulate+ build-depends: base >= 4.7 && < 5 , syb , containers , pretty@@ -41,6 +41,24 @@ , boxes ghc-options: -threaded -rtsopts -with-rtsopts=-N default-language: Haskell2010++test-suite readme+ type: exitcode-stdio-1.0+ hs-source-dirs: test+ main-is: README.lhs+ build-depends: base+ , pptable+ , tasty+ , HUnit+ , QuickCheck+ , tasty-hunit+ , tasty-quickcheck+ , containers+ , vector+ , boxes+ , markdown-unlit+ ghc-options: -threaded -rtsopts -with-rtsopts=-N -pgmL markdown-unlit+ default-language: Haskell2010 source-repository head type: git
src/Text/PrettyPrint/Tabulate.hs view
@@ -1,3 +1,9 @@+{-# LANGUAGE StandaloneDeriving #-}+{-# LANGUAGE DeriveGeneric #-} -- Remove this+{-# LANGUAGE DeriveDataTypeable #-} -- Remove this+{-# LANGUAGE UndecidableInstances #-}+{-# LANGUAGE GADTs #-}+{-# LANGUAGE FunctionalDependencies #-} {-# LANGUAGE FlexibleInstances #-} {-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE Rank2Types #-}@@ -6,18 +12,37 @@ {-# 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- - )where+ (+ 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@@ -25,6 +50,7 @@ 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 @@ -42,48 +68,83 @@ intValueFormat=Nothing, doubleValueFormat=Nothing} +data Tag = Constr | Fields | Values deriving (Show) --- | The Generalized class that implements the print feature--- for any type that derives Generics and Data-class GTabulate f where- -- | Print with default style- gprintTable :: f a -> [B.Box]+class GRecordMeta f where+ toTree:: f a -> [Tree String] - -- | For future, will be able to print with provided style- gprintTableWithStyle :: TablizeValueFormat -> f a -> [B.Box]+instance GRecordMeta U1 where+ toTree U1 = [] ---- | The instance class for Unit type-instance GTabulate U1 where- gprintTable _ = []- gprintTableWithStyle _ _ = []+instance (GRecordMeta (a), GRecordMeta (b)) => GRecordMeta (a :*: b) where+ toTree (x :*: y) = (toTree x) ++ (toTree y) --- | Any records or product types--- | Nested algebraic types are printed using their respective Show methods-instance (GTabulate a, GTabulate b) => GTabulate (a :*: b) where- gprintTable (a :*: b) = gprintTable a ++ gprintTable b- gprintTableWithStyle style (a :*: b) = gprintTableWithStyle style a ++ gprintTableWithStyle style b+instance (GRecordMeta (a), GRecordMeta (b)) => GRecordMeta (a :+: b) where+ toTree x = toTree x --- | Sum types-instance (GTabulate a, GTabulate b) => GTabulate (a :+: b) where- gprintTable (L1 x) = gprintTable x- gprintTable (R1 x) = gprintTable x+instance (GRecordMeta a, Selector s) => GRecordMeta (M1 S s a) where+ toTree a = [Node (selName a) $ toTree (unM1 a)] where - gprintTableWithStyle style (L1 x) = gprintTableWithStyle style x- gprintTableWithStyle style (R1 x) = gprintTableWithStyle style x+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 (GTabulate a) => GTabulate (M1 i c a) where- gprintTable (M1 x) = gprintTable x- gprintTableWithStyle style (M1 x) = gprintTableWithStyle style x+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 --- | The leaf method that actually creates the Boxes that will--- be used to render the boxes as a table.-instance (CellValueFormatter a) => GTabulate (K1 i a) where- gprintTable (K1 x) = [B.text $ ppFormatter x]- gprintTableWithStyle style (K1 x) = [B.text $ ppFormatterWithStyle style 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@@ -108,7 +169,7 @@ instance CellValueFormatter Integer where ppFormatter x = printf "%d" x- + ppFormatterWithStyle style x = case integerValueFormat style of Just f -> f x Nothing -> ppFormatter x@@ -122,7 +183,7 @@ instance CellValueFormatter Float where- ppFormatter x = printf "%14.9g" x+ ppFormatter x = printf "%14.7g" x ppFormatterWithStyle style x = case floatValueFormat style of Just f -> f x@@ -137,7 +198,7 @@ instance CellValueFormatter Double where- ppFormatter x = printf "%14.9g" x+ ppFormatter x = printf "%14.7g" x ppFormatterWithStyle style x = case doubleValueFormat style of Just f -> f x@@ -145,78 +206,256 @@ instance CellValueFormatter Bool --- | Perform the final alignment of all--- | generated [[B.Box]]-alignBox :: [[B.Box]] -> B.Box-alignBox b = B.hsep 5 B.left cols- where- cols = List.map (B.vcat B.left) $ List.transpose b+instance (Show a, CellValueFormatter a) => CellValueFormatter (Maybe a) --- | Helper method that detects an algebraic data type-isAlgRepConstr t = case dataTypeRep . dataTypeOf $ t of- AlgRep [_] -> True- _ -> False --- | Class that can be derived by a 'Traversable' to create--- a list of 'Box' values and print as a Table.--- Default instances for List, Map and Vector are already provided.+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, GTabulate(Rep a)) => b a -> [[B.Box]]- --toBoxWithStyle :: (Data a, G.Generic a, GTabulate(Rep a)) => TablizeValueFormat -> b a -> [[B.Box]]- - printTable :: (Data a, G.Generic a, GTabulate(Rep a)) => b a -> IO ()+ -- 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- toBox a = case a of- [] -> [[B.nullBox]]- x:xs -> gprintTable (from x):toBox xs+ -- | 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 - -- | Prints a "List" as a table. Called by "ppTable"- -- | Need not be called directly- printTable m = do- let r = head $ m- let header = constrFields . toConstr $ r- let header_box = List.map (B.text) header- B.printBox $ alignBox $ header_box:toBox m+ renderTable m = ppRecords m - -instance Boxable V.Vector where- toBox v = V.toList $ fmap (\x -> (gprintTable (from x))) v+ -- | 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 - -- | Prints a "Vector" as a table. Called by "ppTable"++instance Boxable V.Vector where+ -- | Prints a "Vector" as a table. Called by "printTable". -- | Need not be called directly- printTable m = do- let r = m V.! 0- let header = constrFields . toConstr $ r- let header_box = List.map (B.text) header- B.printBox $ alignBox $ header_box:toBox m+ printTable m = B.printBox $ renderTable m --TODO: switch this to Vector+ renderTable m = ppRecords $ V.toList m -instance (Show k) => Boxable (Map.Map k) where- -- | Returns a Map of Boxed values- toBox m = Map.elems- (Map.mapWithKey- (\k v -> B.text (show k):gprintTable (from v))- 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 = do- let r = head . Map.elems $ m- let header = constrFields . toConstr $ r- let header_box = "Key":List.map (B.text) header- B.printBox $ alignBox $ header_box:toBox m+ printTable m = B.printBox $ renderTable m+ renderTable m = ppRecordsWithIndex m - --- | The elements in a Traverserable should be an instance of Tabulate to be displayed in a tabular format-class (Data a) => Tabulate a where+ -- | 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 - -- | Generic function that will be provided by the GTabulate class.- ppTable :: (Boxable f) => f a -> IO()- --ppTableWithStyle :: TablizeValueFormat -> f a -> IO()+ 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 - default ppTable :: (Boxable f, G.Generic a, GTabulate (Rep a)) => f a -> IO ()- ppTable x = printTable x+-- 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 - --default ppTableWithStyle :: (Boxable f, G.Generic a, GTabulate (Rep a)) => TablizeValueFormat -> f a -> IO ()- --ppTableWithStyle x = printTableWithStyle x+-- 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
− src/Text/PrettyPrint/Tabulate/Example.hs
@@ -1,180 +0,0 @@--- | Example usage of ppTable library---module Text.PrettyPrint.Tabulate.Example- (-- -- * Setup extensions- -- $setup_extensions- - -- * Required imports- -- $import_generics_and_data-- -- * Import 'Tabulate'- -- $import_tabulate-- -- * Use any 'Traversable' - -- $import_containers-- -- * Declare an instance of Tabulate- -- $declare_record-- -- * Sample Data- -- $create_sample_data-- -- * Printing the table- -- $print_table-- -- * Complete Example- -- $complete_example-- -- * Output- -- $output- - -- * Extending- -- ** Extending for any 'Traversable'- -- $boxable_instance- - -- ** Customizing formatting- -- $cell_value_formatter- --- ) where--import Text.PrettyPrint.Tabulate---- $setup_extensions--- > :set -XDeriveGeneric--- > :set -XDeriveDataTypeable---- $import_generics_and_data--- Generics and Data need to be imported since--- and type that will needs to be printed in tabular format--- needs to derive from Generic and Data------ > import qualified GHC.Generics as G--- > import Data.Data------- $import_tabulate--- Tabulate class and ppTable function are the only declarations--- to quickly using the library for printing in tabular format------ > import qualified Text.PrettyPrint.Tabulate as T---- $import_containers--- The data type that we will print in tabular format will have--- to be an element of a 'Traversable' instance. The ppTable library--- provides default instances for 'Data.Map', 'Data.Vector' and 'Data.List'.------ We import these modules here to provide examples of their use--- --- > import qualified Data.Map as Map--- > import qualified Data.List as List--- > import qualified Data.Vector as Vector------- $declare_record------ A record structure that will be an element in a 'Traversable' instance.--- The data type has to derive from 'Generic' and 'Data'. --- Also an default instance of 'Tabulate' is declared------ > data Stock = Stock {ticker::String, price::Double, marketCap::Double} deriving (Data, G.Generic)--- > instance T.Tabulate Stock------- $create_sample_data--- --- > yahoo = Stock {ticker="YHOO", price=42.29101010, marketCap=40e9}--- > google = Stock {ticker="GOOG", price=774.210101, marketCap=532.09e9}--- > amazon = Stock {ticker="AMZN", price=799.161717, marketCap=378.86e9}--- > -- List of records--- > tickers = [yahoo, google, amazon]--- >--- > -- The record type 'Stock' can also be in a Map--- > tickers_map = Map.fromList [(10, yahoo), (100, google), (1000, amazon)]--- >--- > -- Or in a Vector--- > tickers_vector = Vector.fromList tickers---- $print_table--- --- > putStrLn "\nElements in a List, with records fields as column names\n"--- > T.ppTable tickers--- >--- > putStrLn "\nElement in a map, with Key as first column\n"--- > T.ppTable tickers_map--- >--- > putStrLn "\nElement in a Vector\n"--- > T.ppTable tickers_vector--- - ---- $complete_example------ > -- ./Example.hs--- > :set -XDeriveGeneric--- > :set -XDeriveDataTypeable--- >--- > import qualified GHC.Generics as G--- > import Data.Data--- >--- > import qualified Text.PrettyPrint.Tabulate as T--- > import qualified Data.Map as Map--- > import qualified Data.List as List--- > import qualified Data.Vector as Vector--- >--- >--- > data Stock = Stock {ticker::String, price::Double, marketCap::Double} deriving (Data, G.Generic)--- > instance T.Tabulate Stock--- >--- > let yahoo = Stock {ticker="YHOO", price=42.29101010, marketCap=40e9}--- > let google = Stock {ticker="GOOG", price=774.210101, marketCap=532.09e9}--- > let amazon = Stock {ticker="AMZN", price=799.161717, marketCap=378.86e9}--- >--- >--- > -- List of records--- > let tickers = [yahoo, google, amazon]------ > -- The record type 'Stock' can also be in a Map--- > let tickers_map = Map.fromList [(10, yahoo), (100, google), (1000, amazon)]--- >--- > -- Or in a Vector--- > let tickers_vector = Vector.fromList tickers---- $output--- > -- Print table from List--- > T.ppTable tickers--- > ticker price marketCap--- > YHOO 42.291010100 4.000000000e10--- > GOOG 774.210101000 5.320900000e11--- > AMZN 799.161717000 3.788600000e11--- >--- > -- Print table from Map--- >T.ppTable tickers_map--- > Key ticker price marketCap--- > 10 YHOO 42.291010100 4.000000000e10--- > 100 GOOG 774.210101000 5.320900000e11--- > 1000 AMZN 799.161717000 3.788600000e11--- >--- >--- > -- Print table from Vector--- > T.ppTable tickers_vector--- > ticker price marketCap--- > YHOO 42.291010100 4.000000000e10--- > GOOG 774.210101000 5.320900000e11--- > AMZN 799.161717000 3.788600000e11--- >------ $boxable_instance--- Any 'Traversable' containers can be extended to work with ppTable by implementing--- an instance of 'Tabulate'---- $cell_value_formatter--- Default instances of 'CellValueFormatter' for 'Int', 'Integer', 'String', 'Float' and 'Double' are provided.--- To customize formatting of any types, an specific implementation of 'CellValueFormatter' can be provided.-
+ test/README.lhs view
@@ -0,0 +1,151 @@+# README #++Textme.PrettyPrint.Tabulate : Print any list, vector or map of records as a well-formatted readable table. Nested record structures are printed with hierarchically arranged headers, thus clearly showing the nested structures.++### Text.PrettyPrint.Tabulate ###++* This module provides simple functions used to print values in tabular format+* Version 0.3.0.0+* Contributions and Bug Reports welcome. Please use the Github issue tracker.++### Release Notes 0.3.0.0 (*This release has breaking changes*)++* This release has some breaking changes which was required to extend+ functionality. The extended functionality improves on previous+ release by printing nested records with hierarchical column headers++* All Records types need to update their `Tabulate` instances to provide a flag, namely `ExpandWhenNested` or `DoNotExpandWhenNested`.++* `ppTable` function has been renamed to `printTable` to make the function name more descriptive.++* New function called printTableWithFlds has been added.++### Example ###++``` haskell++ {-# LANGUAGE MultiParamTypeClasses#-}+ {-# LANGUAGE DeriveGeneric #-}+ {-# LANGUAGE DeriveDataTypeable #-}++ import Text.PrettyPrint.Tabulate+ import qualified GHC.Generics as G+ import Data.Data++ import qualified Text.PrettyPrint.Tabulate as T++ import qualified Data.Map as Map+ import qualified Data.List as List+ import qualified Data.Vector as Vector++```++#### Instance Declaration Requirements ####++1. All records definitions should `derive` from `Data` and `Generic`+2. All field types that are record attributes should have an instance of `CellValueFormatter`. Default instances for standard types are already provided.+3. All records that need to be printed as a table need to be an instance of `Tabulate`.+4. Depending on if the nested record's fields have to be expanded, the+ Tabulate instance could either be++ `instance Tabulate Price ExpandWhenNested` or+ `instance Tabulate Price DoNotExpandWhenNested`.++``` haskell++ data FxCode = USD | EUR | JPY deriving (Show, Data, G.Generic)+ instance T.CellValueFormatter FxCode++ -- This record type will be nested inside `Stock`+ data Price = Price {price::Double, fx_code::FxCode} deriving (Data, G.Generic, Show)++ -- if we do not want the `Price` records to be expanded into their own fields+ -- then choose `T.DoNotExpandWhenNested`+ instance T.Tabulate Price T.ExpandWhenNested+ instance T.CellValueFormatter Price++ data Stock = Stock {ticker::String, local_price::Price, marketCap::Double} deriving (+ Data, G.Generic, Show)+ instance T.Tabulate Stock T.ExpandWhenNested++```+Once we have the records and required instances created, we can see how+the created records can be viewed in the tabular format.++``` haskell++ yahoo = Stock {ticker="YHOO", local_price=Price 42.29101010 USD, marketCap=40e9}+ google = Stock {ticker="GOOG", local_price=Price 774.210101 EUR, marketCap=532.09e9}+ amazon = Stock {ticker="AMZN", local_price=Price 799.161717 JPY, marketCap=378.86e9}++ tickers = [yahoo, google, amazon]+ tickers_vector = Vector.fromList tickers+ tickers_map:: Map.Map Integer Stock+ tickers_map = Map.fromList [(10, yahoo), (100, google), (1000, amazon)]++ printExamples:: IO ()+ printExamples = do+ putStrLn "Printing records in a list\n"+ T.printTable tickers++ putStrLn "\nPrinting records in a map with the index.\nNote the `key(s)` are printed as first columns"+ T.printTable tickers_map++ putStrLn "\nPrinting records in a vector\n"+ T.printTable tickers_vector++ -- Sometimes records may have too many fields. In those case, specific fields can+ -- be chosen to be printed. Currently, support for this functionality is+ -- minimal. The 'headers` are not printed. In the future, a function that+ -- can take header labels as a list will be provided.++ putStrLn "\nPrinting specific fields. Note, currently field names are not printed"+ T.printTableWithFlds [T.DFld (price . local_price), T.DFld ticker] tickers_map++ putStrLn "\nPrint nested record in a map, individually"+ T.printTable $ fmap local_price tickers_map++```++### Print the examples ###++``` haskell++ main:: IO ()+ main = do+ printExamples+```++### Output ###++``` haskell ignore+ Printing records in a list++ ticker local_price local_price marketCap+ - price fx_code -+ YHOO 42.2910101 USD 4.0000000e10+ GOOG 774.2101010 EUR 5.3209000e11+ AMZN 799.1617170 JPY 3.7886000e11++ Printing records in a map with the index (Note the `key` is printed as the first column)++ - ticker local_price local_price marketCap+ - - price fx_code -+ 10 YHOO 42.2910101 USD 4.0000000e10+ 100 GOOG 774.2101010 EUR 5.3209000e11+ 1000 AMZN 799.1617170 JPY 3.7886000e11++ Printing records in a vector++ ticker local_price local_price marketCap+ - price fx_code -+ YHOO 42.2910101 USD 4.0000000e10+ GOOG 774.2101010 EUR 5.3209000e11+ AMZN 799.1617170 JPY 3.7886000e11++ Printing specific fields. Note, currently field names are not printed+ 10 42.2910101 YHOO+ 100 774.2101010 GOOG+ 1000 799.1617170 AMZN++```
test/Spec.hs view
@@ -2,7 +2,7 @@ {-# LANGUAGE DeriveGeneric #-} {-# LANGUAGE DeriveDataTypeable #-} {-# LANGUAGE ConstrainedClassMethods #-}-+{-# LANGUAGE MultiParamTypeClasses#-} import Data.Map as M import Data.List as L@@ -17,44 +17,70 @@ import Test.Tasty.HUnit -- R0 has to derive from Data, since--- it will be nested -data R0 = R0 {test_string::String,+-- it will be nested+data R01 = R01 {test_string::String, test_integer::Integer, test_float::Float, test_double::Double} deriving (Data, Show, G.Generic)--- data R01 = R01 {r1_id::Int, nested_r::R0}--- deriving (Show, G.Generic, Data)--- data R3 = R3 {r3_id::Int, nested_rlist::[R0]}--- deriving (Show, G.Generic, Data)--- data R4 = R4 {r4_id::Int, nested_rtuple::(R0,R0)}--- deriving (Show, G.Generic, Data)+instance T.CellValueFormatter R01 -instance T.Tabulate R0--- instance T.Tabulate R01--- instance T.Tabulate R3--- instance T.Tabulate R4+data R02 = R02 {r2_id::Int, nested_r::R01}+ deriving (Show, G.Generic, Data)+instance T.CellValueFormatter R02 -getR0 = R0 {test_string="Jack-somone"+data R03 = R03 {r3_id::Int, nested_r02:: R02}+ deriving (Show, G.Generic, Data)++instance T.Tabulate R01 T.ExpandWhenNested+instance T.Tabulate R02 T.ExpandWhenNested+instance T.Tabulate R03 T.ExpandWhenNested++getR01 = R01 {test_string="Jack-Jack" , test_integer=10 , test_double=10.101 , test_float=0.101021} +getR02 = R02 {r2_id=10, nested_r=getR01}++getR03 = R03 {r3_id=20, nested_r02=getR02}+ testList = testCase "testList" ( do- let b = (L.head . L.head) $ T.toBox [getR0]- assertEqual "check rows" (B.rows b) 1- assertEqual "check cols" (B.cols b) 11+ let records = Prelude.replicate 2 $ getR03+ rows = B.rows $ T.renderTable records+ cols = B.cols $ T.renderTable records+ assertEqual "row count" rows 5+ assertEqual "col count" cols 91 ) -testLists = testGroup "test printing lists" [- testList- ] +testMap = testCase "testMap"+ (+ do+ let records = M.fromList [("key1", getR03), ("key2", getR03)]+ rows = B.rows $ T.renderTable records+ cols = B.cols $ T.renderTable records+ assertEqual "row count" rows 5+ assertEqual "col count" cols 100+ )++testVector = testCase "testVector"+ (+ do+ let records = V.fromList [getR03, getR03]+ rows = B.rows $ T.renderTable records+ cols = B.cols $ T.renderTable records+ assertEqual "row count" rows 5+ assertEqual "col count" cols 91+ )+ tests :: TestTree tests = testGroup "Tests" [- testLists+ testList,+ testMap,+ testVector ] main = defaultMain tests