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