module Pipes.Csv (
decode,
decodeWith,
decodeByName,
decodeByNameWith,
feedParser,
feedHeaderParser,
encode,
encodeWith,
encodeByName,
encodeByNameWith,
module Data.Csv,
HasHeader(..)
) where
import qualified Data.Csv.Incremental as CI
import qualified Data.ByteString as B
import qualified Pipes.Prelude as P
import Pipes
import Pipes.Csv.Encoding
import Data.Word (Word8)
import Data.ByteString (ByteString)
import Blaze.ByteString.Builder (toByteString, fromByteString)
import Data.Monoid ((<>))
import Data.Csv.Incremental (Parser(..), HeaderParser(..), HasHeader(..))
import Data.Csv (
Header, DecodeOptions, EncodeOptions(encDelimiter), FromRecord(..),
FromNamedRecord(..), ToRecord(..), ToField(..), FromField(..),
defaultDecodeOptions, defaultEncodeOptions, ToNamedRecord(..), Record, Field,
NamedRecord, (.!), (.:), (.=))
feedParser :: Monad m
=> Parser a
-> Producer ByteString m ()
-> Producer (Either String a) m ()
feedParser parser source = case parser of
Fail _ e -> yield (Left e)
Done es -> each es
Many es k -> each es >> cont k source
where
cont = continue feedParser
feedHeaderParser :: (Monad m)
=> HeaderParser (Parser a)
-> Producer ByteString m ()
-> Producer (Either String a) m ()
feedHeaderParser headerParser source = case headerParser of
FailH _bs e -> yield (Left e)
PartialH k -> cont k source
DoneH _ p -> feedParser p source
where
cont = continue feedHeaderParser
continue :: (Monad (t m), Monad m, MonadTrans t)
=> (a -> Producer ByteString m () -> t m b)
-> (ByteString -> a)
-> Producer ByteString m ()
-> t m b
continue feed k producer = do
x <- lift (next producer)
case x of
Left () -> feed (k B.empty) (return ())
Right (bs, producer') ->
if (B.null bs)
then continue feed k producer'
else feed (k bs) producer'
decode :: (Monad m, FromRecord a)
=> CI.HasHeader
-> Producer ByteString m ()
-> Producer (Either String a) m ()
decode = decodeWith defaultDecodeOptions
decodeWith :: (Monad m, FromRecord a)
=> DecodeOptions
-> HasHeader
-> Producer ByteString m ()
-> Producer (Either String a) m ()
decodeWith opts hasHeader src = feedParser (CI.decodeWith opts hasHeader) src
decodeByName :: (Monad m, FromNamedRecord a)
=> Producer ByteString m ()
-> Producer (Either String a) m ()
decodeByName = decodeByNameWith defaultDecodeOptions
decodeByNameWith :: (Monad m, FromNamedRecord a)
=> DecodeOptions
-> Producer ByteString m ()
-> Producer (Either String a) m ()
decodeByNameWith opts src = feedHeaderParser (CI.decodeByNameWith opts) src
encode :: (Monad m, ToRecord a) => Pipe a ByteString m r
encode = encodeWith defaultEncodeOptions
encodeByName :: (Monad m, ToNamedRecord a)
=> Header -> Pipe a ByteString m r
encodeByName = encodeByNameWith defaultEncodeOptions
encodeWithCrLf :: Word8 -> Record -> ByteString
encodeWithCrLf d = toByteString . (<> fromByteString "\r\n") . encodeRecord d
encodeWith :: (Monad m, ToRecord a)
=> EncodeOptions
-> Pipe a ByteString m r
encodeWith opts = P.map (encodeWithCrLf delim . toRecord)
where
delim = encDelimiter opts
encodeByNameWith :: (Monad m, ToNamedRecord a)
=> EncodeOptions
-> Header
-> Pipe a ByteString m r
encodeByNameWith opts hdr = do
yield $ toByteString $ encodeRecord delim hdr <> fromByteString "\r\n"
P.map (encodeWithCrLf delim . namedRecordToRecord hdr . toNamedRecord)
where
delim = encDelimiter opts