pprecord-0.1.0.0: src/Text/PrettyPrint/Records.hs
{-# 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