cassava 0.2.0.0 → 0.2.1.0
raw patch · 8 files changed
+290/−169 lines, 8 filesPVP ok
version bump matches the API change (PVP)
API changes (from Hackage documentation)
+ Data.Csv: index :: FromField a => Record -> Int -> Parser a
+ Data.Csv: lookup :: FromField a => NamedRecord -> ByteString -> Parser a
+ Data.Csv: namedField :: ToField a => ByteString -> a -> (ByteString, ByteString)
+ Data.Csv: runParser :: Parser a -> Either String a
+ Data.Csv.Parser: instance Eq DecodeOptions
+ Data.Csv.Parser: instance Show DecodeOptions
Files
- Data/Csv.hs +48/−9
- Data/Csv/Conversion.hs +144/−140
- Data/Csv/Encoding.hs +17/−9
- Data/Csv/Incremental.hs +8/−7
- Data/Csv/Parser.hs +10/−2
- Data/Csv/Streaming.hs +49/−1
- cassava.cabal +1/−1
- examples/StreamingIndexBasedDecode.hs +13/−0
Data/Csv.hs view
@@ -17,6 +17,9 @@ -- * Treating CSV data as opaque byte strings -- $generic-processing + -- * Custom type conversions+ -- $customtypeconversions+ -- * Encoding and decoding -- $encoding decode@@ -50,6 +53,8 @@ -- $indexbased , FromRecord(..) , Parser+ , runParser+ , index , (.!) , ToRecord(..) , record@@ -58,16 +63,21 @@ -- ** Name-based record conversion -- $namebased , FromNamedRecord(..)+ , lookup , (.:) , ToNamedRecord(..) , namedRecord+ , namedField , (.=) -- ** Field conversion+ -- $fieldconversion , FromField(..) , ToField(..) ) where +import Prelude hiding (lookup)+ import Data.Csv.Conversion import Data.Csv.Encoding import Data.Csv.Types@@ -76,9 +86,8 @@ -- -- A short encoding usage example: ----- @ >>> 'encode' $ fromList [(\"John\" :: Text, 27), (\"Jane\", 28)]--- Chunk \"John,27\\r\\nJane,28\\r\\n\" Empty--- @+-- > >>> encode $ fromList [("John" :: Text, 27), ("Jane", 28)]+-- > Chunk "John,27\r\nJane,28\r\n" Empty -- -- Since string literals are overloaded we have to supply a type -- signature as the compiler couldn't deduce which string type (i.e.@@ -88,9 +97,8 @@ -- -- A short decoding usage example: ----- @ >>> 'decode' 'False' \"John,27\\r\\nJane,28\\r\\n\" :: Either String (Vector (Text, Int))--- Right (fromList [(\"John\",27),(\"Jane\",28)])--- @+-- > >>> decode False "John,27\r\nJane,28\r\n" :: Either String (Vector (Text, Int))+-- > Right (fromList [("John",27),("Jane",28)]) -- -- We pass 'False' as the first argument to indicate that the CSV -- input data isn't preceded by a header.@@ -106,13 +114,37 @@ -- parse a CSV file to a generic representation, just convert each -- record to a @'Vector' 'ByteString'@ value, like so: ----- @ 'decode' 'False' \"John,27\\r\\nJane,28\\r\\n\" :: Either String (Vector (Vector ByteString))--- Right (fromList [fromList [\"John\",\"27\"],fromList [\"Jane\",\"28\"]])--- @+-- > decode False "John,27\r\nJane,28\r\n" :: Either String (Vector (Vector ByteString))+-- > Right (fromList [fromList ["John","27"],fromList ["Jane","28"]]) -- -- As the example output above shows, all the fields are returned as -- uninterpreted 'ByteString' values. +-- $customtypeconversions+--+-- Most of the time the existing 'FromField' and 'ToField' instances+-- do what you want. However, if you need to parse a different format+-- (e.g. hex) but use a type (e.g. 'Int') for which there's already a+-- 'FromField' instance, you need to use a @newtype@. Example:+--+-- > newtype Hex = Hex Int+-- >+-- > parseHex :: ByteString -> Parser Int+-- > parseHex = ...+-- >+-- > instance FromField Hex where+-- > parseField s = Hex <$> parseHex s+--+-- Other than giving an explicit type signature, you can pattern match+-- on the @newtype@ constructor to indicate which type conversion you+-- want to have the library use:+--+-- > case decode False "0xff,0xaa\r\n0x11,0x22\r\n" of+-- > Left err -> putStrLn err+-- > Right v -> forM_ v $ \ (Hex val1, Hex val2) ->+-- > print (val1, val2)++ -- $encoding -- -- Encoding and decoding is a two step process. To encode a value, it@@ -149,3 +181,10 @@ -- These functions can be used to control how data is encoded and -- decoded. For example, they can be used to encode data in a -- tab-separated format instead of in a comma-separated format.++-- $fieldconversion+--+-- The 'FromField' and 'ToField' classes define how to convert between+-- 'Field's and values you care about (e.g. 'Int's). Most of the time+-- you don't need to write your own instances as the standard ones+-- cover most use cases.
Data/Csv/Conversion.hs view
@@ -16,14 +16,15 @@ , ToField(..) -- * Parser- , Result(..) , Parser- , parse- , parseEither+ , runParser -- * Accessors+ , index , (.!)+ , lookup , (.:)+ , namedField , (.=) , record , namedRecord@@ -31,7 +32,8 @@ import Control.Applicative import Control.Monad-import Data.Attoparsec.Char8 (double, number, parseOnly)+import Data.Attoparsec.Char8 (double, parseOnly)+import qualified Data.Attoparsec.Char8 as A8 import qualified Data.ByteString as B import qualified Data.ByteString.Char8 as B8 import qualified Data.ByteString.Lazy as L@@ -49,7 +51,7 @@ import qualified Data.Vector.Unboxed as U import Data.Word import GHC.Float (double2Float)-import Prelude hiding (takeWhile)+import Prelude hiding (lookup, takeWhile) import Data.Csv.Conversion.Internal import Data.Csv.Types@@ -79,15 +81,14 @@ -- -- here's an example type and instance: ----- @data Person = Person { name :: Text, age :: Int }------ instance FromRecord Person where--- parseRecord v--- | 'V.length' v == 2 = Person '<$>'--- v '.!' 0 '<*>'--- v '.!' 1--- | otherwise = mzero--- @+-- > data Person = Person { name :: Text, age :: Int }+-- >+-- > instance FromRecord Person where+-- > parseRecord v+-- > | length v == 2 = Person <$>+-- > v .! 0 <*>+-- > v .! 1+-- > | otherwise = mzero class FromRecord a where parseRecord :: Record -> Parser a @@ -107,12 +108,11 @@ -- -- An example type and instance: ----- @data Person = Person { name :: Text, age :: Int }------ instance ToRecord Person where--- toRecord (Person name age) = 'record' [--- 'toField' name, 'toField' age]--- @+-- > data Person = Person { name :: Text, age :: Int }+-- >+-- > instance ToRecord Person where+-- > toRecord (Person name age) = 'record' [+-- > 'toField' name, 'toField' age] -- -- Outputs data on this form: --@@ -283,15 +283,14 @@ -- -- here's an example type and instance: ----- @{-\# LANGUAGE OverloadedStrings \#-}------ data Person = Person { name :: Text, age :: Int }------ instance FromRecord Person where--- parseNamedRecord m = Person '<$>'--- m '.:' \"name\" '<*>'--- m '.:' \"age\"--- @+-- > {-# LANGUAGE OverloadedStrings #-}+-- >+-- > data Person = Person { name :: Text, age :: Int }+-- >+-- > instance FromRecord Person where+-- > parseNamedRecord m = Person <$>+-- > m .: "name" <*>+-- > m .: "age" -- -- Note the use of the @OverloadedStrings@ language extension which -- enables 'B8.ByteString' values to be written as string literals.@@ -307,12 +306,11 @@ -- -- An example type and instance: ----- @data Person = Person { name :: Text, age :: Int }------ instance ToRecord Person where--- toNamedRecord (Person name age) = 'namedRecord' [--- \"name\" '.=' name, \"age\" '.=' age]--- @+-- > data Person = Person { name :: Text, age :: Int }+-- >+-- > instance ToRecord Person where+-- > toNamedRecord (Person name age) = namedRecord [+-- > "name" .= name, "age" .= age] class ToNamedRecord a where toNamedRecord :: a -> NamedRecord @@ -347,17 +345,16 @@ -- -- Example type and instance: ----- @{-\# LANGUAGE OverloadedStrings \#-}------ data Color = Red | Green | Blue------ instance FromField Color where--- parseField s--- | s == \"R\" = pure Red--- | s == \"G\" = pure Green--- | s == \"B\" = pure Blue--- | otherwise = mzero--- @+-- > {-# LANGUAGE OverloadedStrings #-}+-- >+-- > data Color = Red | Green | Blue+-- >+-- > instance FromField Color where+-- > parseField s+-- > | s == "R" = pure Red+-- > | s == "G" = pure Green+-- > | s == "B" = pure Blue+-- > | otherwise = mzero class FromField a where parseField :: Field -> Parser a @@ -365,30 +362,30 @@ -- -- Example type and instance: ----- @{-\# LANGUAGE OverloadedStrings \#-}------ data Color = Red | Green | Blue------ instance ToField Color where--- toField Red = \"R\"--- toField Green = \"G\"--- toField Blue = \"B\"--- @+-- > {-# LANGUAGE OverloadedStrings #-}+-- >+-- > data Color = Red | Green | Blue+-- >+-- > instance ToField Color where+-- > toField Red = "R"+-- > toField Green = "G"+-- > toField Blue = "B" class ToField a where toField :: a -> Field --- | 'Nothing' if the field is empty, 'Just' otherwise.+-- | 'Nothing' if the 'Field' is 'B.empty', 'Just' otherwise. instance FromField a => FromField (Maybe a) where parseField s | B.null s = pure Nothing | otherwise = Just <$> parseField s {-# INLINE parseField #-} --- | 'Nothing' is encoded as an empty field.+-- | 'Nothing' is encoded as an 'B.empty' field. instance ToField a => ToField (Maybe a) where toField = maybe B.empty toField {-# INLINE toField #-} +-- | Assumes UTF-8 encoding. instance FromField Char where parseField s | T.compareLength t 1 == EQ = pure (T.head t)@@ -396,22 +393,29 @@ where t = T.decodeUtf8 s {-# INLINE parseField #-} +-- | Uses UTF-8 encoding. instance ToField Char where toField = toField . T.encodeUtf8 . T.singleton {-# INLINE toField #-} +-- | Accepts same syntax as 'rational'. instance FromField Double where parseField = parseDouble {-# INLINE parseField #-} +-- | Uses decimal notation or scientific notation, depending on the+-- number. instance ToField Double where toField = realFloat {-# INLINE toField #-} +-- | Accepts same syntax as 'rational'. instance FromField Float where parseField s = double2Float <$> parseDouble s {-# INLINE parseField #-} +-- | Uses decimal notation or scientific notation, depending on the+-- number. instance ToField Float where toField = realFloat {-# INLINE toField #-}@@ -422,90 +426,112 @@ Right n -> pure n {-# INLINE parseDouble #-} +-- | Accepts a signed decimal number. instance FromField Int where- parseField = parseIntegral "Int"+ parseField = parseSigned "Int" {-# INLINE parseField #-} +-- | Uses decimal encoding with optional sign. instance ToField Int where toField = decimal {-# INLINE toField #-} +-- | Accepts a signed decimal number. instance FromField Integer where- parseField = parseIntegral "Integer"+ parseField = parseSigned "Integer" {-# INLINE parseField #-} +-- | Uses decimal encoding with optional sign. instance ToField Integer where toField = decimal {-# INLINE toField #-} +-- | Accepts a signed decimal number. instance FromField Int8 where- parseField = parseIntegral "Int8"+ parseField = parseSigned "Int8" {-# INLINE parseField #-} +-- | Uses decimal encoding with optional sign. instance ToField Int8 where toField = decimal {-# INLINE toField #-} +-- | Accepts a signed decimal number. instance FromField Int16 where- parseField = parseIntegral "Int16"+ parseField = parseSigned "Int16" {-# INLINE parseField #-} +-- | Uses decimal encoding with optional sign. instance ToField Int16 where toField = decimal {-# INLINE toField #-} +-- | Accepts a signed decimal number. instance FromField Int32 where- parseField = parseIntegral "Int32"+ parseField = parseSigned "Int32" {-# INLINE parseField #-} +-- | Uses decimal encoding with optional sign. instance ToField Int32 where toField = decimal {-# INLINE toField #-} +-- | Accepts a signed decimal number. instance FromField Int64 where- parseField = parseIntegral "Int64"+ parseField = parseSigned "Int64" {-# INLINE parseField #-} +-- | Uses decimal encoding with optional sign. instance ToField Int64 where toField = decimal {-# INLINE toField #-} +-- | Accepts an unsigned decimal number. instance FromField Word where- parseField = parseIntegral "Word"+ parseField = parseUnsigned "Word" {-# INLINE parseField #-} +-- | Uses decimal encoding. instance ToField Word where toField = decimal {-# INLINE toField #-} +-- | Accepts an unsigned decimal number. instance FromField Word8 where- parseField = parseIntegral "Word8"+ parseField = parseUnsigned "Word8" {-# INLINE parseField #-} +-- | Uses decimal encoding. instance ToField Word8 where toField = decimal {-# INLINE toField #-} +-- | Accepts an unsigned decimal number. instance FromField Word16 where- parseField = parseIntegral "Word16"+ parseField = parseUnsigned "Word16" {-# INLINE parseField #-} +-- | Uses decimal encoding. instance ToField Word16 where toField = decimal {-# INLINE toField #-} +-- | Accepts an unsigned decimal number. instance FromField Word32 where- parseField = parseIntegral "Word32"+ parseField = parseUnsigned "Word32" {-# INLINE parseField #-} +-- | Uses decimal encoding. instance ToField Word32 where toField = decimal {-# INLINE toField #-} +-- | Accepts an unsigned decimal number. instance FromField Word64 where- parseField = parseIntegral "Word64"+ parseField = parseUnsigned "Word64" {-# INLINE parseField #-} +-- | Uses decimal encoding. instance ToField Word64 where toField = decimal {-# INLINE toField #-}@@ -556,12 +582,18 @@ toField = toField . T.pack {-# INLINE toField #-} -parseIntegral :: Integral a => String -> B.ByteString -> Parser a-parseIntegral typ s = case parseOnly number s of+parseSigned :: (Integral a, Num a) => String -> B.ByteString -> Parser a+parseSigned typ s = case parseOnly (A8.signed A8.decimal) s of Left err -> typeError typ s (Just err)- Right n -> pure (floor n)-{-# INLINE parseIntegral #-}+ Right n -> pure n+{-# INLINE parseSigned #-} +parseUnsigned :: Integral a => String -> B.ByteString -> Parser a+parseUnsigned typ s = case parseOnly A8.decimal s of+ Left err -> typeError typ s (Just err)+ Right n -> pure n+{-# INLINE parseUnsigned #-}+ typeError :: String -> B.ByteString -> Maybe String -> Parser a typeError typ s mmsg = fail $ "expected " ++ typ ++ ", got " ++ show (B8.unpack s) ++ cause@@ -576,22 +608,42 @@ -- | Retrieve the /n/th field in the given record. The result is -- 'empty' if the value cannot be converted to the desired type. -- Raises an exception if the index is out of bounds.+--+-- 'index' is a simple convenience function that is equivalent to+-- @'parseField' (v '!' idx)@. If you're certain that the index is not+-- out of bounds, using @'parseField' (`V.unsafeIndex` v idx)@ is+-- somewhat faster.+index :: FromField a => Record -> Int -> Parser a+index v idx = parseField (v ! idx)+{-# INLINE index #-}++-- | Alias for 'index'. (.!) :: FromField a => Record -> Int -> Parser a-v .! idx = parseField (v ! idx)+(.!) = index {-# INLINE (.!) #-} -- | Retrieve a field in the given record by name. The result is -- 'empty' if the field is missing or if the value cannot be converted -- to the desired type.-(.:) :: FromField a => NamedRecord -> B.ByteString -> Parser a-m .: name = maybe (fail err) parseField $ HM.lookup name m+lookup :: FromField a => NamedRecord -> B.ByteString -> Parser a+lookup m name = maybe (fail err) parseField $ HM.lookup name m where err = "no field named " ++ show (B8.unpack name)+{-# INLINE lookup #-}++-- | Alias for 'lookup'.+(.:) :: FromField a => NamedRecord -> B.ByteString -> Parser a+(.:) = lookup {-# INLINE (.:) #-} -- | Construct a pair from a name and a value. For use with -- 'namedRecord'.+namedField :: ToField a => B.ByteString -> a -> (B.ByteString, B.ByteString)+namedField name val = (name, toField val)+{-# INLINE namedField #-}++-- | Alias for 'namedField'. (.=) :: ToField a => B.ByteString -> a -> (B.ByteString, B.ByteString)-name .= val = (name, toField val)+(.=) = namedField {-# INLINE (.=) #-} -- | Construct a record from a list of 'B.ByteString's. Use 'toField'@@ -607,48 +659,6 @@ ------------------------------------------------------------------------ -- Parser for converting records to data types --- | The result of running a 'Parser'.-data Result a = Error String- | Success a- deriving (Eq, Show)--instance Functor Result where- fmap f (Success a) = Success (f a)- fmap _ (Error err) = Error err- {-# INLINE fmap #-}--instance Monad Result where- return = Success- {-# INLINE return #-}- Success a >>= k = k a- Error err >>= _ = Error err- {-# INLINE (>>=) #-}--instance Applicative Result where- pure = return- {-# INLINE pure #-}- (<*>) = ap- {-# INLINE (<*>) #-}--instance MonadPlus Result where- mzero = fail "mzero"- {-# INLINE mzero #-}- mplus a@(Success _) _ = a- mplus _ b = b- {-# INLINE mplus #-}--instance Alternative Result where- empty = mzero- {-# INLINE empty #-}- (<|>) = mplus- {-# INLINE (<|>) #-}--instance Monoid (Result a) where- mempty = fail "mempty"- {-# INLINE mempty #-}- mappend = mplus- {-# INLINE mappend #-}- -- | Failure continuation. type Failure f r = String -> f r -- | Success continuation.@@ -659,15 +669,15 @@ -- lets you compose several field conversions together in such a way -- that if any of them fail, the whole record conversion fails. newtype Parser a = Parser {- runParser :: forall f r.- Failure f r- -> Success a f r- -> f r+ unParser :: forall f r.+ Failure f r+ -> Success a f r+ -> f r } instance Monad Parser where- m >>= g = Parser $ \kf ks -> let ks' a = runParser (g a) kf ks- in runParser m kf ks'+ m >>= g = Parser $ \kf ks -> let ks' a = unParser (g a) kf ks+ in unParser m kf ks' {-# INLINE (>>=) #-} return a = Parser $ \_kf ks -> ks a {-# INLINE return #-}@@ -676,7 +686,7 @@ instance Functor Parser where fmap f m = Parser $ \kf ks -> let ks' a = ks (f a)- in runParser m kf ks'+ in unParser m kf ks' {-# INLINE fmap #-} instance Applicative Parser where@@ -694,8 +704,8 @@ instance MonadPlus Parser where mzero = fail "mzero" {-# INLINE mzero #-}- mplus a b = Parser $ \kf ks -> let kf' _ = runParser b kf ks- in runParser a kf' ks+ mplus a b = Parser $ \kf ks -> let kf' _ = unParser b kf ks+ in unParser a kf' ks {-# INLINE mplus #-} instance Monoid (Parser a) where@@ -711,24 +721,18 @@ return (b a) {-# INLINE apP #-} --- | Run a 'Parser'. Forces the value in the 'Success' or 'Error'--- constructors to weak head normal form.-parse :: Parser a -> Result a-parse p = runParser p err success- where- err !errMsg = Error errMsg- success !x = Success x-{-# INLINE parse #-}---- | Run a 'Parser', returning either @'Left' msg@ or @'Right'+-- | Run a 'Parser', returning either @'Left' errMsg@ or @'Right' -- result@. Forces the value in the 'Left' or 'Right' constructors to -- weak head normal form.-parseEither :: Parser a -> Either String a-parseEither p = runParser p left right+--+-- You most likely won't need to use this function directly, but it's+-- included for completeness.+runParser :: Parser a -> Either String a+runParser p = unParser p left right where left !errMsg = Left errMsg right !x = Right x-{-# INLINE parseEither #-}+{-# INLINE runParser #-} #ifdef GENERICS
Data/Csv/Encoding.hs view
@@ -75,12 +75,12 @@ decodeByName = decodeByNameWith defaultDecodeOptions {-# INLINE decodeByName #-} --- | Efficiently serialize CVS records as a lazy 'L.ByteString'.+-- | Efficiently serialize CSV records as a lazy 'L.ByteString'. encode :: ToRecord a => V.Vector a -> L.ByteString encode = encodeWith defaultEncodeOptions {-# INLINE encode #-} --- | Efficiently serialize CVS records as a lazy 'L.ByteString'. The+-- | Efficiently serialize CSV records as a lazy 'L.ByteString'. The -- header is written before any records and dictates the field order. encodeByName :: ToNamedRecord a => Header -> V.Vector a -> L.ByteString encodeByName = encodeByNameWith defaultEncodeOptions@@ -96,7 +96,7 @@ -- skipped -> L.ByteString -- ^ CSV data -> Either String (Vector a)-decodeWith = decodeWithC (parse . parseCsv)+decodeWith = decodeWithC (runParser . parseCsv) {-# INLINE [1] decodeWith #-} parseCsv :: FromRecord a => Csv -> Parser (Vector a)@@ -122,7 +122,7 @@ -> Either String (Vector (Vector B.ByteString)) idDecodeWith = decodeWithC pure -decodeWithC :: (Csv -> Result a) -> DecodeOptions -> Bool -> L.ByteString+decodeWithC :: (Csv -> Either String a) -> DecodeOptions -> Bool -> L.ByteString -> Either String a decodeWithC convert !opts skipHeader = decodeWithP parser convert where parser@@ -138,7 +138,7 @@ -> Either String (Header, Vector a) decodeByNameWith !opts = decodeWithP (csvWithHeader opts)- (\ (hdr, vs) -> (,) <$> pure hdr <*> (parse $ parseNamedCsv vs))+ (\ (hdr, vs) -> (,) <$> pure hdr <*> (runParser $ parseNamedCsv vs)) parseNamedCsv :: FromNamedRecord a => Vector NamedRecord -> Parser (Vector a) parseNamedCsv xs = V.fromList <$!> mapM' parseNamedRecord (V.toList xs)@@ -146,10 +146,18 @@ -- | Options that controls how data is encoded. These options can be -- used to e.g. encode data in a tab-separated format instead of in a -- comma-separated format.+--+-- To avoid having your program stop compiling when new fields are+-- added to 'EncodeOptions', create option records by overriding+-- values in 'defaultEncodeOptions'. Example:+--+-- > myOptions = defaultEncodeOptions {+-- > encDelimiter = fromIntegral (ord '\t')+-- > } data EncodeOptions = EncodeOptions { -- | Field delimiter. encDelimiter :: {-# UNPACK #-} !Word8- }+ } deriving (Eq, Show) -- | Encoding options for CSV files. defaultEncodeOptions :: EncodeOptions@@ -228,12 +236,12 @@ prependToAll _ [] = [] prependToAll sep (x:xs) = sep <> x : prependToAll sep xs -decodeWithP :: AL.Parser a -> (a -> Result b) -> L.ByteString -> Either String b+decodeWithP :: AL.Parser a -> (a -> Either String b) -> L.ByteString -> Either String b decodeWithP p to s = case AL.parse p s of AL.Done _ v -> case to v of- Success a -> Right a- Error msg -> Left $ "conversion error: " ++ msg+ Right a -> Right a+ Left msg -> Left $ "conversion error: " ++ msg AL.Fail left _ msg -> Left $ "parse error (" ++ msg ++ ") at " ++ show (BL8.unpack left) {-# INLINE decodeWithP #-}
Data/Csv/Incremental.hs view
@@ -42,7 +42,7 @@ import qualified Data.HashMap.Strict as HM import qualified Data.Vector as V -import Data.Csv.Conversion hiding (Parser, Result, record, toNamedRecord)+import Data.Csv.Conversion hiding (Parser, record, toNamedRecord) import qualified Data.Csv.Conversion as Conversion import Data.Csv.Parser import Data.Csv.Types@@ -124,7 +124,8 @@ feedEndOfInputH p = p -- | Parse a CSV header in an incremental fashion. When done, the--- 'HeaderParser' returns any unconsumed input in its second field.+-- 'HeaderParser' returns any unconsumed input in the second field of+-- the 'DoneH' constructor. decodeHeader :: HeaderParser B.ByteString decodeHeader = decodeHeaderWith defaultDecodeOptions @@ -251,11 +252,11 @@ => DecodeOptions -- ^ Decoding options -> HeaderParser (Parser a) decodeByNameWith !opts =- PartialH (runParser . (decodeHeaderWith opts `feedChunkH`))+ PartialH (go . (decodeHeaderWith opts `feedChunkH`)) where- runParser (FailH rest msg) = FailH rest msg- runParser (PartialH k) = PartialH $ \ s -> runParser (k s)- runParser (DoneH hdr rest) =+ go (FailH rest msg) = FailH rest msg+ go (PartialH k) = PartialH $ \ s -> go (k s)+ go (DoneH hdr rest) = DoneH hdr (decodeWithP (parseNamedRecord . toNamedRecord hdr) opts rest) -- Copied from Data.Csv.Parser@@ -293,7 +294,7 @@ | otherwise = convert r : acc parser = A.parse (record (decDelimiter opts) <* (endOfLine <|> endOfInput))- convert = parseEither . p+ convert = runParser . p {-# INLINE decodeWithP #-} blankLine :: V.Vector B.ByteString -> Bool
Data/Csv/Parser.hs view
@@ -6,7 +6,7 @@ -- * Empty lines are ignored. -- -- * Non-escaped fields may contain any characters except--- double-quotes, commas, carriage returns, and newlines+-- double-quotes, commas, carriage returns, and newlines. -- -- * Escaped fields may contain any characters (but double-quotes -- need to be escaped).@@ -45,10 +45,18 @@ -- | Options that controls how data is decoded. These options can be -- used to e.g. decode tab-separated data instead of comma-separated -- data.+--+-- To avoid having your program stop compiling when new fields are+-- added to 'DecodeOptions', create option records by overriding+-- values in 'defaultDecodeOptions'. Example:+--+-- > myOptions = defaultDecodeOptions {+-- > decDelimiter = fromIntegral (ord '\t')+-- > } data DecodeOptions = DecodeOptions { -- | Field delimiter. decDelimiter :: {-# UNPACK #-} !Word8- }+ } deriving (Eq, Show) -- | Decoding options for parsing CSV files. defaultDecodeOptions :: DecodeOptions
Data/Csv/Streaming.hs view
@@ -5,9 +5,24 @@ -- space. The API also allows you to ignore type conversion errors on -- a per-record basis. module Data.Csv.Streaming- ( Records(..)+ (+ -- * Usage example+ -- $example++ -- * Stream representation+ -- $stream-representation+ Records(..)++ -- * Decoding records+ -- $typeconversion++ -- ** Index-based record conversion+ -- $indexbased , decode , decodeWith++ -- ** Name-based record conversion+ -- $namebased , decodeByName , decodeByNameWith ) where@@ -27,6 +42,38 @@ import Data.Csv.Parser import Data.Csv.Types +-- $example+--+-- A short usage example:+--+-- > for_ (decode False "John,27\r\nJane,28\r\n") $ \ (name, age :: Int) ->+-- > putStrLn $ name ++ " is " ++ show age ++ " years old"+--+-- N.B. The 'Foldable' instance, which is used above, skips records+-- that failed to convert. If you don't want this behavior, work+-- directly with the 'Cons' and 'Nil' constructors.++-- $stream-representation+--+-- A stream of records is represented as a (lazy) list that may+-- contain errors.++-- $typeconversion+--+-- Just like in the case of non-streaming decoding, there are two ways+-- to convert CSV records to and from and user-defined data types:+-- index-based conversion and name-based conversion.++-- $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.+ -- | A stream of parsed records. If type conversion failed for the -- record, the error is returned as @'Left' errMsg@. data Records a@@ -39,6 +86,7 @@ | Nil (Maybe String) BL.ByteString deriving (Eq, Functor, Show) +-- | Skips records that failed to convert. instance Foldable Records where foldr = foldrRecords #if MIN_VERSION_base(4,6,0)
cassava.cabal view
@@ -1,5 +1,5 @@ Name: cassava-Version: 0.2.0.0+Version: 0.2.1.0 Synopsis: A CSV parsing and encoding library Description: A CSV parsing and encoding library optimized for ease of use and high
+ examples/StreamingIndexBasedDecode.hs view
@@ -0,0 +1,13 @@+{-# LANGUAGE ScopedTypeVariables #-}++import qualified Data.ByteString.Lazy as BL+import Data.Csv.Streaming+import Data.Foldable (for_)++main :: IO ()+main = do+ csvData <- BL.readFile "salaries.csv"+ -- N.B. The Foldable instance skips records that failed to+ -- convert.+ for_ (decode False csvData) $ \ (name, salary :: Int) ->+ putStrLn $ name ++ " earns " ++ show salary ++ " dollars"