stdio-0.1.0.0: Std/IO/StdStream.hs
{-# LANGUAGE MultiWayIf #-}
{-# LANGUAGE BangPatterns #-}
{-|
Module : Std.IO.StdStream
Description : TTY devices
Copyright : (c) Dong Han, 2018~2019
License : BSD
Maintainer : winterland1989@gmail.com
Stability : experimental
Portability : non-portable
This module provides stdin\/stderr\/stdout reading and writings. Usually you don't have to use 'stderr' or 'stderrBuf' directly, 'Std.IO.Logger' provides more logging utilities through @stderr@. While 'stdinBuf' and 'stdoutBuf' is useful when you write interactive programs, 'Std.IO.Buffered' module provide many reading and writing operations. Example:
@
import Std.IO.LowResTimer
import Std.IO.Buffered
import Std.IO.StdStream
main = do
-- read by '\n'
b1 <- readLineStd
-- read whatever user input in 3s, otherwise get Nothing
b2 <- timeoutLowRes 30 $ readBuffered stdinBuf
...
putStd "hello world!"
@
-}
module Std.IO.StdStream
( -- * Standard input & output streams
StdStream
, isStdStreamTTY
, stdin, stdout, stderr
, stdinBuf, stdoutBuf
-- * utils
, printStd
, readLineStd
, putStd
, putLineStd
) where
import Std.Data.Builder as B
import Std.Data.Vector as V
import Std.IO.UV.FFI
import Std.IO.UV.Manager
import Control.Monad
import Control.Concurrent.STM.TVar
import Control.Concurrent.MVar
import Std.IO.Exception
import Std.IO.UV.Errno
import System.IO.Unsafe
import Std.IO.Resource
import Std.IO.Buffered
import Std.Data.Vector.Base (defaultChunkSize)
import Foreign.Ptr
-- | Standard input and output streams
--
-- We support both regular file and TTY based streams, when initialized
-- 'uv_guess_handle' is called to decide which type of devices are connected
-- to standard streams.
--
-- 'StdStream' is different from other 'UVStream' in that exception during reading & writing
-- won't close 'StdStream'.
data StdStream
= StdTTY {-# UNPACK #-}!(Ptr UVHandle) {-# UNPACK #-}!UVSlot UVManager -- similar to UVStream
| StdFile {-# UNPACK #-}!UVFD -- similar to UVFile
isStdStreamTTY :: StdStream -> Bool
isStdStreamTTY (StdTTY _ _ _) = True
isStdStreamTTY _ = False
instance Input StdStream where
{-# INLINE readInput #-}
readInput uvs@(StdTTY handle slot uvm) buf len = mask_ $ do
m <- getBlockMVar uvm slot
withUVManager_ uvm $ do
throwUVIfMinus_ (hs_uv_read_start handle)
pokeBufferTable uvm slot buf len
tryTakeMVar m
r <- takeMVar m
if | r > 0 -> return r
-- r == 0 should be impossible, since we guard this situation in c side
| r == fromIntegral UV_EOF -> return 0
| r < 0 -> throwUVIfMinus (return r)
readInput (StdFile fd) buf len =
throwUVIfMinus $ hs_uv_fs_read fd buf len (-1)
instance Output StdStream where
{-# INLINE writeOutput #-}
writeOutput (StdTTY handle _ uvm) buf len = mask_ $ do
(slot, m) <- withUVManager_ uvm $ do
slot <- getUVSlot uvm (hs_uv_write handle buf len)
m <- getBlockMVar uvm slot
tryTakeMVar m
return (slot, m)
throwUVIfMinus_ (takeMVar m)
writeOutput (StdFile fd) buf len = go buf len
where
go !buf !bufSiz = do
written <- throwUVIfMinus
(hs_uv_fs_write fd buf bufSiz (-1))
when (written < bufSiz)
(go (buf `plusPtr` written) (bufSiz-written))
stdin :: StdStream
{-# NOINLINE stdin #-}
stdin = unsafePerformIO (makeStdStream 0)
stdout :: StdStream
{-# NOINLINE stdout #-}
stdout = unsafePerformIO (makeStdStream 1)
stderr :: StdStream
{-# NOINLINE stderr #-}
stderr = unsafePerformIO (makeStdStream 2)
stdinBuf :: BufferedInput StdStream
{-# NOINLINE stdinBuf #-}
stdinBuf = unsafePerformIO (newBufferedInput stdin defaultChunkSize)
stdoutBuf :: BufferedOutput StdStream
{-# NOINLINE stdoutBuf #-}
stdoutBuf = unsafePerformIO (newBufferedOutput stdout defaultChunkSize)
makeStdStream :: UVFD -> IO StdStream
makeStdStream fd = do
typ <- uv_guess_handle fd
if typ == UV_TTY
then do
uvm <- getUVManager
withUVManager uvm $ \ loop -> do
handle <- hs_uv_handle_alloc loop
slot <- getUVSlot uvm (peekUVHandleData handle)
tryTakeMVar =<< getBlockMVar uvm slot -- clear the parking spot
throwUVIfMinus_ (uv_tty_init loop handle (fromIntegral fd))
`onException` hs_uv_handle_free handle
return (StdTTY handle slot uvm)
else return (StdFile fd)
--------------------------------------------------------------------------------
-- | print a 'Show' to stdout
printStd :: Show a => a -> IO ()
printStd s = do
writeBuffer stdoutBuf (B.buildBytes . B.stringUTF8 . show $ s)
flushBuffer stdoutBuf
-- | print a 'Builder' and flush to stdout.
putStd :: Builder a -> IO ()
putStd b = do
writeBuffer stdoutBuf (B.buildBytes b)
flushBuffer stdoutBuf
-- | print a 'Builder' and flush to stdout stdout, with a linefeed.
putLineStd :: Builder a -> IO ()
putLineStd b = do
writeBuffer stdoutBuf (B.buildBytes $ b >> B.char8 '\n')
flushBuffer stdoutBuf
-- | read a line from stdin
readLineStd :: IO V.Bytes
readLineStd = readLine stdinBuf