{-# LANGUAGE BangPatterns, CPP, DeriveFunctor #-}
module Data.Csv.Streaming
(
Records(..)
, HasHeader(..)
, decode
, decodeWith
, decodeByName
, decodeByNameWith
) where
import Control.DeepSeq (NFData(rnf))
import qualified Data.ByteString as B
import qualified Data.ByteString.Lazy as BL
import qualified Data.ByteString.Lazy.Char8 as BL8
import Data.Foldable (Foldable(..))
import Prelude hiding (foldr)
import Data.Csv.Conversion
import Data.Csv.Incremental hiding (decode, decodeByName, decodeByNameWith,
decodeWith)
import qualified Data.Csv.Incremental as I
import Data.Csv.Parser
import Data.Csv.Types
#if !MIN_VERSION_base(4,8,0)
import Control.Applicative ((<$>), (<*>), pure)
import Data.Traversable (Traversable(..))
#endif
#if !MIN_VERSION_bytestring(0,10,0)
import qualified Data.ByteString.Lazy.Internal as BL
#endif
data Records a
=
Cons (Either String a) (Records a)
| Nil (Maybe String) BL.ByteString
deriving (Eq, Functor, Show)
instance Foldable Records where
foldr = foldrRecords
#if MIN_VERSION_base(4,6,0)
foldl' = foldlRecords'
#endif
foldrRecords :: (a -> b -> b) -> b -> Records a -> b
foldrRecords f = go
where
go z (Cons (Right x) rs) = f x (go z rs)
go z (Cons (Left _) rs) = go z rs
go z _ = z
{-# INLINE foldrRecords #-}
#if MIN_VERSION_base(4,6,0)
foldlRecords' :: (a -> b -> a) -> a -> Records b -> a
foldlRecords' f = go
where
go z (Cons (Right x) rs) = let z' = f z x in z' `seq` go z' rs
go z (Cons (Left _) rs) = go z rs
go z _ = z
{-# INLINE foldlRecords' #-}
#endif
instance Traversable Records where
traverse _ (Nil merr rest) = pure $ Nil merr rest
traverse f (Cons x xs) = Cons <$> traverseElem x <*> traverse f xs
where
traverseElem (Left err) = pure $ Left err
traverseElem (Right y) = Right <$> f y
instance NFData a => NFData (Records a) where
rnf (Cons r rs) = rnf r `seq` rnf rs
#if MIN_VERSION_bytestring(0,10,0)
rnf (Nil errMsg rest) = rnf errMsg `seq` rnf rest
#else
rnf (Nil errMsg rest) = rnf errMsg `seq` rnfLazyByteString rest
rnfLazyByteString :: BL.ByteString -> ()
rnfLazyByteString BL.Empty = ()
rnfLazyByteString (BL.Chunk _ b) = rnfLazyByteString b
#endif
decode :: FromRecord a
=> HasHeader
-> BL.ByteString
-> Records a
decode = decodeWith defaultDecodeOptions
decodeWith :: FromRecord a
=> DecodeOptions
-> HasHeader
-> BL.ByteString
-> Records a
decodeWith !opts hasHeader s0 =
go (BL.toChunks s0) (I.decodeWith opts hasHeader)
where
go ss (Done xs) = foldr Cons (Nil Nothing (BL.fromChunks ss)) xs
go ss (Fail rest err) = Nil (Just err) (BL.fromChunks (rest:ss))
go [] (Many xs k) = foldr Cons (go [] (k B.empty)) xs
go (s:ss) (Many xs k) = foldr Cons (go ss (k s)) xs
decodeByName :: FromNamedRecord a
=> BL.ByteString
-> Either String (Header, Records a)
decodeByName = decodeByNameWith defaultDecodeOptions
decodeByNameWith :: FromNamedRecord a
=> DecodeOptions
-> BL.ByteString
-> Either String (Header, Records a)
decodeByNameWith !opts s0 = go (BL.toChunks s0) (I.decodeByNameWith opts)
where
go ss (DoneH hdr p) = Right (hdr, go2 ss p)
go ss (FailH rest err) = Left $ err ++ " at " ++
show (BL8.unpack . BL.fromChunks $ rest : ss)
go [] (PartialH k) = go [] (k B.empty)
go (s:ss) (PartialH k) = go ss (k s)
go2 ss (Done xs) = foldr Cons (Nil Nothing (BL.fromChunks ss)) xs
go2 ss (Fail rest err) = Nil (Just err) (BL.fromChunks (rest:ss))
go2 [] (Many xs k) = foldr Cons (go2 [] (k B.empty)) xs
go2 (s:ss) (Many xs k) = foldr Cons (go2 ss (k s)) xs