repa-flow-4.2.3.1: Data/Repa/Flow/Auto/IO.hs
-- | Read and write files.
--
-- The functions in this module are wrappers for the ones in
-- "Data.Repa.Flow.Default.SizedIO" that use a default chunk size of
-- 64kBytes and just call `error` if the source file appears corruped.
--
module Data.Repa.Flow.Auto.IO
( defaultChunkSize
-- * Buckets
, module Data.Repa.Flow.IO.Bucket
-- * Sourcing
, sourceBytes
, sourceChars
, sourceLines
, sourceRecords
, sourceTSV
, sourceCSV
, sourceFormatLn
-- * Sinking
, sinkBytes
, sinkLines
, sinkChars
, sinkFormatLn
)
where
import Data.Repa.Flow.Auto
import Data.Repa.Flow.IO.Bucket
import Data.Repa.Array.Material as A
import Data.Repa.Array.Auto.Format as A
import Data.Repa.Array.Generic as A
import System.IO
import Data.Word
import qualified Data.Repa.Flow.Generic as G
import qualified Data.Repa.Flow.Generic.IO as G
import qualified Data.Repa.Flow.Auto.SizedIO as F
import Prelude as P
#include "repa-flow.h"
-- | The default chunk size of 64kBytes.
defaultChunkSize :: Integer
defaultChunkSize = 64 * 1024
---------------------------------------------------------------------------------------------------
-- | Read data from some files, using the given chunk length.
sourceBytes :: Array B Bucket -> IO (Sources Word8)
sourceBytes = F.sourceBytes defaultChunkSize
{-# INLINE sourceBytes #-}
-- | Read 8-bit ASCII characters from some files, using the given chunk length.
sourceChars :: Array B Bucket -> IO (Sources Char)
sourceChars = F.sourceChars defaultChunkSize
{-# INLINE sourceChars #-}
-- | Read complete records of data form a file, into chunks of the given length.
-- We read as many complete records as will fit into each chunk.
--
-- The records are separated by a special terminating character, which the
-- given predicate detects. After reading a chunk of data we seek the file to
-- just after the last complete record that was read, so we can continue to
-- read more complete records next time.
--
-- If we cannot fit at least one complete record in the chunk then perform
-- the given failure action. Limiting the chunk length guards against the
-- case where a large input file is malformed, as we won't try to read the
-- whole file into memory.
--
--
-- * Data is read into foreign memory without copying it through the GHC heap.
-- * The provided file handle must support seeking, else you'll get an
-- exception.
-- * Each file is closed the first time the consumer tries to pull a
-- record from the associated stream when no more are available.
--
sourceRecords
:: (Word8 -> Bool) -- ^ Detect the end of a record.
-> Array B Bucket -- ^ Input Buckets.
-> IO (Sources (Array A Word8))
sourceRecords pSep
= F.sourceRecords defaultChunkSize pSep
$ error $ "Record exceeds chunk size of "
++ show defaultChunkSize ++ "bytes."
{-# INLINE sourceRecords #-}
-- | Read complete lines of data from a text file, using the given chunk length.
-- We read as many complete lines as will fit into each chunk.
--
-- * The trailing new-line characters are discarded.
-- * Data is read into foreign memory without copying it through the GHC heap.
-- * The provided file handle must support seeking, else you'll get an
-- exception.
-- * Each file is closed the first time the consumer tries to pull a line
-- from the associated stream when no more are available.
--
sourceLines
:: Array B Bucket -> IO (Sources (Array A Char))
sourceLines
= F.sourceLines defaultChunkSize
$ error $ "Line exceeds chunk size of "
++ show defaultChunkSize ++ "bytes."
{-# INLINE sourceLines #-}
-- | Read a file containing Comma-Separated-Values.
sourceCSV :: Array B Bucket
-> IO (Sources (Array A (Array A Char)))
sourceCSV
= F.sourceCSV defaultChunkSize
$ error $ "Line exceeds chunk size of "
++ show defaultChunkSize ++ "bytes."
{-# INLINE sourceCSV #-}
-- | Read a file containing Tab-Separated-Values.
sourceTSV :: Array B Bucket
-> IO (Sources (Array A (Array A Char)))
sourceTSV
= F.sourceTSV defaultChunkSize
$ error $ "Line exceeds chunk size of "
++ show defaultChunkSize ++ "bytes."
{-# INLINE sourceTSV #-}
-- | Read the lines of a text file,
-- converting each line to values with the given format.
sourceFormatLn
:: ( Unpackable format
, Target A (Value format))
=> Integer -- ^ Chunk length.
-> IO () -- ^ Action when a line is too long.
-> IO (Array A Word8 -> IO ()) -- ^ Action if we can't convert a value.
-> format -- ^ Format of each line.
-> Array B Bucket -- ^ Source buckets.
-> IO (Sources (Value format))
sourceFormatLn = G.sourceLinesFormat
{-# INLINE sourceFormatLn #-}
---------------------------------------------------------------------------------------------------
-- | Write 8-bit bytes to some files.
sinkBytes :: Array B Bucket -> IO (Sinks Word8)
sinkBytes bs
= G.map_o (A.convert F)
=<< G.sinkBytes bs
{-# INLINE sinkBytes #-}
-- | Write 8-bit ASCII characters to some files.
sinkChars :: Array B Bucket -> IO (Sinks Char)
sinkChars = G.sinkChars
{-# INLINE sinkChars #-}
-- | Write vectors of text lines to the given files handles.
sinkLines :: Array B Bucket -> IO (Sinks (Array A Char))
sinkLines = G.sinkLines A A
{-# INLINE sinkLines #-}
-- | Create sinks that convert values to some format and writes
-- them to buckets.
--
-- @
-- > import Data.Repa.Flow as F
-- > import Data.Repa.Convert.Format as F
-- > :{
-- do let format = FixString ASCII 10 :*: Float64be :*: Int16be
-- let vals = listFormat format
-- [ \"red\" :*: 5.3 :*: 100
-- , \"green\" :*: 2.8 :*: 93
-- , \"blue\" :*: 0.99 :*: 42 ]
--
-- ss <- F.fromList 1 vals
-- out <- toFiles' [\"colors.bin\"]
-- $ sinkFormatLn format (error \"convert failed\")
-- drainS ss out
-- :}
-- @
--
sinkFormatLn
:: (Packable format, Bulk A (Value format))
=> format -- ^ Binary format for each value.
-> IO () -- ^ Action when a value cannot be serialized.
-> Array B Bucket -- ^ Output buckets.
-> IO (Sinks (Value format))
sinkFormatLn format aFail bs
= return $ G.Sinks (A.length bs)
push_sinkFormatLn eject_sinkFormatLn
where
push_sinkFormatLn i !chunk
= case A.packsFormatLn format chunk of
Nothing -> aFail
Just buf -> bPutArray (bs `index` i) (A.convert F buf)
{-# INLINE push_sinkFormatLn #-}
eject_sinkFormatLn i
= bClose (bs `index` i)
{-# INLINE eject_sinkFormatLn #-}
{-# INLINE_FLOW sinkFormatLn #-}
---------------------------------------------------------------------------------------------------
{-
-- | Create sinks that write values from some binary Repa table,
-- where all the values have a fixed length.
toTable :: (Packable format, Bulk A (Value format))
=> FilePath -- ^ Directory holding table.
-> Int -- ^ Number of buckets to use.
-> format -- ^ Format of data.
-> IO () -- ^ Action when a value cannot be serialised.
-> IO (Maybe (Sinks (Value format)))
toTable path nBuckets format aFail
| nBuckets <= 0
= return $ Nothing
| otherwise
= do
createDirectory path
-- Create all the bucket files.
let makeName i = path </> ((replicate (6 - (P.length $ show i)) '0') ++ show i)
let names = [makeName i | i <- [0 .. nBuckets - 1]]
let newBucket file
= do h <- openBinaryFile file WriteMode
return $ Bucket
{ bucketFilePath = Just file
, bucketStartPos = 0
, bucketLength = Nothing
, bucketHandle = h }
bs <- mapM newBucket names
-- Create a sink bundle for the buckets.
kk <- sinkFormatLn format aFail (A.fromList B bs)
return $ Just kk
{-# INLINE_FLOW toTable #-}
-- | Create sources that read values from some binary Repa table,
-- where all the values have a fixed length.
fromTable
:: (Packable format, Target A (Value format))
=> FilePath -- ^ Directory holding table.
-> format -- ^ Format of data.
-> IO () -- ^ Action when a value cannot be deserialised.
-> IO (Maybe (Sources (Value format)))
fromTable path format aFail
= do
-- All the files in the table directory.
fs <- getDirectoryContents path
-- Filter out special file names and make them relative to the dir stem.
let fsRel
= P.map (path </>)
$ P.filter (\f -> f /= "." && f /= "..") fs
let newBucket file
= do h <- openBinaryFile file ReadMode
return $ Bucket
{ bucketFilePath = Just file
, bucketStartPos = 0
, bucketLength = Nothing
, bucketHandle = h }
bs <- mapM newBucket fsRel
-- Create a source bundle for the buckets.
ss <- sourceFixedFormat format aFail (A.fromList B bs)
return $ Just ss
{-# INLINE_FLOW fromTable #-}
-}