{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE TupleSections #-}
module Data.Sv (
parseDecode
, parseDecodeFromFile
, parseDecodeFromDsvCursor
, parseDecodeNamed
, parseDecodeNamedFromFile
, parseDecodeNamedFromDsvCursor
, decode
, decodeMay
, decodeEither
, decodeEither'
, (.:)
, (>>==)
, (==<<)
, module Data.Sv.Parse
, module Data.Sv.Decode.Type
, module Data.Sv.Decode.Error
, encode
, encodeToFile
, encodeToHandle
, encodeBuilder
, encodeNamed
, encodeNamedToFile
, encodeNamedToHandle
, encodeNamedBuilder
, encodeRow
, (=:)
, module Data.Sv.Encode.Type
, module Data.Sv.Encode.Options
, module Data.Sv.Structure
, Alt (..)
, Contravariant (..)
, Divisible (..)
, divided
, Decidable (..)
, chosen
, Validation (..)
) where
import Control.Monad.IO.Class (MonadIO (liftIO))
import qualified Data.Attoparsec.ByteString.Lazy as AL
import Data.Bifunctor (Bifunctor (first))
import Data.ByteString (ByteString)
import qualified Data.ByteString.UTF8 as UTF8
import qualified Data.ByteString.Lazy as LBS
import HaskellWorks.Data.Dsv.Lazy.Cursor as DSV
import Data.Functor.Alt (Alt (..))
import Data.Functor.Contravariant (Contravariant (..))
import Data.Functor.Contravariant.Divisible (Divisible (..), divided, Decidable (..), chosen)
import Data.Validation (Validation (..))
import Data.Sv.Alien.Cassava (field)
import Data.Sv.Decode
import Data.Sv.Decode.Type
import Data.Sv.Decode.Error
import Data.Sv.Encode
import Data.Sv.Encode.Options
import Data.Sv.Encode.Type
import Data.Sv.Structure
import Data.Sv.Parse
parseDecode ::
Decode' ByteString a
-> ParseOptions
-> LBS.ByteString
-> DecodeValidation ByteString [a]
parseDecode d opts bs =
let sep = _separator opts
cursor = DSV.makeCursor sep bs
in parseDecodeFromDsvCursor d opts cursor
parseDecodeNamed ::
NameDecode' ByteString a
-> ParseOptions
-> LBS.ByteString
-> DecodeValidation ByteString [a]
parseDecodeNamed d opts bs =
parseDecodeNamedFromDsvCursor d opts
(DSV.makeCursor (_separator opts) bs)
parseDecodeFromFile ::
MonadIO m
=> Decode' ByteString a
-> ParseOptions
-> FilePath
-> m (DecodeValidation ByteString [a])
parseDecodeFromFile d opts fp =
parseDecode d opts <$> liftIO (LBS.readFile fp)
parseDecodeNamedFromFile ::
MonadIO m
=> NameDecode' ByteString a
-> ParseOptions
-> FilePath
-> m (DecodeValidation ByteString [a])
parseDecodeNamedFromFile d opts fp =
parseDecodeNamed d opts <$> liftIO (LBS.readFile fp)
parseDecodeFromDsvCursor :: Decode' ByteString a -> ParseOptions -> DsvCursor -> DecodeValidation ByteString [a]
parseDecodeFromDsvCursor d opts cursor =
flip bindValidation (decode d) . traverse (traverse toField) . DSV.toListVector $ case _headedness opts of
Unheaded -> cursor
Headed -> nextPosition (nextRow cursor)
parseDecodeNamedFromDsvCursor :: NameDecode' ByteString a -> ParseOptions -> DsvCursor -> DecodeValidation ByteString [a]
parseDecodeNamedFromDsvCursor n opts cursor =
let parts =
case DSV.toListVector cursor of
[] -> missingHeader
(header:body) ->
let header' = traverse toField header
in case _headedness opts of
Unheaded ->
badConfig $ mconcat
[ "Your ParseOptions indicates a CSV with no header (Unheaded), "
, "but your decoder requires column names."
]
Headed ->
(,body) <$> bindValidation header' (flip makePositional n)
in bindValidation parts $ \(d,b) ->
flip bindValidation (decode d) $ traverse (traverse toField) b
toField :: LBS.ByteString -> DecodeValidation ByteString ByteString
toField = Prelude.either badParse pure . first UTF8.fromString . AL.eitherResult . AL.parse field