pptable-0.1.0.1: src/Text/PrettyPrint/Tabilize.hs
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE Rank2Types #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE DefaultSignatures #-}
{-# LANGUAGE TypeOperators #-}
{-# LANGUAGE ConstrainedClassMethods #-}
-- | Module implements the default methods for Tabilize
module Text.PrettyPrint.Tabilize
(
Tabilize
, printList
, printMap
, printVector
, listToBox
, mapToBox
, vectorToBox
)where
import Data.Data
import Data.Typeable
import Data.Generics.Aliases
import GHC.Generics as G
import GHC.Show
import qualified Data.Map as Map
import qualified Text.PrettyPrint.Boxes as B
import qualified Data.List as List
import Text.Printf
import qualified Data.Vector as V
-- | The Generalized class that provides the print
-- | functionality for any type that derives Generics and Data
class GTabilize f where
gprintTable :: f a -> [B.Box]
--- | The instance class for Unit type
instance GTabilize U1 where
gprintTable _ = []
-- | Any records or product types
-- | Nested algebraic types are printed using their respective Show methods
instance (GTabilize a, GTabilize b) => GTabilize (a :*: b) where
gprintTable (a :*: b) = gprintTable a ++ gprintTable b
-- | Sum types
instance (GTabilize a, GTabilize b) => GTabilize (a :+: b) where
gprintTable (L1 x) = gprintTable x
gprintTable (R1 x) = gprintTable x
-- |
instance (GTabilize a) => GTabilize (M1 i c a) where
gprintTable (M1 x) = gprintTable x
-- | The leaf method that actually creates the Boxes that will
-- | be used to render the boxes as a table.
instance (Data a, Show a) => GTabilize (K1 i a) where
gprintTable (K1 x) = createBox x
-- | Create the B.Box around the provided value
-- | If the value is an algebraic type the algebraic types
-- | Show method is used. All values have to be an instance of
-- | Data. This dependency is there to support identify Numeric
-- | values for right-aligning those values
createBox :: (Data x, Show x) => x -> [B.Box]
createBox x
| isBool = [B.text $ show x]
| isNumeric = [B.text $ printf "%10s" . show $ x]
| otherwise = [B.text $ show x]
where
isBool = get_type == "Bool"
isNumeric = get_type == "Integer" || get_type == "Double" || get_type == "Float"
get_type = tyConName . typeRepTyCon . typeOf $ x
-- | 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
-- | Helper method that detects an algebraic data type
isAlgRepConstr t = case dataTypeRep . dataTypeOf $ t of
AlgRep [_] -> True
_ -> False
-- | Specialized class that provides default methods
-- methods to print List, Map or Vector values as a
-- pretty table
class (Data a) => Tabilize a where
-- | Return a list of values wrapped in a Box. Each entry in input is assumed to be a
-- list of records keyed by any data type. The first entry in the
-- list of values will be used to infer the names of fields
listToBox :: [a] -> [[B.Box]]
-- | Return a list of values wrapped in Box. Each entry in input is assumed to be a
-- list of records keyed by any data type. The first entry in the
-- list of values will be used to infer the names of fields
mapToBox ::(Show b) => Map.Map b a -> [[B.Box]]
-- | Return a list of values wrapped in Box. Each entry in input is assumed to be a
-- list of records keyed by any data type. The first entry in the
-- list of values will be used to infer the names of fields
vectorToBox :: V.Vector a -> [[B.Box]]
default listToBox :: (G.Generic a, GTabilize (Rep a)) => [a] -> [[B.Box]]
listToBox a = case a of
[] -> [[B.nullBox]]
x:xs -> gprintTable (from x):listToBox xs
default mapToBox :: (G.Generic a, GTabilize (Rep a), Show b) => Map.Map b a -> [[B.Box]]
mapToBox m = Map.elems
(Map.mapWithKey
(\k v -> B.text (show k):gprintTable (from v))
m)
default vectorToBox :: (G.Generic a, GTabilize (Rep a)) => V.Vector a -> [[B.Box]]
vectorToBox v = V.toList $ fmap (\x -> (gprintTable (from x))) v
-- |
-- > import qualified Data.Map as M
-- > -- declare a Map
-- > data Portfolio = M.Map String Stock
-- > Add the Stock values we create
-- > let p = M.fromList [("YHOO", yahoo), ("GOOG", google), ("AMZN" amazon)]
-- >
-- > printMap p
-- >
-- > Key ticker price marketCap
-- > "amzn" "AMZN" 799.161717 3.7886e11
-- > "goog" "GOOG" 774.210101 5.3209e11
-- > "yhoo" "YHOO" 42.2910101 4.0e10
printMap :: (Show b) => Map.Map b a -> IO ()
printMap 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:mapToBox m
-- |
-- > -- List of records
-- > let tickers = [yahoo, google, amazon]
-- >
-- > printList tickers
-- >
-- > ticker price marketCap
-- > "YHOO" 42.2910101 4.0e10
-- > "GOOG" 774.210101 5.3209e11
-- > "AMZN" 799.161717 3.7886e11
printList :: [a] -> IO ()
printList m = do
let r = head $ m
let header = constrFields . toConstr $ r
let header_box = List.map (B.text) header
B.printBox $ alignBox $ header_box:listToBox m
-- |
-- > import qualified Data.Vector as V
-- > -- Vector of records
-- > let tickers = V.fromList [yahoo, google, amazon]
-- >
-- > printVector tickers
-- >
-- > ticker price marketCap
-- > "YHOO" 42.2910101 4.0e10
-- > "GOOG" 774.210101 5.3209e11
-- > "AMZN" 799.161717 3.7886e11
printVector :: V.Vector a -> IO ()
printVector 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:vectorToBox m
-- print :: (Functor f) => f a -> f [B.Box]
-- default ppTabFL :: (G.Generic a, Tabilize (Rep a), Functor f) => (f a) -> f [B.Box]
-- ppTabFL f = fmap (\x -> (gprintTable (from x))) f