packages feed

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 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"