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 +30/−0
- README.markdown +75/−0
- Setup.hs +2/−0
- csv-conduit.cabal +86/−0
- src/Data/CSV/Conduit.hs +231/−0
- src/Data/CSV/Conduit/Parser/ByteString.hs +100/−0
- src/Data/CSV/Conduit/Parser/Text.hs +99/−0
- src/Data/CSV/Conduit/Types.hs +60/−0
- test/Test.hs +57/−0
- test/csv-enumerator-test.cabal +69/−0
- test/test.csv +4/−0
+ 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`