packages feed

csv-conduit (empty) → 0.1

raw patch · 11 files changed

+813/−0 lines, 11 filesdep +attoparsecdep +attoparsec-conduitdep +basesetup-changed

Dependencies added: attoparsec, attoparsec-conduit, base, bytestring, conduit, containers, directory, monad-control, safe, text, transformers, unix-compat

Files

+ LICENSE view
@@ -0,0 +1,30 @@+Copyright (c)2010, Ozgun Ataman++All rights reserved.++Redistribution and use in source and binary forms, with or without+modification, are permitted provided that the following conditions are met:++    * Redistributions of source code must retain the above copyright+      notice, this list of conditions and the following disclaimer.++    * Redistributions in binary form must reproduce the above+      copyright notice, this list of conditions and the following+      disclaimer in the documentation and/or other materials provided+      with the distribution.++    * Neither the name of Ozgun Ataman nor the names of other+      contributors may be used to endorse or promote products derived+      from this software without specific prior written permission.++THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ README.markdown view
@@ -0,0 +1,75 @@+# README++## CSV Files and Haskell++CSV files are the de-facto standard in many cases of data transfer,+particularly when dealing with enterprise application or disparate database+systems.++While there are a number of csv libraries in Haskell, at the time of this+project's start in 2010, there wasn't one that provided all of the following:++* Full flexibility in quote characters, separators, input/output+* Constant space operation+* Robust parsing and error resiliency+* Fast operation+* Convenient interface that supports a variety of use cases++This library is an attempt to close these gaps.+++## This package++csv-conduit is a conduits based CSV parsing library that is easy to+use, flexible and fast. Furthermore, it provides ways to use+constant-space during operation, which is absolutely critical in many+real world use cases.+++### Introduction++* The CSVeable typeclass implements the key operations.+* CSVeable is parameterized on both a stream type and a target CSV row type.+* There are 2 basic row types and they implement *exactly* the same operations,+  so you can chose the right one for the job at hand:+  - type MapRow t = Map t t+  - type Row t = [t]+* You basically use the Conduits defined in this library to do the+  parsing from a CSV stream and rendering back into a CSV stream.+* Use the full flexibility and modularity of conduits for sources and sinks.++### Speed++While fast operation is of concern, I have so far cared more about correct+operation and a flexible API. Please let me know if you notice any performance+regressions or optimization opportunities.+++### Usage Examples++#### Example 1: Basic Operation ++    {-# LANGUAGE OverloadedStrings #-}++    import Data.Conduit.Text+    import Data.Conduit.Binary+    import Data.Conduit+    import Data.CSV.Conduit+    +    -- Let's simply stream from a file, parse the CSV, reserialize it+    -- and push back into another file.+    test :: IO ()+    test = runResourceT $ +      sourceFile "test/BigFile.csv" $= +      decode utf8 $=+      (intoCSV defCSVSettings +        :: forall m. MonadResource m => Conduit Text m (MapRow Text)) $= +      fromCSV defCSVSettings $=+      encode utf8 $$+      sinkFile "test/BigFileOut.csv"+++and we are done. +++
+ Setup.hs view
@@ -0,0 +1,2 @@+import Distribution.Simple+main = defaultMain
+ csv-conduit.cabal view
@@ -0,0 +1,86 @@+Name:                csv-conduit+Version:             0.1+Synopsis:            A flexible, fast, conduit-based CSV parser library for Haskell.  +Homepage:            http://github.com/ozataman/csv-conduit+License:             BSD3+License-file:        LICENSE+Author:              Ozgun Ataman +Maintainer:          Ozgun Ataman <ozataman@gmail.com>+Category:            Data+Build-type:          Simple+Cabal-version:       >=1.6+Description:++  CSV files are the de-facto standard in many situations involving data transfer,+  particularly when dealing with enterprise application or disparate database+  systems.++  .++  While there are a number of CSV libraries in Haskell, at the time of this+  project's start in 2010, there wasn't one that provided all of the following:++  .++  * Full flexibility in quote characters, separators, input/output+  .+  * Constant space operation+  .+  * Robust parsing, correctness and error resiliency+  .+  * Convenient interface that supports a variety of use cases+  .+  * Fast operation+  .++  This library is an attempt to close these gaps. Please note that+  this library started its life based on the enumerator package and+  has recently been ported to work with conduits instead. In the+  process, it has been greatly simplified thanks to the modular nature+  of the conduits library.++  . +  +  Following the port to conduits, the library has also gained the+  ability to parameterize on the stream type and work both with+  ByteString and Text.++  .++  For more documentation and examples, check out the README at:+  .++  <http://github.com/ozataman/csv-conduit>+  .+  ++extra-source-files:+  README.markdown+  test/csv-enumerator-test.cabal+  test/test.csv+  test/Test.hs++Library+  hs-source-dirs: src+  Exposed-modules:     +      Data.CSV.Conduit+      Data.CSV.Conduit.Parser.ByteString+      Data.CSV.Conduit.Parser.Text+  Other-modules:+      Data.CSV.Conduit.Types+  build-depends:+      attoparsec >= 0.10+    , attoparsec-conduit+    , conduit == 0.4.*+    , base >= 4 && < 5+    , containers >= 0.3+    , directory+    , bytestring+    , text+    , transformers >= 0.2+    , safe+    , unix-compat >= 0.2.1.1+    , monad-control+  extensions:+    ScopedTypeVariables+  ghc-options: -funbox-strict-fields
+ src/Data/CSV/Conduit.hs view
@@ -0,0 +1,231 @@+{-# LANGUAGE OverloadedStrings, BangPatterns #-}+{-# LANGUAGE PackageImports #-}+{-# LANGUAGE FlexibleContexts #-}+{-# LANGUAGE RankNTypes #-}+{-# LANGUAGE MultiParamTypeClasses #-}+{-# LANGUAGE TypeSynonymInstances, FlexibleInstances #-}++module Data.CSV.Conduit+    ( CSVeable (..)+    , CSVSettings (..)+    , defCSVSettings+    , MapRow+    , Row+    -- * Convenience Functions+    , readCSVFile+    , mapCSVFile+    ) where++-------------------------------------------------------------------------------+import           Control.Applicative        hiding (many)+import           Control.Exception          (bracket, SomeException)+import           Control.Monad              (mzero, mplus, foldM, when, liftM)+import           Control.Monad.IO.Class     (liftIO, MonadIO)+import           Control.Monad.Trans.Control+import           Data.Attoparsec            as P hiding (take)+import qualified Data.Attoparsec.Char8      as C8+import qualified Data.ByteString            as B+import           Data.ByteString.Char8      (ByteString)+import qualified Data.ByteString.Char8      as B8+import           Data.ByteString.Internal   (c2w)+import           Data.Conduit as C+import           Data.Conduit.Attoparsec+import           Data.Conduit.Binary (sourceFile, sinkFile)+import qualified Data.Conduit.List as C+import           Data.Conduit.Text+import qualified Data.Map                   as M+import           Data.String+import           Data.Text (Text)+import qualified Data.Text as T+import qualified Data.Text.Encoding as T+import           Data.Word                  (Word8)+import           Safe                       (headMay)+import           System.Directory+import           System.PosixCompat.Files   (getFileStatus, fileSize)+-------------------------------------------------------------------------------+import qualified Data.CSV.Conduit.Parser.ByteString as BSP+import qualified Data.CSV.Conduit.Parser.Text as TP+import           Data.CSV.Conduit.Types+-------------------------------------------------------------------------------+++-------------------------------------------------------------------------------+-- | Represents types 'r' that can be converted from an underlying+-- stream of type 's'.+--+-- Example processing using MapRow Text isntance:+--+-- @+-- test :: IO ()+-- test = runResourceT $ +--   sourceFile "test/BigFile.csv" $= +--   decode utf8 $=+--   intoCSV defCSVSettings $=+--   myMapRowProcessingConduit $=+--   fromCSV defCSVSettings $=+--   encode utf8 $$+--   sinkFile "test/BigFileOut.csv"+-- @+class CSVeable s r where++  -----------------------------------------------------------------------------+  -- | Convert a CSV row into strict ByteString equivalent.+  rowToStr :: CSVSettings -> r -> s++  -----------------------------------------------------------------------------+  -- | Turn a stream of 's' into a stream of CSV row type+  intoCSV :: MonadResource m => CSVSettings -> Conduit s m r++  -----------------------------------------------------------------------------+  -- | Turn a stream of CSV row type back into a stream of 's'+  fromCSV :: MonadResource m => CSVSettings -> Conduit r m s+++------------------------------------------------------------------------------+-- | 'Row' instance using 'ByteString'+instance CSVeable ByteString (Row ByteString) where+  rowToStr s !r = +    let +      sep = B.pack [c2w (csvOutputColSep s)] +      wrapField !f = case (csvOutputQuoteChar s) of+        Just !x -> x `B8.cons` escape x f `B8.snoc` x+        otherwise -> f+      escape c str = B8.intercalate (B8.pack [c,c]) $ B8.split c str+    in B.intercalate sep . map wrapField $ r++  intoCSV set = intoCSVRow (BSP.row set)+  fromCSV set = fromCSVRow set+++------------------------------------------------------------------------------+-- | 'Row' instance using 'Text'+instance CSVeable Text (Row Text) where+  rowToStr s !r = +    let +      sep = T.pack [(csvOutputColSep s)] +      wrapField !f = case (csvOutputQuoteChar s) of+        Just !x -> x `T.cons` escape x f `T.snoc` x+        otherwise -> f+      escape c str = T.intercalate (T.pack [c,c]) $ T.split (== c) str+    in T.intercalate sep . map wrapField $ r++  intoCSV set = intoCSVRow (TP.row set)+  fromCSV set = fromCSVRow set+++-------------------------------------------------------------------------------+-- | 'Row' instance using 'Text' based on 'ByteString' stream+instance CSVeable ByteString (Row Text) where+    rowToStr s r = T.encodeUtf8 $ rowToStr s r+    intoCSV set = intoCSV set =$= C.map (map T.decodeUtf8)+    fromCSV set = fromCSV set =$= C.map T.encodeUtf8++++-------------------------------------------------------------------------------+fromCSVRow set = conduitState init push close+  where+    init = ()+    push st r = return $ StateProducing st [rowToStr set r, "\n"]+    close _ = return []+++-------------------------------------------------------------------------------+intoCSVRow p = parser =$= puller+  where+    parser = sequenceSink () seqSink+    seqSink _ = do+      p <- sinkParser p+      return $ Emit () [p]+    puller = do+      inc <- await+      case inc of+        Nothing -> return ()+        Just i ->+          case i of+            Just i' -> yield i' >> puller+            Nothing -> puller++++-------------------------------------------------------------------------------+-- | Generic 'MapRow' instance; any stream type with a 'Row' instance+-- automatically gets a 'MapRow' instance.+instance (CSVeable s (Row s'), Ord s', IsString s) => CSVeable s (MapRow s') where+  rowToStr s r = rowToStr s . M.elems $ r+  intoCSV set = intoCSVMap set+  fromCSV set = fromCSVMap set+++-------------------------------------------------------------------------------+intoCSVMap set = intoCSV set =$= converter+  where+    converter = conduitState Nothing push close +      where+        push Nothing row = +          case row of+            [] -> return $ StateProducing Nothing []+            xs -> return $ StateProducing (Just xs) []+        push st@(Just hs) row = return $ StateProducing st [toMapCSV hs row]+        toMapCSV !headers !fs = M.fromList $ zip headers fs+        close _ = return []+++-------------------------------------------------------------------------------+fromCSVMap set = conduitState False push close+  where+    push False r = return $ StateProducing True +                   [rowToStr set (M.keys r), "\n", rowToStr set (M.elems r), "\n"]+    push True r = return $ StateProducing True+                   [rowToStr set (M.elems r), "\n"]+    close _ = return []++++                          ---------------------------+                          -- Convenience Functions --+                          ---------------------------+++-------------------------------------------------------------------------------+-- | Read the entire contents of a CSV file into memory+readCSVFile set fp = runResourceT $ sourceFile fp $= intoCSV set $$ C.consume+++-------------------------------------------------------------------------------+-- | Map over the rows of a CSV file. Don't be scared by the type+-- signature, this can just run in IO.+mapCSVFile +    :: (MonadIO m, MonadUnsafeIO m, MonadThrow m, +        MonadBaseControl IO m, CSVeable ByteString a, CSVeable ByteString b) +    => CSVSettings +    -- ^ Settings to use both for input and output+    -> (a -> b) +    -- ^ A mapping function+    -> FilePath +    -- ^ Input file+    -> FilePath +    -- ^ Output file+    -> m ()+mapCSVFile set f fi fo = runResourceT $+    sourceFile fi $=+    intoCSV set $=+    C.map f $=+    fromCSV set $$+    sinkFile fo+++                               -----------------+                               -- Simple Test --+                               -----------------+++test :: IO ()+test = runResourceT $ +  sourceFile "test/BigFile.csv" $= +  decode utf8 $=+  (intoCSV defCSVSettings +    :: forall m. MonadResource m => Conduit Text m (MapRow Text)) $= +  fromCSV defCSVSettings $=+  encode utf8 $$+  sinkFile "test/BigFileOut.csv"
+ src/Data/CSV/Conduit/Parser/ByteString.hs view
@@ -0,0 +1,100 @@+{-| ++  This module exports the underlying Attoparsec row parser. This is helpful if+  you want to do some ad-hoc CSV string parsing.++-}++module Data.CSV.Conduit.Parser.ByteString+    ( parseCSV+    , parseRow+    , row+    , csv+    ) where++-------------------------------------------------------------------------------+import           Control.Applicative+import           Control.Monad (mzero, mplus, foldM, when, liftM)+import           Control.Monad.IO.Class (liftIO, MonadIO)+import           Data.Attoparsec as P hiding (take)+import qualified Data.Attoparsec.Char8 as C8+import qualified Data.ByteString as B+import           Data.ByteString.Char8 (ByteString)+import qualified Data.ByteString.Char8 as B8+import           Data.ByteString.Internal (c2w)+import qualified Data.Map as M+import           Data.Word (Word8)+-------------------------------------------------------------------------------+import           Data.CSV.Conduit.Types+++------------------------------------------------------------------------------+-- | Try to parse given string as CSV+parseCSV :: CSVSettings -> ByteString -> Either String [Row ByteString]+parseCSV s = parseOnly $ csv s+++------------------------------------------------------------------------------+-- | Try to parse given string as 'Row ByteString'+parseRow :: CSVSettings -> ByteString -> Either String (Maybe (Row ByteString))+parseRow s = parseOnly $ row s+++------------------------------------------------------------------------------+-- | Parse CSV+csv :: CSVSettings -> Parser [Row ByteString]+csv s = do+  r <- row s+  end <- atEnd+  case end of+    True -> case r of+      Just x -> return [x]+      Nothing -> return []+    False -> do+      rest <- csv s+      return $ case r of+        Just x -> x : rest+        Nothing -> rest+++------------------------------------------------------------------------------+-- | Parse a CSV row+row :: CSVSettings -> Parser (Maybe (Row ByteString))+row csvs = csvrow csvs <|> badrow+++badrow :: Parser (Maybe (Row ByteString))+badrow = P.takeWhile (not . C8.isEndOfLine) *> +         (C8.endOfLine <|> C8.endOfInput) *> return Nothing++csvrow :: CSVSettings -> Parser (Maybe (Row ByteString))+csvrow c = +  let rowbody = (quotedField' <|> (field c)) `sepBy` C8.char (csvSep c)+      properrow = rowbody <* (C8.endOfLine <|> P.endOfInput)+      quotedField' = case csvQuoteChar c of+          Nothing -> mzero+          Just q' -> try (quotedField q')+  in do+    res <- properrow+    return $ Just res++field :: CSVSettings -> Parser ByteString+field s = P.takeWhile (isFieldChar s) ++isFieldChar s = notInClass xs'+  where xs = csvSep s : "\n\r"+        xs' = case csvQuoteChar s of +          Nothing -> xs+          Just x -> x : xs++quotedField :: Char -> Parser ByteString+quotedField c = +  let quoted = string dbl *> return c+      dbl = B8.pack [c,c]+  in do+  C8.char c +  f <- many (C8.notChar c <|> quoted)+  C8.char c +  return $ B8.pack f++
+ src/Data/CSV/Conduit/Parser/Text.hs view
@@ -0,0 +1,99 @@+{-| ++  This module exports the underlying Attoparsec row parser. This is helpful if+  you want to do some ad-hoc CSV string parsing.++-}++module Data.CSV.Conduit.Parser.Text+    ( parseCSV+    , parseRow+    , row+    , csv+    ) where++-------------------------------------------------------------------------------+import           Control.Applicative+import           Control.Monad (mzero, mplus, foldM, when, liftM)+import           Control.Monad.IO.Class (liftIO, MonadIO)+import           Data.Attoparsec.Text as P hiding (take)+import qualified Data.Attoparsec.Text as T+import qualified Data.Map as M+import           Data.Text (Text)+import qualified Data.Text as T+import           Data.Word (Word8)+-------------------------------------------------------------------------------+import           Data.CSV.Conduit.Types+-------------------------------------------------------------------------------+++------------------------------------------------------------------------------+-- | Try to parse given string as CSV+parseCSV :: CSVSettings -> Text -> Either String [Row Text]+parseCSV s = parseOnly $ csv s+++------------------------------------------------------------------------------+-- | Try to parse given string as 'Row Text'+parseRow :: CSVSettings -> Text -> Either String (Maybe (Row Text))+parseRow s = parseOnly $ row s+++------------------------------------------------------------------------------+-- | Parse CSV+csv :: CSVSettings -> Parser [Row Text]+csv s = do+  r <- row s+  end <- atEnd+  case end of+    True -> case r of+      Just x -> return [x]+      Nothing -> return []+    False -> do+      rest <- csv s+      return $ case r of+        Just x -> x : rest+        Nothing -> rest+++------------------------------------------------------------------------------+-- | Parse a CSV row+row :: CSVSettings -> Parser (Maybe (Row Text))+row csvs = csvrow csvs <|> badrow+++badrow :: Parser (Maybe (Row Text))+badrow = P.takeWhile (not . T.isEndOfLine) *> +         (T.endOfLine <|> T.endOfInput) *> return Nothing++csvrow :: CSVSettings -> Parser (Maybe (Row Text))+csvrow c = +  let rowbody = (quotedField' <|> (field c)) `sepBy` T.char (csvSep c)+      properrow = rowbody <* (T.endOfLine <|> P.endOfInput)+      quotedField' = case csvQuoteChar c of+          Nothing -> mzero+          Just q' -> try (quotedField q')+  in do+    res <- properrow+    return $ Just res++field :: CSVSettings -> Parser Text+field s = P.takeWhile (isFieldChar s) ++isFieldChar s = notInClass xs'+  where xs = csvSep s : "\n\r"+        xs' = case csvQuoteChar s of +          Nothing -> xs+          Just x -> x : xs++quotedField :: Char -> Parser Text+quotedField c = +  let quoted = string dbl *> return c+      dbl = T.pack [c,c]+  in do+  T.char c +  f <- many (T.notChar c <|> quoted)+  T.char c +  return $ T.pack f++
+ src/Data/CSV/Conduit/Types.hs view
@@ -0,0 +1,60 @@+{-# LANGUAGE OverloadedStrings, BangPatterns #-}+{-# LANGUAGE PackageImports #-}+{-# LANGUAGE TypeSynonymInstances #-}++module Data.CSV.Conduit.Types where++-------------------------------------------------------------------------------+import qualified Data.ByteString as B+import Data.Text (Text)+import qualified Data.Map as M+-------------------------------------------------------------------------------+++-------------------------------------------------------------------------------+-- | Settings for a CSV file. This library is intended to be flexible+-- and offer a way to process the majority of text data files out+-- there.+data CSVSettings = CSVS+  { +    -- | Separator character to be used in between fields+    csvSep :: !Char          ++    -- | Quote character that may sometimes be present around fields.+    -- If 'Nothing' is given, the library will never expect quotation+    -- even if it is present.+  , csvQuoteChar :: !(Maybe Char)+  +    -- | Quote character that should be used in the output.+  , csvOutputQuoteChar :: !(Maybe Char)+  +    -- | Field separator that should be used in the output.+  , csvOutputColSep :: !Char+  } deriving (Read, Show, Eq)++++-------------------------------------------------------------------------------+-- | Default settings for a CSV file. +--+-- > csvSep = ','+-- > csvQuoteChar = Just '"'+-- > csvOutputQuoteChar = Just '"'+-- > csvOutputColSep = ','+--+defCSVSettings :: CSVSettings+defCSVSettings = CSVS+  { csvSep = ','+  , csvQuoteChar = Just '"'+  , csvOutputQuoteChar = Just '"'+  , csvOutputColSep = ','+  } +++-------------------------------------------------------------------------------+-- | A 'Row' is just a list of fields+type Row a = [a]++-------------------------------------------------------------------------------+-- | A 'MapRow' is a dictionary based on 'Data.Map'+type MapRow a = M.Map a a
+ test/Test.hs view
@@ -0,0 +1,57 @@++module Main where++import qualified Data.ByteString.Char8 as B+import Data.Map ((!))+import System.Directory++import Test.Framework (defaultMain, testGroup)+import Test.Framework.Providers.HUnit+import Test.Framework.Providers.QuickCheck2 (testProperty)++import Test.QuickCheck+import Test.HUnit+++import Data.CSV.Enumerator+++main = defaultMain tests++tests = [ testGroup "Basic Ops" baseTests ]+++baseTests = [+    testCase "mapping with id works" test_identityMap+  , testCase "simple parsing works" test_simpleParse+  ]+++test_identityMap = do+  Right r <- mapCSVFile "test.csv" csvSettings f "testOut.csv"+  3 @=? r+  f1 <- readFile "test.csv"+  f2 <- readFile "testOut.csv"+  f1 @=? f2+  removeFile "testOut.csv"+  where +    f :: MapRow -> [MapRow]+    f = return+++test_simpleParse = do+  Right (d :: [MapRow]) <- readCSVFile csvSettings "test.csv" +  mapM_ assertRow d+  where+    assertRow r = v3 @=? (v1 + v2)+      where v1 = readBS $ r ! "Col2"+            v2 = readBS $ r ! "Col3" +            v3 = readBS $ r ! "Sum" +++csvSettings = +  defCSVSettings { csvQuoteChar = Just '`'+                 , csvOutputQuoteChar = Just '`' }+++readBS = read . B.unpack
+ test/csv-enumerator-test.cabal view
@@ -0,0 +1,69 @@+Name:                csv-enumerator-test+Version:             0.8.2+Synopsis:            A flexible, fast, enumerator-based CSV parser library for Haskell.  +Homepage:            http://github.com/ozataman/csv-enumerator+License:             BSD3+License-file:        LICENSE+Author:              Ozgun Ataman +Maintainer:          Ozgun Ataman <ozataman@gmail.com>+Category:            Data+Build-type:          Simple+Cabal-version:       >=1.2++extra-source-files:+  README.markdown++Executable test+  main-is: Test.hs+  hs-source-dirs: ./ ../src+  Other-modules:+      Data.CSV.Enumerator.Types+      Data.CSV.Enumerator+  build-depends:+      attoparsec >= 0.8 && < 0.10+    , attoparsec-enumerator >= 0.2.0.3+    , base >= 4 && < 5+    , containers >= 0.3+    , directory+    , bytestring+    , enumerator >= 0.4.5 && < 0.5+    , transformers >= 0.2+    , safe+    , unix-compat >= 0.2.1.1+    , test-framework+    , test-framework-quickcheck2+    , test-framework-hunit+    , QuickCheck >= 2+    , HUnit >= 1.2+  extensions:+    ScopedTypeVariables+    OverloadedStrings+++Executable bench+  main-is: Bench.hs+  hs-source-dirs: ./ ../src+  Other-modules:+      Data.CSV.Enumerator.Types+      Data.CSV.Enumerator+  build-depends:+      attoparsec >= 0.8 && < 0.10+    , attoparsec-enumerator >= 0.2.0.3+    , base >= 4 && < 5+    , containers >= 0.3+    , directory+    , bytestring+    , enumerator >= 0.4.5 && < 0.5+    , transformers >= 0.2+    , safe+    , unix-compat >= 0.2.1.1+    , test-framework+    , test-framework-quickcheck2+    , test-framework-hunit+    , QuickCheck >= 2+    , HUnit >= 1.2+  extensions:+    ScopedTypeVariables+    OverloadedStrings+  ghc-options: -rtsopts +  ghc-prof-options: -rtsopts -caf-all -auto-all    
+ test/test.csv view
@@ -0,0 +1,4 @@+`Col1`,`Col2`,`Col3`,`Sum`+`A`,`2`,`3`,`5`+`B`,`3`,`4`,`7`+`Field using the quote char ``this is the in-quoted value```,`4`,`5`,`9`