streaming-bracketed-0.1.0.0: library/Streaming/Bracketed/Internal.hs
{-# LANGUAGE DeriveFunctor #-}
{-# LANGUAGE RankNTypes #-}
module Streaming.Bracketed.Internal where
import Data.Foldable
import Data.Bifunctor
import Data.IORef
import Control.Exception
import Streaming
import qualified Streaming.Prelude as S
import System.IO
newtype Bracketed a r =
Bracketed { runBracketed :: IORef Finstack -> Stream (Of a) IO r }
deriving Functor
-- | `first` maps over the yielded elements.
instance Bifunctor Bracketed where
first f (Bracketed b) = Bracketed (S.map f . b)
second = fmap
instance Applicative (Bracketed a) where
pure = Bracketed . const . pure
Bracketed b <*> Bracketed b' = Bracketed (\finref ->
b finref <*> b' finref)
instance Monad (Bracketed a) where
return = pure
Bracketed b >>= f = Bracketed (\finref ->
do r <- b finref
let Bracketed b' = f r
b' finref)
instance MonadIO (Bracketed a) where
liftIO action = Bracketed (\_ -> liftIO action)
-- | A stack of finalizers, accompanied by its length.
--
-- Finalizers at the head of the list correspond to deeper levels of nesting.
data Finstack = Finstack !Int [IO ()]
-- | Lift a `Stream` that doesn't perform allocation to a `Bracketed`.
clear :: Stream (Of x) IO r -> Bracketed x r
clear stream = Bracketed (const stream)
-- | Lift a `Stream` that requires resource allocation to a `Bracketed`.
bracketed :: IO a -> (a -> IO ()) -> (a -> Stream (Of x) IO r) -> Bracketed x r
bracketed allocate finalize stream = Bracketed (\finref ->
let open = do
a <- allocate
Finstack size0 fins <- readIORef finref
writeIORef finref (Finstack (succ size0) (finalize a : fins))
pure (size0,a)
in do (size0,a) <- liftIO (mask (\_ -> open))
r <- stream a
liftIO (mask (\_ -> reset size0 finref))
pure r)
-- | Consume a `Bracketed` stream, exhausting it.
with :: Bracketed a r -> (forall x. Stream (Of a) IO x -> IO (Of b x)) -> IO (Of b r)
with (Bracketed b) f =
Control.Exception.bracket (newIORef (Finstack 0 []))
(reset 0)
(f . b)
-- | Consume a `Bracketed` stream, possibly wihout exhausting it.
--
-- Finalizers lying in unconsumed parts of the stream will not be executed
-- until the callback returns, so better not tarry too long if you want
-- prompt finalization.
with_ :: Bracketed a r -> (Stream (Of a) IO r -> IO b) -> IO b
with_ (Bracketed b) f =
Control.Exception.bracket (newIORef (Finstack 0 []))
(reset 0)
(f . b)
-- | Apply to the underlying stream a transformation that preserves the return value.
over :: (forall x. Stream (Of a) IO x -> Stream (Of b) IO x) -> Bracketed a r -> Bracketed b r
over transform (Bracketed b) = Bracketed (transform . b)
-- | Apply to the underlying stream a transformation that might not preserve
-- the return value.
over_ :: (Stream (Of a) IO r -> Stream (Of b) IO r') -> Bracketed a r -> Bracketed b r'
over_ transform (Bracketed b) = Bracketed (\finref ->
let level = do
Finstack size _ <- readIORef finref
pure size
in do size0 <- liftIO level
r <- transform (b finref)
liftIO (mask (\_ -> reset size0 finref))
pure r)
-- | Replaces each element of a stream with an associated stream.
--
-- Can be useful for traversing hierachical structures.
for :: Bracketed a r -> (a -> Bracketed b x) -> Bracketed b r
for (Bracketed b) f =
Bracketed (\fins -> S.for (b fins) (flip (runBracketed . f) fins))
-- | Executes all finalizers that lie above a certain level.
reset :: Int -> IORef Finstack -> IO ()
reset size0 finref =
do Finstack size fins <- readIORef finref
let (pending,fins') = splitAt (size - size0) fins
writeIORef finref (Finstack size0 fins')
foldr finally (pure ()) pending
-- | A bracketed stream of all the lines in a text file.
--
-- This is adequate for simple use cases. For more advanced ones where
-- efficiency and memory usage are important, it's better to use a packed
-- text representation like the one provided by the @text@ package.
linesFromFile :: TextEncoding -> NewlineMode -> FilePath -> Bracketed String ()
linesFromFile encoding newlineMode path = bracketed
(openFile path ReadMode)
hClose
(\h -> do
liftIO (hSetEncoding h encoding)
liftIO (hSetNewlineMode h newlineMode)
S.untilRight
(do
eof <- hIsEOF h
if eof then Right <$> pure () else Left <$> hGetLine h
)
)
-- | Given a list of text files and line ranges, create a stream of lines
-- belonging to the concatenated ranges.
concatRanges
:: TextEncoding
-> NewlineMode
-> [(FilePath, Int, Int)]
-> Bracketed String ()
concatRanges encoding newlineMode ranges =
let streamRange (path, start, end) =
over_ (S.take (end - start)) . over (S.drop start) $ linesFromFile
encoding
newlineMode
path
in traverse_ streamRange ranges