{-# LANGUAGE BangPatterns, CPP, DeriveFunctor, ScopedTypeVariables #-}

-- | This module allows for incremental decoding and encoding of CSV
-- data. This is useful if you e.g. want to interleave I\/O with
-- parsing or if you want finer grained control over how you deal with
-- type conversion errors.
--
-- Decoding example:
--
-- > main :: IO ()
-- > main = withFile "salaries.csv" ReadMode $ \ csvFile -> do
-- >     let loop !_ (Fail _ errMsg) = putStrLn errMsg >> exitFailure
-- >         loop acc (Many rs k)    = loop (acc + sumSalaries rs) =<< feed k
-- >         loop acc (Done rs)      = putStrLn $ "Total salaries: " ++
-- >                                   show (sumSalaries rs + acc)
-- >
-- >         feed k = do
-- >             isEof <- hIsEOF csvFile
-- >             if isEof
-- >                 then return $ k B.empty
-- >                 else k `fmap` B.hGetSome csvFile 4096
-- >     loop 0 (decode NoHeader)
-- >   where
-- >     sumSalaries rs = sum [salary | Right (_ :: String, salary :: Int) <- rs]
--
-- Encoding example:
--
-- > data Person = Person { name   :: !String, salary :: !Int }
-- >     deriving Generic
-- >
-- > instance FromNamedRecord Person
-- > instance ToNamedRecord Person
-- > instance DefaultOrdered Person
-- >
-- > persons :: [Person]
-- > persons = [Person "John" 50000, Person "Jane" 60000]
-- >
-- > main :: IO ()
-- > main = putStrLn $ encodeDefaultOrderedByName (go persons)
-- >   where
-- >     go (x:xs) = encodeNamedRecord x <> go xs
--
module Data.Csv.Incremental
    (
    -- * Decoding
      HeaderParser(..)
    , decodeHeader
    , decodeHeaderWith

    -- $typeconversion
    , Parser(..)

    -- ** Index-based record conversion
    -- $indexbased
    , HasHeader(..)
    , decode
    , decodeWith
    , decodeWithP

    -- ** Name-based record conversion
    -- $namebased
    , decodeByName
    , decodeByNameWith
    , decodeByNameWithP

    -- * Encoding
    -- ** Index-based record conversion
    -- $indexbased
    , encode
    , encodeWith
    , encodeRecord
    , Builder

    -- ** Name-based record conversion
    -- $namebased
    , encodeByName
    , encodeDefaultOrderedByName
    , encodeByNameWith
    , encodeDefaultOrderedByNameWith
    , encodeNamedRecord
    , NamedBuilder
    ) where

import Control.Applicative ((<|>))
import qualified Data.Attoparsec.ByteString as A
import Data.Attoparsec.ByteString.Char8 (endOfInput)
import qualified Data.ByteString as B
import qualified Data.ByteString.Builder as Builder
import qualified Data.ByteString.Lazy as L
import Data.Semigroup as Semi (Semigroup, (<>))
import qualified Data.Vector as V
import Data.Word (Word8)

import Data.Csv.Conversion hiding (Parser, header, namedRecord, record,
                                   toNamedRecord)
import qualified Data.Csv.Conversion as Conversion
import qualified Data.Csv.Encoding as Encoding
import Data.Csv.Encoding (EncodeOptions(..), Quoting(..), recordSep)
import Data.Csv.Parser
import Data.Csv.Types
import Data.Csv.Util (endOfLine)

#if !MIN_VERSION_base(4,8,0)
import Data.Monoid (Monoid(mappend, mempty))
import Control.Applicative ((<*))
#endif

-- $feed-header
--
-- These functions are sometimes convenient when working with
-- 'HeaderParser', but don't let you do anything you couldn't already
-- do using the 'HeaderParser' constructors directly.

-- $indexbased
--
-- See documentation on index-based conversion in "Data.Csv" for more
-- information.

-- $namebased
--
-- See documentation on name-based conversion in "Data.Csv" for more
-- information.

-- $feed-records
--
-- These functions are sometimes convenient when working with
-- 'Parser', but don't let you do anything you couldn't already do
-- using the 'Parser' constructors directly.

------------------------------------------------------------------------
-- * Decoding headers

-- | An incremental parser that when fed data eventually returns a
-- parsed 'Header', or an error.
data HeaderParser a =
      -- | The input data was malformed. The first field contains any
      -- unconsumed input and second field contains information about
      -- the parse error.
      FailH !B.ByteString String

      -- | The parser needs more input data before it can produce a
      -- result. Use an 'B.empty' string to indicate that no more
      -- input data is available. If fed an 'B.empty string', the
      -- continuation is guaranteed to return either 'FailH' or
      -- 'DoneH'.
    | PartialH (B.ByteString -> HeaderParser a)

      -- | The parse succeeded and produced the given 'Header'.
    | DoneH !Header a
    deriving a -> HeaderParser b -> HeaderParser a
(a -> b) -> HeaderParser a -> HeaderParser b
(forall a b. (a -> b) -> HeaderParser a -> HeaderParser b)
-> (forall a b. a -> HeaderParser b -> HeaderParser a)
-> Functor HeaderParser
forall a b. a -> HeaderParser b -> HeaderParser a
forall a b. (a -> b) -> HeaderParser a -> HeaderParser b
forall (f :: * -> *).
(forall a b. (a -> b) -> f a -> f b)
-> (forall a b. a -> f b -> f a) -> Functor f
<$ :: a -> HeaderParser b -> HeaderParser a
$c<$ :: forall a b. a -> HeaderParser b -> HeaderParser a
fmap :: (a -> b) -> HeaderParser a -> HeaderParser b
$cfmap :: forall a b. (a -> b) -> HeaderParser a -> HeaderParser b
Functor

instance Show a => Show (HeaderParser a) where
    showsPrec :: Int -> HeaderParser a -> ShowS
showsPrec Int
d (FailH ByteString
rest String
msg) = Bool -> ShowS -> ShowS
showParen (Int
d Int -> Int -> Bool
forall a. Ord a => a -> a -> Bool
> Int
appPrec) ShowS
showStr
      where
        showStr :: ShowS
showStr = String -> ShowS
showString String
"FailH " ShowS -> ShowS -> ShowS
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Int -> ByteString -> ShowS
forall a. Show a => Int -> a -> ShowS
showsPrec (Int
appPrecInt -> Int -> Int
forall a. Num a => a -> a -> a
+Int
1) ByteString
rest ShowS -> ShowS -> ShowS
forall b c a. (b -> c) -> (a -> b) -> a -> c
.
                  String -> ShowS
showString String
" " ShowS -> ShowS -> ShowS
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Int -> String -> ShowS
forall a. Show a => Int -> a -> ShowS
showsPrec (Int
appPrecInt -> Int -> Int
forall a. Num a => a -> a -> a
+Int
1) String
msg
    showsPrec Int
_ (PartialH ByteString -> HeaderParser a
_) = String -> ShowS
showString String
"PartialH <function>"
    showsPrec Int
d (DoneH Header
hdr a
x) = Bool -> ShowS -> ShowS
showParen (Int
d Int -> Int -> Bool
forall a. Ord a => a -> a -> Bool
> Int
appPrec) ShowS
showStr
      where
        showStr :: ShowS
showStr = String -> ShowS
showString String
"DoneH " ShowS -> ShowS -> ShowS
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Int -> Header -> ShowS
forall a. Show a => Int -> a -> ShowS
showsPrec (Int
appPrecInt -> Int -> Int
forall a. Num a => a -> a -> a
+Int
1) Header
hdr ShowS -> ShowS -> ShowS
forall b c a. (b -> c) -> (a -> b) -> a -> c
.
                  String -> ShowS
showString String
" " ShowS -> ShowS -> ShowS
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Int -> a -> ShowS
forall a. Show a => Int -> a -> ShowS
showsPrec (Int
appPrecInt -> Int -> Int
forall a. Num a => a -> a -> a
+Int
1) a
x

-- Application has precedence one more than the most tightly-binding
-- operator
appPrec :: Int
appPrec :: Int
appPrec = Int
10

-- | Parse a CSV header in an incremental fashion. When done, the
-- 'HeaderParser' returns any unconsumed input in the second field of
-- the 'DoneH' constructor.
decodeHeader :: HeaderParser B.ByteString
decodeHeader :: HeaderParser ByteString
decodeHeader = DecodeOptions -> HeaderParser ByteString
decodeHeaderWith DecodeOptions
defaultDecodeOptions

-- | Like 'decodeHeader', but lets you customize how the CSV data is
-- parsed.
decodeHeaderWith :: DecodeOptions -> HeaderParser B.ByteString
decodeHeaderWith :: DecodeOptions -> HeaderParser ByteString
decodeHeaderWith !DecodeOptions
opts = (ByteString -> HeaderParser ByteString) -> HeaderParser ByteString
forall a. (ByteString -> HeaderParser a) -> HeaderParser a
PartialH (IResult ByteString Header -> HeaderParser ByteString
go (IResult ByteString Header -> HeaderParser ByteString)
-> (ByteString -> IResult ByteString Header)
-> ByteString
-> HeaderParser ByteString
forall b c a. (b -> c) -> (a -> b) -> a -> c
. ByteString -> IResult ByteString Header
parser)
  where
    parser :: ByteString -> IResult ByteString Header
parser = Parser Header -> ByteString -> IResult ByteString Header
forall a. Parser a -> ByteString -> Result a
A.parse (Word8 -> Parser Header
header (Word8 -> Parser Header) -> Word8 -> Parser Header
forall a b. (a -> b) -> a -> b
$ DecodeOptions -> Word8
decDelimiter DecodeOptions
opts)

    go :: IResult ByteString Header -> HeaderParser ByteString
go (A.Fail ByteString
rest [String]
_ String
msg) = ByteString -> String -> HeaderParser ByteString
forall a. ByteString -> String -> HeaderParser a
FailH ByteString
rest String
err
      where err :: String
err = String
"parse error (" String -> ShowS
forall a. [a] -> [a] -> [a]
++ String
msg String -> ShowS
forall a. [a] -> [a] -> [a]
++ String
")"
    -- TODO: Check empty and give attoparsec one last chance to return
    -- something:
    go (A.Partial ByteString -> IResult ByteString Header
k)       = (ByteString -> HeaderParser ByteString) -> HeaderParser ByteString
forall a. (ByteString -> HeaderParser a) -> HeaderParser a
PartialH ((ByteString -> HeaderParser ByteString)
 -> HeaderParser ByteString)
-> (ByteString -> HeaderParser ByteString)
-> HeaderParser ByteString
forall a b. (a -> b) -> a -> b
$ \ ByteString
s -> IResult ByteString Header -> HeaderParser ByteString
go (ByteString -> IResult ByteString Header
k ByteString
s)
    go (A.Done ByteString
rest Header
r)     = Header -> ByteString -> HeaderParser ByteString
forall a. Header -> a -> HeaderParser a
DoneH Header
r ByteString
rest

------------------------------------------------------------------------
-- * Decoding records

-- $typeconversion
--
-- Just like in the case of non-incremental decoding, there are two
-- ways to convert CSV records to and from and user-defined data
-- types: index-based conversion and name-based conversion.

-- | An incremental parser that when fed data eventually produces some
-- parsed records, converted to the desired type, or an error in case
-- of malformed input data.
data Parser a =
      -- | The input data was malformed. The first field contains any
      -- unconsumed input and second field contains information about
      -- the parse error.
      Fail !B.ByteString String

      -- | The parser parsed and converted zero or more records. Any
      -- records that failed type conversion are returned as @'Left'
      -- errMsg@ and the rest as @'Right' val@. Feed a 'B.ByteString'
      -- to the continuation to continue parsing. Use an 'B.empty'
      -- string to indicate that no more input data is available. If
      -- fed an 'B.empty' string, the continuation is guaranteed to
      -- return either 'Fail' or 'Done'.
    | Many [Either String a] (B.ByteString -> Parser a)

      -- | The parser parsed and converted some records. Any records
      -- that failed type conversion are returned as @'Left' errMsg@
      -- and the rest as @'Right' val@.
    | Done [Either String a]
    deriving a -> Parser b -> Parser a
(a -> b) -> Parser a -> Parser b
(forall a b. (a -> b) -> Parser a -> Parser b)
-> (forall a b. a -> Parser b -> Parser a) -> Functor Parser
forall a b. a -> Parser b -> Parser a
forall a b. (a -> b) -> Parser a -> Parser b
forall (f :: * -> *).
(forall a b. (a -> b) -> f a -> f b)
-> (forall a b. a -> f b -> f a) -> Functor f
<$ :: a -> Parser b -> Parser a
$c<$ :: forall a b. a -> Parser b -> Parser a
fmap :: (a -> b) -> Parser a -> Parser b
$cfmap :: forall a b. (a -> b) -> Parser a -> Parser b
Functor

instance Show a => Show (Parser a) where
    showsPrec :: Int -> Parser a -> ShowS
showsPrec Int
d (Fail ByteString
rest String
msg) = Bool -> ShowS -> ShowS
showParen (Int
d Int -> Int -> Bool
forall a. Ord a => a -> a -> Bool
> Int
appPrec) ShowS
showStr
      where
        showStr :: ShowS
showStr = String -> ShowS
showString String
"Fail " ShowS -> ShowS -> ShowS
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Int -> ByteString -> ShowS
forall a. Show a => Int -> a -> ShowS
showsPrec (Int
appPrecInt -> Int -> Int
forall a. Num a => a -> a -> a
+Int
1) ByteString
rest ShowS -> ShowS -> ShowS
forall b c a. (b -> c) -> (a -> b) -> a -> c
.
                  String -> ShowS
showString String
" " ShowS -> ShowS -> ShowS
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Int -> String -> ShowS
forall a. Show a => Int -> a -> ShowS
showsPrec (Int
appPrecInt -> Int -> Int
forall a. Num a => a -> a -> a
+Int
1) String
msg
    showsPrec Int
d (Many [Either String a]
rs ByteString -> Parser a
_) = Bool -> ShowS -> ShowS
showParen (Int
d Int -> Int -> Bool
forall a. Ord a => a -> a -> Bool
> Int
appPrec) ShowS
showStr
      where
        showStr :: ShowS
showStr = String -> ShowS
showString String
"Many " ShowS -> ShowS -> ShowS
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Int -> [Either String a] -> ShowS
forall a. Show a => Int -> a -> ShowS
showsPrec (Int
appPrecInt -> Int -> Int
forall a. Num a => a -> a -> a
+Int
1) [Either String a]
rs ShowS -> ShowS -> ShowS
forall b c a. (b -> c) -> (a -> b) -> a -> c
.
                  String -> ShowS
showString String
" <function>"
    showsPrec Int
d (Done [Either String a]
rs) = Bool -> ShowS -> ShowS
showParen (Int
d Int -> Int -> Bool
forall a. Ord a => a -> a -> Bool
> Int
appPrec) ShowS
showStr
      where
        showStr :: ShowS
showStr = String -> ShowS
showString String
"Done " ShowS -> ShowS -> ShowS
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Int -> [Either String a] -> ShowS
forall a. Show a => Int -> a -> ShowS
showsPrec (Int
appPrecInt -> Int -> Int
forall a. Num a => a -> a -> a
+Int
1) [Either String a]
rs

-- | Have we read all available input?
data More = Incomplete | Complete
          deriving (More -> More -> Bool
(More -> More -> Bool) -> (More -> More -> Bool) -> Eq More
forall a. (a -> a -> Bool) -> (a -> a -> Bool) -> Eq a
/= :: More -> More -> Bool
$c/= :: More -> More -> Bool
== :: More -> More -> Bool
$c== :: More -> More -> Bool
Eq, Int -> More -> ShowS
[More] -> ShowS
More -> String
(Int -> More -> ShowS)
-> (More -> String) -> ([More] -> ShowS) -> Show More
forall a.
(Int -> a -> ShowS) -> (a -> String) -> ([a] -> ShowS) -> Show a
showList :: [More] -> ShowS
$cshowList :: [More] -> ShowS
show :: More -> String
$cshow :: More -> String
showsPrec :: Int -> More -> ShowS
$cshowsPrec :: Int -> More -> ShowS
Show)

-- | Efficiently deserialize CSV in an incremental fashion. Equivalent
-- to @'decodeWith' 'defaultDecodeOptions'@.
decode :: FromRecord a
       => HasHeader     -- ^ Data contains header that should be
                        -- skipped
       -> Parser a
decode :: HasHeader -> Parser a
decode = DecodeOptions -> HasHeader -> Parser a
forall a. FromRecord a => DecodeOptions -> HasHeader -> Parser a
decodeWith DecodeOptions
defaultDecodeOptions

-- | Like 'decode', but lets you customize how the CSV data is parsed.
decodeWith :: FromRecord a
           => DecodeOptions  -- ^ Decoding options
           -> HasHeader      -- ^ Data contains header that should be
                             -- skipped
           -> Parser a
decodeWith :: DecodeOptions -> HasHeader -> Parser a
decodeWith !DecodeOptions
opts HasHeader
hasHeader = (Header -> Parser a) -> DecodeOptions -> HasHeader -> Parser a
forall a.
(Header -> Parser a) -> DecodeOptions -> HasHeader -> Parser a
decodeWithP Header -> Parser a
forall a. FromRecord a => Header -> Parser a
parseRecord DecodeOptions
opts HasHeader
hasHeader

-- | Like 'decodeWith', but lets you pass an explicit parser value instead of
-- using a typeclass
--
-- @since 0.5.2.0
decodeWithP :: (Record -> Conversion.Parser a)
            -> DecodeOptions  -- ^ Decoding options
            -> HasHeader      -- ^ Data contains header that should be
                             -- skipped
            -> Parser a
decodeWithP :: (Header -> Parser a) -> DecodeOptions -> HasHeader -> Parser a
decodeWithP Header -> Parser a
p !DecodeOptions
opts HasHeader
hasHeader = case HasHeader
hasHeader of
    HasHeader
HasHeader -> HeaderParser ByteString -> Parser a
go (DecodeOptions -> HeaderParser ByteString
decodeHeaderWith DecodeOptions
opts)
    HasHeader
NoHeader  -> [Either String a] -> (ByteString -> Parser a) -> Parser a
forall a. [Either String a] -> (ByteString -> Parser a) -> Parser a
Many [] ((ByteString -> Parser a) -> Parser a)
-> (ByteString -> Parser a) -> Parser a
forall a b. (a -> b) -> a -> b
$ \ ByteString
s -> (Header -> Parser a) -> DecodeOptions -> ByteString -> Parser a
forall a.
(Header -> Parser a) -> DecodeOptions -> ByteString -> Parser a
decodeWithP' Header -> Parser a
p DecodeOptions
opts ByteString
s
  where go :: HeaderParser ByteString -> Parser a
go (FailH ByteString
rest String
msg) = ByteString -> String -> Parser a
forall a. ByteString -> String -> Parser a
Fail ByteString
rest String
msg
        go (PartialH ByteString -> HeaderParser ByteString
k)     = [Either String a] -> (ByteString -> Parser a) -> Parser a
forall a. [Either String a] -> (ByteString -> Parser a) -> Parser a
Many [] ((ByteString -> Parser a) -> Parser a)
-> (ByteString -> Parser a) -> Parser a
forall a b. (a -> b) -> a -> b
$ \ ByteString
s' -> HeaderParser ByteString -> Parser a
go (ByteString -> HeaderParser ByteString
k ByteString
s')
        go (DoneH Header
_ ByteString
rest)   = (Header -> Parser a) -> DecodeOptions -> ByteString -> Parser a
forall a.
(Header -> Parser a) -> DecodeOptions -> ByteString -> Parser a
decodeWithP' Header -> Parser a
p DecodeOptions
opts ByteString
rest

------------------------------------------------------------------------

-- | Efficiently deserialize CSV in an incremental fashion. The data
-- is assumed to be preceded by a header. Returns a 'HeaderParser'
-- that when done produces a 'Parser' for parsing the actual records.
-- Equivalent to @'decodeByNameWith' 'defaultDecodeOptions'@.
decodeByName :: FromNamedRecord a
             => HeaderParser (Parser a)
decodeByName :: HeaderParser (Parser a)
decodeByName = DecodeOptions -> HeaderParser (Parser a)
forall a.
FromNamedRecord a =>
DecodeOptions -> HeaderParser (Parser a)
decodeByNameWith DecodeOptions
defaultDecodeOptions

-- | Like 'decodeByName', but lets you customize how the CSV data is
-- parsed.
decodeByNameWith :: FromNamedRecord a
                 => DecodeOptions  -- ^ Decoding options
                 -> HeaderParser (Parser a)
decodeByNameWith :: DecodeOptions -> HeaderParser (Parser a)
decodeByNameWith !DecodeOptions
opts = (NamedRecord -> Parser a)
-> DecodeOptions -> HeaderParser (Parser a)
forall a.
(NamedRecord -> Parser a)
-> DecodeOptions -> HeaderParser (Parser a)
decodeByNameWithP NamedRecord -> Parser a
forall a. FromNamedRecord a => NamedRecord -> Parser a
parseNamedRecord DecodeOptions
opts

-- | Like 'decodeByNameWith', but lets you pass an explicit parser value instead
-- of using a typeclass
--
-- @since 0.5.2.0
decodeByNameWithP :: (NamedRecord -> Conversion.Parser a)
                  -> DecodeOptions  -- ^ Decoding options
                  -> HeaderParser (Parser a)
decodeByNameWithP :: (NamedRecord -> Parser a)
-> DecodeOptions -> HeaderParser (Parser a)
decodeByNameWithP NamedRecord -> Parser a
p !DecodeOptions
opts = HeaderParser ByteString -> HeaderParser (Parser a)
go (DecodeOptions -> HeaderParser ByteString
decodeHeaderWith DecodeOptions
opts)
  where
    go :: HeaderParser ByteString -> HeaderParser (Parser a)
go (FailH ByteString
rest String
msg) = ByteString -> String -> HeaderParser (Parser a)
forall a. ByteString -> String -> HeaderParser a
FailH ByteString
rest String
msg
    go (PartialH ByteString -> HeaderParser ByteString
k)     = (ByteString -> HeaderParser (Parser a)) -> HeaderParser (Parser a)
forall a. (ByteString -> HeaderParser a) -> HeaderParser a
PartialH ((ByteString -> HeaderParser (Parser a))
 -> HeaderParser (Parser a))
-> (ByteString -> HeaderParser (Parser a))
-> HeaderParser (Parser a)
forall a b. (a -> b) -> a -> b
$ \ ByteString
s -> HeaderParser ByteString -> HeaderParser (Parser a)
go (ByteString -> HeaderParser ByteString
k ByteString
s)
    go (DoneH Header
hdr ByteString
rest) =
        Header -> Parser a -> HeaderParser (Parser a)
forall a. Header -> a -> HeaderParser a
DoneH Header
hdr ((Header -> Parser a) -> DecodeOptions -> ByteString -> Parser a
forall a.
(Header -> Parser a) -> DecodeOptions -> ByteString -> Parser a
decodeWithP' (NamedRecord -> Parser a
p (NamedRecord -> Parser a)
-> (Header -> NamedRecord) -> Header -> Parser a
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Header -> Header -> NamedRecord
toNamedRecord Header
hdr) DecodeOptions
opts ByteString
rest)

------------------------------------------------------------------------

-- TODO: 'decodeWithP' should probably not take an initial
-- 'B.ByteString' input.

-- | Like 'decode', but lets you customize how the CSV data is parsed.
decodeWithP' :: (Record -> Conversion.Parser a) -> DecodeOptions -> B.ByteString
            -> Parser a
decodeWithP' :: (Header -> Parser a) -> DecodeOptions -> ByteString -> Parser a
decodeWithP' Header -> Parser a
p !DecodeOptions
opts = More -> [Either String a] -> IResult ByteString Header -> Parser a
go More
Incomplete [] (IResult ByteString Header -> Parser a)
-> (ByteString -> IResult ByteString Header)
-> ByteString
-> Parser a
forall b c a. (b -> c) -> (a -> b) -> a -> c
. ByteString -> IResult ByteString Header
parser
  where
    go :: More -> [Either String a] -> IResult ByteString Header -> Parser a
go !More
_ ![Either String a]
acc (A.Fail ByteString
rest [String]
_ String
msg)
        | [Either String a] -> Bool
forall (t :: * -> *) a. Foldable t => t a -> Bool
null [Either String a]
acc  = ByteString -> String -> Parser a
forall a. ByteString -> String -> Parser a
Fail ByteString
rest String
err
        | Bool
otherwise = [Either String a] -> (ByteString -> Parser a) -> Parser a
forall a. [Either String a] -> (ByteString -> Parser a) -> Parser a
Many ([Either String a] -> [Either String a]
forall a. [a] -> [a]
reverse [Either String a]
acc) (\ ByteString
s -> ByteString -> String -> Parser a
forall a. ByteString -> String -> Parser a
Fail (ByteString
rest ByteString -> ByteString -> ByteString
`B.append` ByteString
s) String
err)
      where err :: String
err = String
"parse error (" String -> ShowS
forall a. [a] -> [a] -> [a]
++ String
msg String -> ShowS
forall a. [a] -> [a] -> [a]
++ String
")"
    go More
Incomplete [Either String a]
acc (A.Partial ByteString -> IResult ByteString Header
k) = [Either String a] -> (ByteString -> Parser a) -> Parser a
forall a. [Either String a] -> (ByteString -> Parser a) -> Parser a
Many ([Either String a] -> [Either String a]
forall a. [a] -> [a]
reverse [Either String a]
acc) ByteString -> Parser a
cont
      where cont :: ByteString -> Parser a
cont ByteString
s = More -> [Either String a] -> IResult ByteString Header -> Parser a
go More
m [] (ByteString -> IResult ByteString Header
k ByteString
s)
              where m :: More
m | ByteString -> Bool
B.null ByteString
s  = More
Complete
                      | Bool
otherwise = More
Incomplete
    go More
Complete [Either String a]
_ (A.Partial ByteString -> IResult ByteString Header
_) = String -> String -> Parser a
forall a. String -> String -> a
moduleError String
"decodeWithP'" String
msg
        where msg :: String
msg = String
"attoparsec should never return Partial in this case"
    go More
m [Either String a]
acc (A.Done ByteString
rest Header
r)
        | ByteString -> Bool
B.null ByteString
rest = case More
m of
            More
Complete   -> [Either String a] -> Parser a
forall a. [Either String a] -> Parser a
Done ([Either String a] -> [Either String a]
forall a. [a] -> [a]
reverse [Either String a]
acc')
            More
Incomplete -> [Either String a] -> (ByteString -> Parser a) -> Parser a
forall a. [Either String a] -> (ByteString -> Parser a) -> Parser a
Many ([Either String a] -> [Either String a]
forall a. [a] -> [a]
reverse [Either String a]
acc') ([Either String a] -> ByteString -> Parser a
cont [])
        | Bool
otherwise   = More -> [Either String a] -> IResult ByteString Header -> Parser a
go More
m [Either String a]
acc' (ByteString -> IResult ByteString Header
parser ByteString
rest)
      where cont :: [Either String a] -> ByteString -> Parser a
cont [Either String a]
acc'' ByteString
s
                | ByteString -> Bool
B.null ByteString
s  = [Either String a] -> Parser a
forall a. [Either String a] -> Parser a
Done ([Either String a] -> [Either String a]
forall a. [a] -> [a]
reverse [Either String a]
acc'')
                | Bool
otherwise = More -> [Either String a] -> IResult ByteString Header -> Parser a
go More
Incomplete [Either String a]
acc'' (ByteString -> IResult ByteString Header
parser ByteString
s)
            acc' :: [Either String a]
acc' | Header -> Bool
blankLine Header
r = [Either String a]
acc
                 | Bool
otherwise   = let !r' :: Either String a
r' = Header -> Either String a
convert Header
r in Either String a
r' Either String a -> [Either String a] -> [Either String a]
forall a. a -> [a] -> [a]
: [Either String a]
acc

    parser :: ByteString -> IResult ByteString Header
parser = Parser Header -> ByteString -> IResult ByteString Header
forall a. Parser a -> ByteString -> Result a
A.parse (Word8 -> Parser Header
record (DecodeOptions -> Word8
decDelimiter DecodeOptions
opts) Parser Header -> Parser ByteString () -> Parser Header
forall (f :: * -> *) a b. Applicative f => f a -> f b -> f a
<* (Parser ByteString ()
endOfLine Parser ByteString ()
-> Parser ByteString () -> Parser ByteString ()
forall (f :: * -> *) a. Alternative f => f a -> f a -> f a
<|> Parser ByteString ()
forall t. Chunk t => Parser t ()
endOfInput))
    convert :: Header -> Either String a
convert = Parser a -> Either String a
forall a. Parser a -> Either String a
runParser (Parser a -> Either String a)
-> (Header -> Parser a) -> Header -> Either String a
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Header -> Parser a
p
{-# INLINE decodeWithP' #-}

blankLine :: V.Vector B.ByteString -> Bool
blankLine :: Header -> Bool
blankLine Header
v = Header -> Int
forall a. Vector a -> Int
V.length Header
v Int -> Int -> Bool
forall a. Eq a => a -> a -> Bool
== Int
1 Bool -> Bool -> Bool
&& (ByteString -> Bool
B.null (Header -> ByteString
forall a. Vector a -> a
V.head Header
v))

------------------------------------------------------------------------
-- * Encoding

-- | Efficiently serialize records in an incremental
-- fashion. Equivalent to @'encodeWith' 'defaultEncodeOptions'@.
encode :: ToRecord a => Builder a -> L.ByteString
encode :: Builder a -> ByteString
encode = EncodeOptions -> Builder a -> ByteString
forall a. ToRecord a => EncodeOptions -> Builder a -> ByteString
encodeWith EncodeOptions
Encoding.defaultEncodeOptions

-- | Like 'encode', but lets you customize how the CSV data is
-- encoded.
encodeWith :: ToRecord a => EncodeOptions -> Builder a
                 -> L.ByteString
encodeWith :: EncodeOptions -> Builder a -> ByteString
encodeWith EncodeOptions
opts Builder a
b =
    Builder -> ByteString
Builder.toLazyByteString (Builder -> ByteString) -> Builder -> ByteString
forall a b. (a -> b) -> a -> b
$
    Builder a -> Quoting -> Word8 -> Bool -> Builder
forall a. Builder a -> Quoting -> Word8 -> Bool -> Builder
runBuilder Builder a
b (EncodeOptions -> Quoting
encQuoting EncodeOptions
opts) (EncodeOptions -> Word8
encDelimiter EncodeOptions
opts) (EncodeOptions -> Bool
encUseCrLf EncodeOptions
opts)

-- | Encode a single record.
encodeRecord :: ToRecord a => a -> Builder a
encodeRecord :: a -> Builder a
encodeRecord a
r = (Quoting -> Word8 -> Bool -> Builder) -> Builder a
forall a. (Quoting -> Word8 -> Bool -> Builder) -> Builder a
Builder ((Quoting -> Word8 -> Bool -> Builder) -> Builder a)
-> (Quoting -> Word8 -> Bool -> Builder) -> Builder a
forall a b. (a -> b) -> a -> b
$ \ Quoting
qtng Word8
delim Bool
useCrLf ->
    Quoting -> Word8 -> Header -> Builder
Encoding.encodeRecord Quoting
qtng Word8
delim (a -> Header
forall a. ToRecord a => a -> Header
toRecord a
r) Builder -> Builder -> Builder
forall a. Semigroup a => a -> a -> a
<> Bool -> Builder
recordSep Bool
useCrLf

-- | A builder for building the CSV data incrementally. Just like the
-- @ByteString@ builder, this builder should be used in a
-- right-associative, 'foldr' style. Using '<>' to compose builders in
-- a left-associative, `foldl'` style makes the building not be
-- incremental.
newtype Builder a = Builder {
      Builder a -> Quoting -> Word8 -> Bool -> Builder
runBuilder :: Quoting -> Word8 -> Bool -> Builder.Builder
    }

-- | @since 0.5.0.0
instance Semi.Semigroup (Builder a) where
    Builder Quoting -> Word8 -> Bool -> Builder
f <> :: Builder a -> Builder a -> Builder a
<> Builder Quoting -> Word8 -> Bool -> Builder
g =
        (Quoting -> Word8 -> Bool -> Builder) -> Builder a
forall a. (Quoting -> Word8 -> Bool -> Builder) -> Builder a
Builder ((Quoting -> Word8 -> Bool -> Builder) -> Builder a)
-> (Quoting -> Word8 -> Bool -> Builder) -> Builder a
forall a b. (a -> b) -> a -> b
$ \ Quoting
qtng Word8
delim Bool
useCrlf ->
        Quoting -> Word8 -> Bool -> Builder
f Quoting
qtng Word8
delim Bool
useCrlf Builder -> Builder -> Builder
forall a. Semigroup a => a -> a -> a
<> Quoting -> Word8 -> Bool -> Builder
g Quoting
qtng Word8
delim Bool
useCrlf

instance Monoid (Builder a) where
    mempty :: Builder a
mempty  = (Quoting -> Word8 -> Bool -> Builder) -> Builder a
forall a. (Quoting -> Word8 -> Bool -> Builder) -> Builder a
Builder (\ Quoting
_ Word8
_ Bool
_ -> Builder
forall a. Monoid a => a
mempty)
    mappend :: Builder a -> Builder a -> Builder a
mappend = Builder a -> Builder a -> Builder a
forall a. Semigroup a => a -> a -> a
(Semi.<>)

------------------------------------------------------------------------
-- ** Index-based record conversion

-- | Efficiently serialize named records in an incremental fashion,
-- including the leading header. Equivalent to @'encodeWith'
-- 'defaultEncodeOptions'@. The header is written before any records
-- and dictates the field order.
encodeByName :: ToNamedRecord a => Header -> NamedBuilder a -> L.ByteString
encodeByName :: Header -> NamedBuilder a -> ByteString
encodeByName = EncodeOptions -> Header -> NamedBuilder a -> ByteString
forall a.
ToNamedRecord a =>
EncodeOptions -> Header -> NamedBuilder a -> ByteString
encodeByNameWith EncodeOptions
Encoding.defaultEncodeOptions

-- | Like 'encodeByName', but header and field order is dictated by
-- the 'Conversion.headerOrder' method.
encodeDefaultOrderedByName :: (DefaultOrdered a, ToNamedRecord a) =>
                              NamedBuilder a -> L.ByteString
encodeDefaultOrderedByName :: NamedBuilder a -> ByteString
encodeDefaultOrderedByName =
    EncodeOptions -> NamedBuilder a -> ByteString
forall a.
(DefaultOrdered a, ToNamedRecord a) =>
EncodeOptions -> NamedBuilder a -> ByteString
encodeDefaultOrderedByNameWith EncodeOptions
Encoding.defaultEncodeOptions

-- | Like 'encodeByName', but lets you customize how the CSV data is
-- encoded.
encodeByNameWith :: ToNamedRecord a => EncodeOptions -> Header -> NamedBuilder a
                 -> L.ByteString
encodeByNameWith :: EncodeOptions -> Header -> NamedBuilder a -> ByteString
encodeByNameWith EncodeOptions
opts Header
hdr NamedBuilder a
b =
    Builder -> ByteString
Builder.toLazyByteString (Builder -> ByteString) -> Builder -> ByteString
forall a b. (a -> b) -> a -> b
$
    Builder
encHdr Builder -> Builder -> Builder
forall a. Semigroup a => a -> a -> a
<>
    NamedBuilder a -> Header -> Quoting -> Word8 -> Bool -> Builder
forall a.
NamedBuilder a -> Header -> Quoting -> Word8 -> Bool -> Builder
runNamedBuilder NamedBuilder a
b Header
hdr (EncodeOptions -> Quoting
encQuoting EncodeOptions
opts) (EncodeOptions -> Word8
encDelimiter EncodeOptions
opts)
    (EncodeOptions -> Bool
encUseCrLf EncodeOptions
opts)
  where
    encHdr :: Builder
encHdr
      | EncodeOptions -> Bool
encIncludeHeader EncodeOptions
opts =
          Quoting -> Word8 -> Header -> Builder
Encoding.encodeRecord (EncodeOptions -> Quoting
encQuoting EncodeOptions
opts) (EncodeOptions -> Word8
encDelimiter EncodeOptions
opts) Header
hdr
          Builder -> Builder -> Builder
forall a. Semigroup a => a -> a -> a
<> Bool -> Builder
recordSep (EncodeOptions -> Bool
encUseCrLf EncodeOptions
opts)
      | Bool
otherwise = Builder
forall a. Monoid a => a
mempty

-- | Like 'encodeDefaultOrderedByName', but lets you customize how the
-- CSV data is encoded.
encodeDefaultOrderedByNameWith ::
    forall a. (DefaultOrdered a, ToNamedRecord a) =>
    EncodeOptions -> NamedBuilder a -> L.ByteString
encodeDefaultOrderedByNameWith :: EncodeOptions -> NamedBuilder a -> ByteString
encodeDefaultOrderedByNameWith EncodeOptions
opts NamedBuilder a
b =
    Builder -> ByteString
Builder.toLazyByteString (Builder -> ByteString) -> Builder -> ByteString
forall a b. (a -> b) -> a -> b
$
    Builder
encHdr Builder -> Builder -> Builder
forall a. Semigroup a => a -> a -> a
<>
    NamedBuilder a -> Header -> Quoting -> Word8 -> Bool -> Builder
forall a.
NamedBuilder a -> Header -> Quoting -> Word8 -> Bool -> Builder
runNamedBuilder NamedBuilder a
b Header
hdr (EncodeOptions -> Quoting
encQuoting EncodeOptions
opts)
    (EncodeOptions -> Word8
encDelimiter EncodeOptions
opts) (EncodeOptions -> Bool
encUseCrLf EncodeOptions
opts)
  where
    hdr :: Header
hdr = a -> Header
forall a. DefaultOrdered a => a -> Header
Conversion.headerOrder (a
forall a. HasCallStack => a
undefined :: a)

    encHdr :: Builder
encHdr
      | EncodeOptions -> Bool
encIncludeHeader EncodeOptions
opts =
          Quoting -> Word8 -> Header -> Builder
Encoding.encodeRecord (EncodeOptions -> Quoting
encQuoting EncodeOptions
opts) (EncodeOptions -> Word8
encDelimiter EncodeOptions
opts) Header
hdr
          Builder -> Builder -> Builder
forall a. Semigroup a => a -> a -> a
<> Bool -> Builder
recordSep (EncodeOptions -> Bool
encUseCrLf EncodeOptions
opts)
      | Bool
otherwise = Builder
forall a. Monoid a => a
mempty

-- | Encode a single named record.
encodeNamedRecord :: ToNamedRecord a => a -> NamedBuilder a
encodeNamedRecord :: a -> NamedBuilder a
encodeNamedRecord a
nr = (Header -> Quoting -> Word8 -> Bool -> Builder) -> NamedBuilder a
forall a.
(Header -> Quoting -> Word8 -> Bool -> Builder) -> NamedBuilder a
NamedBuilder ((Header -> Quoting -> Word8 -> Bool -> Builder) -> NamedBuilder a)
-> (Header -> Quoting -> Word8 -> Bool -> Builder)
-> NamedBuilder a
forall a b. (a -> b) -> a -> b
$ \ Header
hdr Quoting
qtng Word8
delim Bool
useCrLf ->
    Header -> Quoting -> Word8 -> NamedRecord -> Builder
Encoding.encodeNamedRecord Header
hdr Quoting
qtng Word8
delim
    (a -> NamedRecord
forall a. ToNamedRecord a => a -> NamedRecord
Conversion.toNamedRecord a
nr) Builder -> Builder -> Builder
forall a. Semigroup a => a -> a -> a
<> Bool -> Builder
recordSep Bool
useCrLf

-- | A builder for building the CSV data incrementally. Just like the
-- @ByteString@ builder, this builder should be used in a
-- right-associative, 'foldr' style. Using '<>' to compose builders in
-- a left-associative, `foldl'` style makes the building not be
-- incremental.
newtype NamedBuilder a = NamedBuilder {
      NamedBuilder a -> Header -> Quoting -> Word8 -> Bool -> Builder
runNamedBuilder :: Header -> Quoting -> Word8 -> Bool -> Builder.Builder
    }

-- | @since 0.5.0.0
instance Semigroup (NamedBuilder a) where
    NamedBuilder Header -> Quoting -> Word8 -> Bool -> Builder
f <> :: NamedBuilder a -> NamedBuilder a -> NamedBuilder a
<> NamedBuilder Header -> Quoting -> Word8 -> Bool -> Builder
g =
        (Header -> Quoting -> Word8 -> Bool -> Builder) -> NamedBuilder a
forall a.
(Header -> Quoting -> Word8 -> Bool -> Builder) -> NamedBuilder a
NamedBuilder ((Header -> Quoting -> Word8 -> Bool -> Builder) -> NamedBuilder a)
-> (Header -> Quoting -> Word8 -> Bool -> Builder)
-> NamedBuilder a
forall a b. (a -> b) -> a -> b
$ \ Header
hdr Quoting
qtng Word8
delim Bool
useCrlf ->
        Header -> Quoting -> Word8 -> Bool -> Builder
f Header
hdr Quoting
qtng Word8
delim Bool
useCrlf Builder -> Builder -> Builder
forall a. Semigroup a => a -> a -> a
<> Header -> Quoting -> Word8 -> Bool -> Builder
g Header
hdr Quoting
qtng Word8
delim Bool
useCrlf

instance Monoid (NamedBuilder a) where
    mempty :: NamedBuilder a
mempty = (Header -> Quoting -> Word8 -> Bool -> Builder) -> NamedBuilder a
forall a.
(Header -> Quoting -> Word8 -> Bool -> Builder) -> NamedBuilder a
NamedBuilder (\ Header
_ Quoting
_ Word8
_ Bool
_ -> Builder
forall a. Monoid a => a
mempty)
    mappend :: NamedBuilder a -> NamedBuilder a -> NamedBuilder a
mappend = NamedBuilder a -> NamedBuilder a -> NamedBuilder a
forall a. Semigroup a => a -> a -> a
(Semi.<>)

------------------------------------------------------------------------

moduleError :: String -> String -> a
moduleError :: String -> String -> a
moduleError String
func String
msg = String -> a
forall a. HasCallStack => String -> a
error (String -> a) -> String -> a
forall a b. (a -> b) -> a -> b
$ String
"Data.Csv.Incremental." String -> ShowS
forall a. [a] -> [a] -> [a]
++ String
func String -> ShowS
forall a. [a] -> [a] -> [a]
++ String
": " String -> ShowS
forall a. [a] -> [a] -> [a]
++ String
msg
{-# NOINLINE moduleError #-}