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