stack-0.0.1: src/Control/Monad/Logger/Sticky.hs
{-# LANGUAGE TemplateHaskell #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
-- | Output a sticky line at the end of output.
module Control.Monad.Logger.Sticky
(StickyLoggingT
,runStickyLoggingT
,logSticky)
where
import Control.Applicative
import Control.Concurrent.MVar
import Control.Monad
import Control.Monad.IO.Class
import Control.Monad.Logger
import Control.Monad.Trans.Class
import Control.Monad.Catch
import Control.Monad.Reader
import Data.ByteString (ByteString)
import qualified Data.ByteString.Char8 as S8
import Data.Monoid
import Language.Haskell.TH
import System.IO
import System.Log.FastLogger
import Prelude -- avoid AMP warnings
data State = State
{ stateCurrentLine :: !(Maybe ByteString)
, stateMaxColumns :: !Int
, stateLastWasSticky :: !Bool
}
newtype StickyLoggingT m a = StickyLoggingT
{ unStickyLoggingT :: ReaderT (MVar State) m a
} deriving (Functor,Applicative,Monad,MonadIO,MonadTrans,MonadThrow)
runStickyLoggingT :: MonadIO m => StickyLoggingT m a -> m a
runStickyLoggingT m = do
state <- liftIO (newMVar (State Nothing 0 False))
originalMode <- liftIO (hGetBuffering stdout)
liftIO (hSetBuffering stdout NoBuffering)
a <- runReaderT (unStickyLoggingT m) state
state' <- liftIO (takeMVar state)
liftIO (when (stateLastWasSticky state') (S8.putStr "\n"))
liftIO (hSetBuffering stdout originalMode)
return a
logSticky :: Q Exp
logSticky =
logOther "sticky"
instance (MonadLogger m, MonadIO m) => MonadLogger (StickyLoggingT m) where
monadLoggerLog loc src level msg = do
ref <- StickyLoggingT ask
state <- liftIO (takeMVar ref) -- TODO: make exception-safe.
case level of
LevelOther "sticky" ->
liftIO $
do S8.putStr
("\r" <>
pad (stateMaxColumns state) msgBytes)
putMVar
ref
(state
{ stateLastWasSticky = True
, stateMaxColumns = max
(stateMaxColumns state)
(S8.length msgBytes)
, stateCurrentLine = Just msgBytes
})
_ -> do
liftIO
(S8.putStr
("\r" <>
S8.replicate
(stateMaxColumns state)
' ' <>
"\r"))
lift (monadLoggerLog loc src level msg)
liftIO
(case stateCurrentLine state of
Nothing ->
putMVar
ref
(state
{ stateLastWasSticky = False
, stateMaxColumns = max
(stateMaxColumns state)
(S8.length msgBytes)
})
Just line -> do
S8.putStr line
putMVar
ref
(state
{ stateLastWasSticky = True
, stateMaxColumns = max
(stateMaxColumns state)
(S8.length msgBytes)
}))
where
msgBytes =
fromLogStr
(toLogStr msg)
pad width s =
S8.take
(max width (S8.length s))
(s <>
S8.replicate width ' ')