{-# LANGUAGE DefaultSignatures #-} {-# LANGUAGE TypeOperators #-} {-# LANGUAGE FlexibleInstances #-} {-# LANGUAGE FlexibleContexts #-} module Text.PrettyPrint.Records (FQuery, VQuery, RFmt, format, formatUntil, fields, values, dfltFmt, simpleFmt, tableFmt, Format(..), TableFmt(..), formatTable) where import GHC.Generics import Data.Typeable (cast, Typeable) import Text.PrettyPrint.Boxes ((<+>), vcat, text, Box(), left, hsep, top) import Data.List (transpose) --- FQuery ------------------------------------------------------------------- class FQuery' f where fields' :: f p -> [String] instance FQuery' V1 where fields' _ = [] instance FQuery' U1 where fields' _ = [] instance FQuery c => FQuery' (Rec0 c) where fields' _ = [] instance (FQuery' f, FQuery' g) => FQuery' (f :*: g) where fields' (x :*: y) = fields' x <> fields' y instance FQuery' f => FQuery' (D1 c f) where fields' (M1 x) = fields' x instance FQuery' f => FQuery' (C1 c f) where fields' (M1 x) = fields' x instance Selector s => FQuery' (S1 s f) where fields' x = [selName x] -- | Accumulate Record accessors as [String] (in order of definition). class FQuery a where fields :: a -> [String] default fields :: (Generic a, FQuery' (Rep a)) => a -> [String] fields = fields' . from -- VQuery ---------------------------------------------------------------------- class VQuery' f where values' :: f p -> [String] instance VQuery' f => VQuery' (M1 t c f) where values' (M1 x) = values' x instance (VQuery' f, VQuery' g) => VQuery' (f :*: g) where values' (x :*: y) = values' x <> values' y instance (Show c) => VQuery' (Rec0 c) where values' K1{unK1=v} = [show v] instance VQuery' U1 where values' _ = [] instance VQuery' V1 where values' _ = [] -- | Accumulate Record values as [String] (in order of definition). class VQuery a where values :: a -> [String] default values :: (Generic a, VQuery' (Rep a)) => a -> [String] values = values' . from -- RFmt ------------------------------------------------------------------------ -- |A Format is responsible for converting a Record into a formatted Box. -- -- @ -- __Example:__ -- dfltFmt :: Format a -- dfltFmt = Format -- { arg = text -- , label = \\a b -> text (a <> ":") <+> b -- , finally = vcat left } -- @ -- data Format a = Format { -- | Converts Record fields to Boxes arg :: String -> Box, -- | Merge accessor with field label :: String -> Box -> Box, -- | Finally reduce list of boxes finally :: [Box] -> Box } class RFmt' f where fvalues' :: (Typeable a, RFmt a) => Int -> f p -> Format a -> [Box] -- |Reduce Record to Box given a Format -- -- @ -- -- __Example:__ -- data List a = List { label :: String, val :: a, tail :: List a } -- | Nil deriving (Generic, Show) -- instance FQuery (List a) -- instance (Show a, Typeable a) => RFmt (List a) -- test = List "head" 3 $ List "mid" 4 $ List "tail" 5 $ Nil -- -- ghci> printBox $ format test dfltFmt -- label: "head" -- val: 3 -- tail: label: "mid" -- val: 4 -- tail: label: "tail" -- val: 5 -- tail: Nil -- @ -- class (Typeable a, FQuery a, Show a) => RFmt a where fvalues :: Int -> a -> Format a -> [Box] default fvalues :: (Generic a, RFmt' (Rep a)) => Int -> a -> Format a -> [Box] fvalues 0 _ _ = [text "....."] fvalues n a f = fvalues' n (from a) f instance RFmt' f => RFmt' (M1 t c f) where fvalues' n (M1 x) = fvalues' n x instance (RFmt' f, RFmt' g) => RFmt' (f :*: g) where fvalues' n (x :*: y) f = let l = fvalues' n x f r = fvalues' n y f in l <> r instance (Typeable c, Show c) => RFmt' (Rec0 c) where fvalues' n K1{unK1=v} f = let g :: Typeable k => Format k -> Maybe k g _ = cast v in case g f of Nothing -> [arg f . show $ v] Just x -> [formatUntil (n - 1) x f] instance RFmt' U1 where fvalues' _ _ _ = [] instance RFmt' V1 where fvalues' _ _ _ = [] -- Helpers --------------------------------------------------------------------- data TableFmt = TableFmt { -- | Converts Record fields to Boxes argT :: String -> Box, -- | Merge accessor with field labelT :: String -> [Box] -> Box, -- | Finally reduce list of boxes finallyT :: [Box] -> Box } dfltFmt :: Format a dfltFmt = Format { arg = text , label = \a b -> text (a <> ":") <+> b , finally = vcat left } -- | A table formatter which produces a top-down table tableFmt :: TableFmt tableFmt = TableFmt { argT = text , labelT = \a bs -> vcat left $ text a : bs , finallyT = hsep 1 top } -- | Given String representation of accessor and value return b simpleFmt :: (VQuery a, FQuery a) => (String -> String -> b) -> a -> [b] simpleFmt f x = zipWith f (fields x) (values x) -- | Format Record a with up to n levels of recursivity formatUntil :: RFmt a => Int -> a -> Format a -> Box formatUntil n a f = let lhs = fields a rhs = fvalues n a f in if null rhs then arg f . show $ a else finally f $ zipWith (label f) lhs rhs -- | Format Record a record fully expanding the data structure if it is -- recursive format :: RFmt a => a -> Format a -> Box format = formatUntil (-1) -- | Format a list of RFmt instances as a table (no recursive expansion). -- -- @ -- __Example:__ -- data Employee = E -- { eId :: Int -- , name :: String -- , email :: String } deriving (Generic, Show) -- instance FQuery Employee -- instance RFmt Employee -- e1 = E 3 \"John\" "johnbelcher@foobar.xyz" -- e1 = E 3 \"John\" "johnbelcher@foobar.xyz" -- e2 = E 17 \"Maria\" "Mariafoobar@net.net" -- e3 = E 1 \"Stanley\" "stanleytheceo@foobar.org" -- e4 = E 2 \"Kayla\" "klabar@foo.com" -- e5 = E 4 \"Sammy\" "sammersfoobar@foo.bar" -- -- ghci> printBox $ formatTable [e1, e2, e3, e4, e5] tableFmt -- eId name email -- 3 \"John\" "johnbelcher@foobar.xyz" -- 17 \"Maria\" "Mariafoobar@net.net" -- 1 \"Stanley\" "stanleytheceo@foobar.org" -- 2 \"Kayla\" "klabar@foo.com" -- 4 \"Sammy\" "sammersfoobar@foo.bar" -- @ -- formatTable :: RFmt a => [a] -> TableFmt -> Box formatTable as t = let minFmt = Format (argT t) undefined (finallyT t) header = fields (head as) fvals = transpose $ flip (fvalues 1) minFmt <$> as in finallyT t $ zipWith (labelT t) header fvals