{-# LANGUAGE LambdaCase #-} -- | IO on streams. module Hpp.StreamIO where import Control.Monad.IO.Class (MonadIO, liftIO) import Hpp.Streamer import Hpp.Types import System.Directory (getTemporaryDirectory, renameFile, removeFile) import System.IO (IOMode(ReadMode), hClose, hPutStr, openTempFile, openFile, hGetLine, hIsEOF, hIsClosed, hSetBuffering, BufferMode(..)) -- | @sourceFile registerCleanup filePath@ produces a 'Source' of -- lines from file @filePath@ after registering an action that closes -- the file using the provided @registerCleanup@ function. sourceFile :: (MonadIO m, MonadIO m') => (Cleanup -> m' ()) -> FilePath -> m' (Source m String ()) sourceFile register fp = do h <- liftIO $ do h <- openFile fp ReadMode hSetBuffering h (BlockBuffering Nothing) return h (cleanup,neutralize) <- liftIO $ mkCleanup (hClose h) let {-# INLINABLE go #-} go :: MonadIO m => Source m String () go = Streamer $ do closed <- liftIO $ hIsClosed h if closed then return $ Done (Just ()) else do eof <- liftIO $ hIsEOF h if eof then Done (Just ()) <$ (liftIO (neutralize >> hClose h)) else liftIO (fmap (flip Yield go) (hGetLine h)) -- else liftIO (hGetLine h) >>= return . flip Yield go -- . (++"\n") register cleanup >> return go {-# INLINE sourceFile #-} -- | Incrementally writes 'String's to a temporary file. When all -- input is exhausted, the temporary file is renamed to the supplied -- 'FilePath'. sinkToFile :: MonadIO m => (Cleanup -> m ()) -> FilePath -> Streamer m String o () sinkToFile register fp = Streamer$ do (tmp,h) <- liftIO $ getTemporaryDirectory >>= flip openTempFile "hpp.tmp" (cleanup, neutralize) <- liftIO $ mkCleanup (hClose h >> removeFile tmp) let dunzo = Streamer . liftIO $ do neutralize hClose h renameFile tmp fp return (Done (Just ())) go = encase $ Await (\s -> Streamer $ liftIO (hPutStr h s) >> runStream go) dunzo register cleanup runStream go {-# INLINE sinkToFile #-} -- | Sink a stream with a function evaluated only for its -- side-effects. sinkTell :: Monad m => (a -> m ()) -> Streamer m a o () sinkTell tell = go where go = awaits (\i -> Streamer (tell i >> runStream go)) {-# INLINE sinkTell #-} -- | Sink a stream to 'System.IO.stdout' sinkToStdOut :: MonadIO m => Streamer m String o () sinkToStdOut = sinkTell (liftIO . putStr) {-# INLINE sinkToStdOut #-} -- | @sink_ = forever await@ Simply discards all inputs. This may be -- used to exhaust a stream solely for its effects. sink_ :: Monad m => Streamer m i o () sink_ = awaits (const sink_)