{-# LANGUAGE TypeFamilies #-} {-# LANGUAGE MultiParamTypeClasses, UndecidableInstances #-} {-# LANGUAGE ScopedTypeVariables #-} {-# LANGUAGE FlexibleInstances #-} {-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE DataKinds #-} {- TODO: Can this be rewritten to be less reliant on Data.Typeable? Should be possible to just use it for getting the field type names, and not for parsing the entire type. TODO: Check that nothing is missing from the export list; only HaskRel's dependencies are known to be covered. TODO: New functions that did a tiny bit more than the last ones have been added haphazardly, clean up this and the users of this. -} {-| Module : HFWTabulation Description : Presentation of HList values in a two-dimensional fixed-width font form. Copyright : © Thor Michael Støre, 2015 License : GPL v2 without "any later version" clause Maintainer : thormichael át gmail døt com Stability : experimental HList fixed-width tabular presentation. Presentation of HList values in a two-dimensional, tabular fixed-width font form with a header consisting of labels and optionally types. Only records are supported by this module, see `TIPFWTabulation` for support for TIPs. -} module Database.HaskRel.HFWTabulation ( HFWPresent ( hfwPrint, hfwPrintTyped, hfwPrintTypedTS ), FWPresent ( fwPresent, fwPresentTyped ), FWPresent' ( fwPresent', fwPresentTyped' ), printHRecSetTab, printHRecSetTabTyped, printHRecSetTabTypedTS, showHRecSetTab, showTR, showTRTS, showHTypeTS, showHListSetType, HFWTIPSet, HFWTIP, HFWRec, HFWString, HFWOther, HListTypeSynonym ( hRecTS, hRecSetTS, hTIPTS, hTIPSetTS ), FWPPred, HPresentRecAttr(HPresentRecAttr), HPresentTypedRecAttr(HPresentTypedRecAttr) ) where import Data.HList.CommonMain import Data.Typeable import Data.Set ( Set, toList ) import Data.List ( intercalate ) import Database.HaskRel.FWTabulation -- Presentation of table heading -- data HFWTIPList -- <- TODO. Use ↑ to distinguish them from sets? Not really appropriate, it's not known to be ordered. [] in some fashion? data HFWTIPSet data HFWTIP -- data HFWRecList -- <- TODO data HFWRecSet data HFWRec data HFWString data HFWOther type family FWPPred a where -- FWPPred [ Record a ] = HFWRecList FWPPred ( Set ( Record a ) ) = HFWRecSet FWPPred ( Record a ) = HFWRec -- FWPPred [ ( TIP a ) ] = HFWTIPList FWPPred ( Set ( TIP a ) ) = HFWTIPSet FWPPred ( TIP a ) = HFWTIP FWPPred String = HFWString FWPPred a = HFWOther -- As having no type synonyms in effect data EmptyTS = EmptyTS -- | Type synoyms used when building the table header with type names class HListTypeSynonym s where hRecTS :: s -> String hRecSetTS :: s -> String hTIPTS :: s -> String hTIPSetTS :: s -> String {- TODO: This only supports showing a single type synonym, it doesn't support showing "Set ( TIP '[Foo] )", for instance. This is sufficient for HaskRel, which only uses full type synonyms, for instance "Relation '[Foo]". -} instance HListTypeSynonym EmptyTS where hRecTS _ = "Record" hRecSetTS _ = "Set-Record" hTIPTS _ = "TIP" hTIPSetTS _ = "Set-TIP" -- | Show a TypeRep showTR :: TypeRep -> String showTR = showTRTS EmptyTS {- TODO: Record presentation is copy'n'paste of TIP presentation, which gives the unfortunate 'Record '["foo"]' -} -- | Show a TypeRep, using the given type synonyms showTRTS :: HListTypeSynonym ts => ts -> TypeRep -> String showTRTS ts t | t == stringType = "String" | tyCon == recTyCon = hRecTS ts ++ showHListType app | tyCon == tipTyCon = hTIPTS ts ++ showHListType app | tyCon == setTyCon && typeRepTyCon ( head app ) == recTyCon = hRecSetTS ts ++ showHListType ( typeRepArgs $ head app ) | tyCon == setTyCon && typeRepTyCon ( head app ) == tipTyCon = hTIPSetTS ts ++ showHListType ( typeRepArgs $ head app ) | otherwise = show t where ( tyCon, app ) = splitTyConApp t stringType = typeRep ( Proxy :: Proxy String ) -- The argument to Set could be anything that is an instance of Typeable setTyCon = typeRepTyCon $ typeRep ( Proxy :: Proxy ( Set Int ) ) -- listTyCon = ... recTyCon = typeRepTyCon $ typeRep ( Proxy :: Proxy ( Record '[] ) ) tipTyCon = typeRepTyCon $ typeRep ( Proxy :: Proxy ( TIP '[] ) ) showHListSetType :: forall a (r :: [*] -> *) . ( Typeable r, Typeable a ) => Set (r a) -> String showHListSetType = showHListType . typeRepArgs . head . typeRepArgs . typeOf showHListType :: [TypeRep] -> String showHListType = (++) " '" . show . hListTypeToTRList hListTypeToTRList :: [TypeRep] -> [TypeRep] hListTypeToTRList = parseHListType . typeRepArgs . head parseHListType :: [TypeRep] -> [TypeRep] parseHListType [] = [] parseHListType [_] = error "Not a valid TIP/Record type" -- Always come in pairs. Will also fail here if it's not tagged parseHListType (tr:trx) = head ( typeRepArgs tr ) : parseHListType ( typeRepArgs $ head trx ) -- Meta information tagFName :: TypeRep -> String tagFName = tail . init . show . head . typeRepArgs {-| >>> hRecFNames $ sno .=. "S1" .*. status .=. 10 .*. emptyRecord ["sno","status"] -} hRecFNames :: Typeable a => a -> [String] hRecFNames = map tagFName . flatHRec tagQFName :: TypeRep -> String tagQFName = tagQFNameTS EmptyTS tagQFNameTS :: HListTypeSynonym ts => ts -> TypeRep -> String tagQFNameTS ts a = tagFName a ++ " :: " ++ showHTypeTS ts a {-| >>> hRecQFNames $ sno .=. "S1" .*. status .=. 10 .*. emptyRecord ["sno :: String","status :: Integer"] -} hRecQFNames :: Typeable a => a -> [String] hRecQFNames = map tagQFName . flatHRec hRecQFNamesTS :: ( HListTypeSynonym ts, Typeable a ) => ts -> a -> [String] hRecQFNamesTS ts = map ( tagQFNameTS ts ) . flatHRec showHTypeTS :: HListTypeSynonym ts => ts -> TypeRep -> String showHTypeTS ts = showTRTS ts . head . tail . typeRepArgs {- >>> typeOf $ sno .=. "S1" .*. status .=. 10 .*. emptyRecord Record (: * (Tagged Symbol "sno" [Char]) (: * (Tagged Symbol "status" Integer) [])) >>> flatHRec $ sno .=. "S1" .*. status .=. 10 .*. emptyRecord [Tagged Symbol "sno" [Char],Tagged Symbol "status" Integer] -} -- I did this before noticing hEnd, would that in the right location work just as well? -- TODO: Rewrite to use typeRep and Proxy. (This probably goes for other places too.) flatHRec :: Typeable a => a -> [TypeRep] flatHRec = flatHRec' . typeRepArgs . head . typeRepArgs . typeOf flatHRec' :: [TypeRep] -> [TypeRep] flatHRec' [] = [] flatHRec' [m] = [m] flatHRec' (m:mx) = m : flatHRec' ( typeRepArgs $ head mx ) -- Presentation class HFWPresent r where hfwPrint :: r -> IO () hfwPrintTyped :: r -> IO () hfwPrintTyped = hfwPrintTypedTS EmptyTS hfwPrintTypedTS :: HListTypeSynonym ts => ts -> r -> IO () class Show a => FWPresent a where fwPresent :: a -> [String] fwPresentTyped :: a -> [String] class Show a => FWPresent' flag a where fwPresent' :: flag -> a -> [String] fwPresentTyped' :: flag -> a -> [String] -- Presentation of a non-HList value instance (FWPPred a ~ flag, FWPresent' flag a) => FWPresent a where fwPresent = fwPresent' ( undefined :: flag ) fwPresentTyped = fwPresentTyped' ( undefined :: flag ) instance Show a => FWPresent' HFWOther a where fwPresent' _ x = [show x] fwPresentTyped' _ x = [show x] instance FWPresent' HFWString String where fwPresent' _ x = [x] fwPresentTyped' _ x = [x] -- Presentation of a single record buildHRec :: (Typeable r, RecordValues r, HFoldr (Mapcar HPresentRecAttr) [[String]] (RecordValuesR r) [[String]]) => Record r -> [String] buildHRec rrTup = present1LineValue ( listPresentRec rrTup ) ( hRecFNames rrTup ) buildHRecTyped :: (Typeable r, RecordValues r, HFoldr (Mapcar HPresentTypedRecAttr) [[String]] (RecordValuesR r) [[String]]) => Record r -> [String] buildHRecTyped rrTup = present1LineValue ( listPresentTypedRec rrTup ) ( hRecQFNames rrTup ) buildHRecTypedTS :: (HListTypeSynonym ts, Typeable r, RecordValues r, HFoldr (Mapcar HPresentTypedRecAttr) [[String]] (RecordValuesR r) [[String]]) => ts -> Record r -> [String] buildHRecTypedTS ts rrTup = present1LineValue ( listPresentTypedRec rrTup ) ( hRecQFNamesTS ts rrTup ) listPresentRec :: (RecordValues r, HFoldr (Mapcar HPresentRecAttr) [e] (RecordValuesR r) [e]) => Record r -> [e] listPresentRec = hMapOut HPresentRecAttr . recordValues listPresentTypedRec :: (RecordValues r, HFoldr (Mapcar HPresentTypedRecAttr) [e] (RecordValuesR r) [e]) => Record r -> [e] listPresentTypedRec = hMapOut HPresentTypedRecAttr . recordValues data HPresentRecAttr = HPresentRecAttr instance ([String] ~ stringL, FWPresent' (FWPPred a) a) => ApplyAB HPresentRecAttr a stringL where applyAB _ = fwPresent data HPresentTypedRecAttr = HPresentTypedRecAttr instance ([String] ~ stringL, FWPresent' (FWPPred a) a) => ApplyAB HPresentTypedRecAttr a stringL where applyAB _ = fwPresentTyped instance (HFoldr (Mapcar HPresentRecAttr) [[String]] (RecordValuesR r) [[String]], HFoldr (Mapcar HPresentTypedRecAttr) [[String]] (RecordValuesR r) [[String]], Typeable r, RecordValues r ) => HFWPresent ( Record r ) where hfwPrint = putStrLn . intercalate "\n" . buildHRec hfwPrintTyped = putStrLn . intercalate "\n" . buildHRecTyped hfwPrintTypedTS ts = putStrLn . intercalate "\n" . buildHRecTypedTS ts instance ( HFoldr (Mapcar HPresentRecAttr) [[String]] (RecordValuesR r) [[String]], HFoldr (Mapcar HPresentTypedRecAttr) [[String]] (RecordValuesR r) [[String]], Typeable r, RecordValues r, ShowComponents r ) => FWPresent' HFWRec ( Record r ) where fwPresent' _ = buildHRec fwPresentTyped' _ = buildHRecTyped -- Presentation of a set of records showHRecSetTab :: (Typeable a, RecordValues a, HFoldr (Mapcar HPresentRecAttr) [[String]] (RecordValuesR a) [[String]]) => Set (Record a) -> String showHRecSetTab = intercalate "\n" . buildHRecSet {- showHRecSetTabTyped = intercalate "\n" . buildHRecSetTyped showHRecSetTabTypedTS = intercalate "\n" . buildHRecSetTypedTS -} -- | Prints a set of HList records in a table format printHRecSetTab a = putStrLn $ intercalate "\n" $ buildHRecSet a -- | Prints a set of HList records in a table format, with types in the header printHRecSetTabTyped a = putStrLn $ intercalate "\n" $ buildHRecSetTyped a -- | Prints a set of HList records in a table format, with types that use the given type synonyms in the header printHRecSetTabTypedTS ts a = putStrLn $ intercalate "\n" $ buildHRecSetTypedTS ts a instance (Typeable a, RecordValues a, HFoldr (Mapcar HPresentRecAttr) [[String]] (RecordValuesR a) [[String]], HFoldr (Mapcar HPresentTypedRecAttr) [[String]] (RecordValuesR a) [[String]]) => HFWPresent ( Set ( Record a ) ) where hfwPrint = printHRecSetTab hfwPrintTyped = printHRecSetTabTyped hfwPrintTypedTS = printHRecSetTabTypedTS instance (Typeable a, RecordValues a, ShowComponents a, HFoldr (Mapcar HPresentRecAttr) [[String]] (RecordValuesR a) [[String]], HFoldr (Mapcar HPresentTypedRecAttr) [[String]] (RecordValuesR a) [[String]]) => FWPresent' HFWRecSet ( Set ( Record a ) ) where fwPresent' _ = buildHRecSet fwPresentTyped' _ = buildHRecSetTyped unwrap :: x (Record a) -> Record a unwrap = undefined buildHRecSet :: (Typeable a, RecordValues a, HFoldr (Mapcar HPresentRecAttr) [[String]] (RecordValuesR a) [[String]]) => Set (Record a) -> [String] buildHRecSet rs = presentNLineValue ( map listPresentRec $ toList rs ) ( hRecFNames $ unwrap rs ) buildHRecSetTyped :: (Typeable a, RecordValues a, HFoldr (Mapcar HPresentTypedRecAttr) [[String]] (RecordValuesR a) [[String]]) => Set (Record a) -> [String] buildHRecSetTyped rs = presentNLineValue ( map listPresentTypedRec $ toList rs ) ( hRecQFNames $ unwrap rs ) buildHRecSetTypedTS :: (Typeable a, RecordValues a, HFoldr (Mapcar HPresentTypedRecAttr) [[String]] (RecordValuesR a) [[String]], HListTypeSynonym ts) => ts -> Set (Record a) -> [String] buildHRecSetTypedTS ts rs = presentNLineValue ( map listPresentTypedRec $ toList rs ) ( hRecQFNamesTS ts $ unwrap rs )