csv-enumerator 0.8.2 → 0.9.0
raw patch · 3 files changed
+97/−47 lines, 3 filesPVP ok
version bump matches the API change (PVP)
API changes (from Hackage documentation)
+ Data.CSV.Enumerator: mapIntoHandle :: CSVeable r => CSVSettings -> Bool -> Handle -> (r -> IO [r]) -> Iteratee ByteString IO Int
Files
- README.markdown +4/−1
- csv-enumerator.cabal +1/−1
- src/Data/CSV/Enumerator.hs +92/−45
README.markdown view
@@ -81,11 +81,14 @@ ### TODO - Next Steps +* Refactor all operations to use iterCSV as the basic building block --+ in progress.+* The CSVeable typeclass can be refactored to have a more minimal definition.+* Get mapCSVFiles out of the typeclass if possible. * Need to think about specializing an Exception type for the library and properly notifying the user when parsing-related problems occur. * Some operations can be further broken down to their atoms, increasing the flexibility of the library.-* The CSVeable typeclass can be refactored to have a more minimal definition. * Operating on Text in addition to ByteString would be phenomenal. * A test-suite needs to be added. * Some benchmarking would be nice.
csv-enumerator.cabal view
@@ -1,5 +1,5 @@ Name: csv-enumerator-Version: 0.8.2+Version: 0.9.0 Synopsis: A flexible, fast, enumerator-based CSV parser library for Haskell. Homepage: http://github.com/ozataman/csv-enumerator License: BSD3
src/Data/CSV/Enumerator.hs view
@@ -10,7 +10,6 @@ , MapRow , CSVeable(..)- , ParsedRow(..) -- * CSV Setttings@@ -18,16 +17,23 @@ , defCSVSettings -- * Reading / Writing CSV Files+ -- | These are some simple file-related operations for basic use cases. , readCSVFile , writeCSVFile , appendCSVFile - -- * Folding Over CSV Files - -- | These enumerators generalize the map* family of functions with a running accumulator.+ -- * Generic Folds Over CSV Files+ -- | These operations enable you to do whatever you want with CSV files;+ -- including interleaved IO, etc.+ , foldCSVFile , CSVAction , funToIter , funToIterIO + -- * Mapping Over CSV Files+ , mapCSVFile+ , mapIntoHandle+ -- * Primitive Iteratees , collectRows , outputRowIter@@ -44,7 +50,7 @@ import Control.Applicative hiding (many) import Control.Exception (bracket, SomeException)-import Control.Monad (mzero, mplus, foldM, when)+import Control.Monad (mzero, mplus, foldM, when, liftM) import Control.Monad.IO.Class (liftIO, MonadIO) import qualified Data.ByteString as B import qualified Data.ByteString.Char8 as B8@@ -57,7 +63,6 @@ import Data.Attoparsec as P hiding (take) import qualified Data.Attoparsec.Char8 as C8--- import Data.Attoparsec.Enum import Data.Attoparsec.Enumerator import qualified Data.Enumerator as E import Data.Enumerator (($$), yield, continue)@@ -81,40 +86,14 @@ -> a -> E.Iteratee B.ByteString IO a - -- | Iteratee to push rows into a given file- fileSink :: CSVSettings- -> FilePath- -> (Maybe Handle, Int)- -> ParsedRow r- -> E.Iteratee B.ByteString IO (Maybe Handle, Int) - -- | Open & fold over the CSV file. Processing starts on row 2 for MapRow- -- instance to use first row as column headers.- foldCSVFile :: FilePath -- ^ File to open as a CSV file- -> CSVSettings -- ^ CSV settings to use on the input file- -> CSVAction r a -- ^ Fold action- -> a -- ^ Initial accumulator- -> IO (Either SomeException a) -- ^ Error or the resulting accumulator-- -- | Take a CSV file, apply function to each of its rows and save the- -- resulting rows into a new file.- --- -- Each row is simply a list of fields.- mapCSVFile :: FilePath -- ^ Input file- -> CSVSettings -- ^ CSV Settings- -> (r -> [r]) -- ^ A function to map a row onto rows- -> FilePath -- ^ Output file- -> IO (Either SomeException Int) -- ^ Number of rows processed - mapCSVFile fi s f fo = do- res <- foldCSVFile fi s iter (Nothing, 0)- return $ snd `fmap` res- where- iter !acc (ParsedRow (Just !r)) = foldM chain acc (f r) - iter !acc x = fileSink s fo acc x-- chain !acc !r = singleSink r acc-- singleSink !x !acc = fileSink s fo acc (ParsedRow (Just x))+ -- | Iteratee to push rows into a given file+ fileSink + :: CSVSettings+ -> FilePath+ -> (Maybe Handle, Int)+ -> ParsedRow r+ -> E.Iteratee B.ByteString IO (Maybe Handle, Int) ----------------------------------------------------------------------------@@ -152,11 +131,6 @@ comboIter acc' = procRow acc' >>= loop - foldCSVFile fp csvs f acc = E.run iter - where- iter = enumFile fp $$ iterCSV csvs f acc-- fileSink csvs fo = iter where iter :: (Maybe Handle, Int) @@ -237,9 +211,7 @@ toMapCSV !headers !fs = yield (fs >>= (Just . M.fromList . zip headers)) (E.Chunks []) - foldCSVFile fp csvs f !acc = E.run (enumFile fp $$ iterCSV csvs f acc) - fileSink s fo = mapIter where mapIter :: (Maybe Handle, Int) @@ -299,6 +271,42 @@ ------------------------------------------------------------------------------+-- | Open & fold over the CSV file. +--+-- Processing starts on row 2 for MapRow instance to use first row as column+-- headers.+foldCSVFile + :: (CSVeable r)+ => FilePath -- ^ File to open as a CSV file+ -> CSVSettings -- ^ CSV settings to use on the input file+ -> CSVAction r a -- ^ Fold action+ -> a -- ^ Initial accumulator+ -> IO (Either SomeException a) -- ^ Error or the resulting accumulator+foldCSVFile fp csvs f acc = E.run (enumFile fp $$ iterCSV csvs f acc)+++------------------------------------------------------------------------------+-- | Take a CSV file, apply function to each of its rows and save the+-- resulting rows into a new file.+--+-- Each row is simply a list of fields.+mapCSVFile + :: (CSVeable r)+ => FilePath -- ^ Input file+ -> CSVSettings -- ^ CSV Settings+ -> (r -> [r]) -- ^ A function to map a row onto rows+ -> FilePath -- ^ Output file+ -> IO (Either SomeException Int) -- ^ Number of rows processed +mapCSVFile fi s f fo = do+ res <- foldCSVFile fi s iter (Nothing, 0)+ return $ snd `fmap` res+ where+ iter !acc (ParsedRow (Just !r)) = foldM chain acc (f r) + iter !acc x = fileSink s fo acc x+ chain !acc !r = fileSink s fo acc (ParsedRow (Just r))+++------------------------------------------------------------------------------ readCSVFile :: (CSVeable r) => CSVSettings -- ^ CSV settings -> FilePath -- ^ FilePath -> IO (Either SomeException [r]) -- ^ Collected data@@ -429,6 +437,45 @@ where iterf !acc EOF = yield (f acc EOF) E.EOF iterf !acc r = yield (f acc r) (E.Chunks [])++++------------------------------------------------------------------------------+-- | Create an iteratee that can map over a CSV stream and output results to+-- a handle in an interleaved fashion. +--+-- Example use: Let's map over a CSV file coming in through 'stdin' and push+-- results to 'stdout'.+--+-- > f r = return [r] -- a function that just returns the given row+--+-- > E.run (E.enumHandle 4096 stdin $$ mapIntoHandle defCSVSettings True stdout f)+--+-- This nicely allows us to do things like (assuming you have pv installed):+--+-- > pv inputFile.csv | myApp > output.CSV+--+-- And monitor the ongoing progress of processing.+mapIntoHandle + :: (CSVeable r)+ => CSVSettings -- ^ 'CSVSettings'+ -> Bool -- ^ Whether to write headers+ -> Handle -- ^ Handle to stream results+ -> (r -> IO [r]) -- ^ Map function+ -> E.Iteratee ByteString IO Int -- ^ Resulting Iteratee+mapIntoHandle csvs outh h f = do+ snd `liftM` iterCSV csvs (funToIterIO f') (False,0)+ where+ f' acc EOF = return acc+ f' acc (ParsedRow Nothing) = return acc+ f' (False, _) r'@(ParsedRow (Just r)) = do+ rs <- f r+ when outh $ writeHeaders csvs h rs+ f' (True, 0) r'+ f' (True, !i) (ParsedRow (Just r)) = do+ rs <- f r+ outputRows csvs h rs+ return (True, i+1) ------------------------------------------------------------------------------