@@ -24,7 +24,9 @@ module Data.Csv.Encoding
2424 , DecodeOptions (.. )
2525 , defaultDecodeOptions
2626 , decodeWith
27+ , decodeWithP
2728 , decodeByNameWith
29+ , decodeByNameWithP
2830 , EncodeOptions (.. )
2931 , defaultEncodeOptions
3032 , encodeWith
@@ -57,6 +59,7 @@ import Data.Csv.Conversion (FromNamedRecord, FromRecord, ToNamedRecord,
5759 ToRecord , parseNamedRecord , parseRecord , runParser ,
5860 toNamedRecord , toRecord )
5961import Data.Csv.Parser hiding (csv , csvWithHeader )
62+ import qualified Data.Csv.Conversion as Conversion
6063import qualified Data.Csv.Parser as Parser
6164import Data.Csv.Types hiding (toNamedRecord )
6265import qualified Data.Csv.Types as Types
@@ -117,7 +120,7 @@ decodeWith :: FromRecord a
117120 -- skipped
118121 -> L. ByteString -- ^ CSV data
119122 -> Either String (Vector a )
120- decodeWith = decodeWithC csv
123+ decodeWith = decodeWithC ( csv parseRecord)
121124{-# INLINE [1] decodeWith #-}
122125
123126{-# RULES
@@ -130,12 +133,23 @@ idDecodeWith :: DecodeOptions -> HasHeader -> L.ByteString
130133 -> Either String (Vector (Vector B. ByteString ))
131134idDecodeWith = decodeWithC Parser. csv
132135
136+ -- | Like 'decodeWith'', but lets you specify a parser function.
137+ decodeWithP :: (Record -> Conversion. Parser a )
138+ -- ^ Custom parser function
139+ -> DecodeOptions -- ^ Decoding options
140+ -> HasHeader -- ^ Data contains header that should be
141+ -- skipped
142+ -> L. ByteString -- ^ CSV data
143+ -> Either String (Vector a )
144+ decodeWithP _parseRecord = decodeWithC (csv _parseRecord)
145+ {-# INLINE [1] decodeWithP #-}
146+
133147-- | Decode CSV data using the provided parser, skipping a leading
134148-- header if 'hasHeader' is 'HasHeader'. Returns 'Left' @errMsg@ on
135149-- failure.
136150decodeWithC :: (DecodeOptions -> AL. Parser a ) -> DecodeOptions -> HasHeader
137151 -> BL8. ByteString -> Either String a
138- decodeWithC p ! opts hasHeader = decodeWithP parser
152+ decodeWithC p ! opts hasHeader = decodeWithP' parser
139153 where parser = case hasHeader of
140154 HasHeader -> header (decDelimiter opts) *> p opts
141155 NoHeader -> p opts
@@ -147,7 +161,16 @@ decodeByNameWith :: FromNamedRecord a
147161 => DecodeOptions -- ^ Decoding options
148162 -> L. ByteString -- ^ CSV data
149163 -> Either String (Header , Vector a )
150- decodeByNameWith ! opts = decodeWithP (csvWithHeader opts)
164+ decodeByNameWith ! opts = decodeWithP' (csvWithHeader parseNamedRecord opts)
165+
166+ -- | Like 'decodeByNameWith', but lets you specify a parser function.
167+ decodeByNameWithP :: (NamedRecord -> Conversion. Parser a )
168+ -- ^ Custom parser function
169+ -> DecodeOptions -- ^ Decoding options
170+ -> L. ByteString -- ^ CSV data
171+ -> Either String (Header , Vector a )
172+ decodeByNameWithP _parseNamedRecord ! opts =
173+ decodeWithP' (csvWithHeader _parseNamedRecord opts)
151174
152175-- | Should quoting be applied to fields, and at which level?
153176data Quoting
@@ -328,8 +351,8 @@ prependToAll :: Builder -> [Builder] -> [Builder]
328351prependToAll _ [] = []
329352prependToAll sep (x: xs) = sep <> x : prependToAll sep xs
330353
331- decodeWithP :: AL. Parser a -> L. ByteString -> Either String a
332- decodeWithP p s =
354+ decodeWithP' :: AL. Parser a -> L. ByteString -> Either String a
355+ decodeWithP' p s =
333356 case AL. parse p s of
334357 AL. Done _ v -> Right v
335358 AL. Fail left _ msg -> Left errMsg
@@ -338,7 +361,7 @@ decodeWithP p s =
338361 (if BL8. length left > 100
339362 then (take 100 $ BL8. unpack left) ++ " (truncated)"
340363 else show (BL8. unpack left))
341- {-# INLINE decodeWithP #-}
364+ {-# INLINE decodeWithP' #-}
342365
343366-- These alternative implementation of the 'csv' and 'csvWithHeader'
344367-- parsers from the 'Parser' module performs the
@@ -351,26 +374,27 @@ decodeWithP p s =
351374-- "parse error: conversion error: ...".
352375
353376-- | Parse a CSV file that does not include a header.
354- csv :: FromRecord a => DecodeOptions -> AL. Parser (V. Vector a )
355- csv ! opts = do
377+ csv :: (Record -> Conversion. Parser a ) -> DecodeOptions
378+ -> AL. Parser (V. Vector a )
379+ csv _parseRecord ! opts = do
356380 vals <- records
357381 return $! V. fromList vals
358382 where
359383 records = do
360384 ! r <- record (decDelimiter opts)
361385 if blankLine r
362386 then (endOfInput *> pure [] ) <|> (endOfLine *> records)
363- else case runParser (parseRecord r) of
387+ else case runParser (_parseRecord r) of
364388 Left msg -> fail $ " conversion error: " ++ msg
365389 Right val -> do
366390 ! vals <- (endOfInput *> AP. pure [] ) <|> (endOfLine *> records)
367391 return (val : vals)
368392{-# INLINE csv #-}
369393
370394-- | Parse a CSV file that includes a header.
371- csvWithHeader :: FromNamedRecord a = > DecodeOptions
395+ csvWithHeader :: ( NamedRecord -> Conversion. Parser a ) - > DecodeOptions
372396 -> AL. Parser (Header , V. Vector a )
373- csvWithHeader ! opts = do
397+ csvWithHeader _parseNamedRecord ! opts = do
374398 ! hdr <- header (decDelimiter opts)
375399 vals <- records hdr
376400 let ! v = V. fromList vals
@@ -386,4 +410,4 @@ csvWithHeader !opts = do
386410 ! vals <- (endOfInput *> pure [] ) <|> (endOfLine *> records hdr)
387411 return (val : vals)
388412
389- convert hdr = parseNamedRecord . Types. toNamedRecord hdr
413+ convert hdr = _parseNamedRecord . Types. toNamedRecord hdr
0 commit comments