module Data.Csv.Streaming
(
Records(..)
, HasHeader(..)
, decode
, decodeWith
, decodeByName
, decodeByNameWith
) where
import Control.Applicative ((<$>), (<*>), pure)
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 Data.Traversable (Traversable(..))
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_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 _ = z
#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 _ = z
#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