cassava-generic-0.1.0.0: src/Data/Csv/GenericInstances.hs
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE RankNTypes #-}
{-# LANGUAGE UndecidableInstances #-}
{-# LANGUAGE ScopedTypeVariables #-}
-- | Generic instances of Csv.ToNamedRecord,
-- and Csv.DefaultOrdered.
--
-- Allow modular treatment of nested records when writing with Cassava.
module Data.Csv.GenericInstances
( -- Instances only
)
where
import GHC.Generics
import Data.Typeable
import Data.Aeson
import Data.List (sort)
import Data.Aeson.Types (prependFailure)
import Control.Applicative
import Data.Text as Text
import qualified Data.ByteString.Char8 as BS
import qualified Data.Text.Encoding as Text
import Data.Vector
import qualified Data.Foldable as F
import Data.Functor.Identity
import qualified Data.HashMap.Strict as Map
import Data.Default
import Data.Functor.Classes
import qualified Data.Csv as Csv
-- * Missing instances of `Data.Csv.ToField class`
instance Csv.ToField [Text] where
toField = Text.encodeUtf8 . Text.intercalate " "
instance Csv.ToField Bool where
toField True = "yes"
toField False = "no"
instance Csv.ToField [String] where
toField aList = " " `BS.intercalate` (BS.pack <$> aList)
-- * Assoc list:
instance Csv.ToField [(Text, Text)] where
toField ttList = " " `BS.intercalate` Prelude.map (
\(a, b) ->
Text.encodeUtf8 $ "=" `Text.intercalate` [a, b]) ttList
instance {-# OVERLAPPABLE #-} Show a => Csv.ToField a where
toField = BS.pack . show
-- * These instances assume that we use argument for `headerOrder` like `Proxy`
-- (so do not need value at all).
-- Generic Either instance for ToNamedRecord
instance (Csv.DefaultOrdered a
,Csv.ToNamedRecord a)
=> Csv.ToNamedRecord (Either String a) where
toNamedRecord (arg :: Either String a) = case arg of
Left msg -> Csv.namedRecord (F.foldMap emptyField subHeader)
<> errorField msg
Right r -> Csv.toNamedRecord r
<> errorField (""::String)
where
subHeader :: Csv.Header
subHeader = Csv.headerOrder (error "fails in ToNamedRecord (Either String a)" :: a)
errorField errorMessage = Csv.namedRecord
[(Data.Vector.head subHeader<>"_error") Csv..= errorMessage]
-- Generic Either instance for ToNamedRecord
instance (Csv.DefaultOrdered a
,Csv.ToNamedRecord a)
=> Csv.DefaultOrdered (Either String a) where
headerOrder (_ :: Either String a) = Csv.header [Data.Vector.head subHeader<>"_error"]
<> subHeader
where
subHeader :: Csv.Header
subHeader = Csv.headerOrder (error "fails in DefaultOrdered (Either String a)" :: a)
-- Generic Maybe instance for ToNamedRecord
instance (Csv.DefaultOrdered a
,Csv.ToNamedRecord a)
=> Csv.ToNamedRecord (Maybe a) where
toNamedRecord (arg :: Maybe a) = case arg of
Nothing -> Csv.namedRecord (F.foldMap emptyField subHeader)
Just r -> Csv.toNamedRecord r
where
subHeader :: Csv.Header
subHeader = Csv.headerOrder (error "fails in ToNamedRecord (Maybe a)" :: a)
-- | Generic Identity instance for ToNamedRecord
instance Csv.ToNamedRecord a
=> Csv.ToNamedRecord (Identity a) where
toNamedRecord (Identity a) = Csv.toNamedRecord a
-- | Empty field for generic instances
emptyField name = [name Csv..= ("" :: String)]
instance (Csv.DefaultOrdered a
,Csv.ToNamedRecord a)
=> Csv.DefaultOrdered (Maybe a) where
headerOrder (_ :: Maybe a) = subHeader
where
subHeader :: Csv.Header
subHeader = Csv.headerOrder (error "fails in DefaultOrdered (Maybe a)" :: a)
-- | Generic Identity instance for DefaultOrdered
instance Csv.DefaultOrdered a
=> Csv.DefaultOrdered (Identity a) where
headerOrder _ = Csv.headerOrder (undefined :: a)