packages feed

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 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