chatty-0.2.0.1: Text/Chatty/Printer.hs
{-# LANGUAGE FlexibleInstances, MultiParamTypeClasses, FunctionalDependencies #-}
-- | Provides a typeclass for all monads that may print text.
module Text.Chatty.Printer where
import Control.Arrow
import Control.Monad
import Control.Monad.State
import Control.Monad.Identity
import System.IO
-- | A typeclass for all monads that may output strings.
class Monad m => MonadPrinter m where
-- | Just print it!
mprint :: String -> m ()
-- | Print it, except you are IO.
mnoecho :: String -> m ()
mnoecho = mprint
-- | Flush the buffer.
mflush :: m ()
mflush = return ()
instance MonadPrinter IO where
mprint = putStr
mnoecho _ = return ()
mflush = hFlush stdout
instance Monad m => MonadPrinter (StateT String m) where
mprint s = modify (++s)
-- | DeafT discards all output (much like >\/dev\/null in the shell)
newtype DeafT m a = Deaf { runDeafT :: m a }
instance Monad m => Monad (DeafT m) where
return = Deaf . return
(Deaf d) >>= f = Deaf $ do d' <- d; runDeafT (f d')
instance MonadTrans DeafT where
lift = Deaf
instance Functor m => Functor (DeafT m) where
fmap f (Deaf a) = Deaf $ fmap f a
instance MonadIO m => MonadIO (DeafT m) where
liftIO = lift . liftIO
instance Monad m => MonadPrinter (DeafT m) where
mprint _ = return ()
-- Definition of OutRedirT + instances
-- | Redirects all output to a given handle (much like >filename in the shell)
newtype OutRedirT m a = OutRedir { runOutRedirT' :: Handle -> m (a,Handle) }
-- | 'OutRedirT' on a blank 'IO' monad
type OutRedir = OutRedirT IO
instance Monad m => Monad (OutRedirT m) where
return a = OutRedir $ \h -> return (a,h)
(OutRedir r) >>= f = OutRedir $ \h -> do (a,h') <- r h; runOutRedirT' (f a) h'
instance MonadTrans OutRedirT where
lift m = OutRedir $ \h -> do a <- m; return (a,h)
instance MonadIO m => MonadIO (OutRedirT m) where
liftIO = lift . liftIO
instance MonadIO m => MonadPrinter (OutRedirT m) where
mprint s = OutRedir $ \h -> do liftIO $ hPutStr h s; return ((),h)
mflush = OutRedir $ \h -> do liftIO $ hFlush h; return ((),h)
instance Monad m => Functor (OutRedirT m) where
fmap f a = OutRedir $ \h -> do (a',h') <- runOutRedirT' a h; return (f a',h')
-- | Run 'OutRedirT' with a 'Handle'
runOutRedirT :: Functor m => OutRedirT m a -> Handle -> m a
runOutRedirT m h = fmap fst $ runOutRedirT' m h
-- | Run 'OutRedir' with a 'Handle'
runOutRedir :: OutRedir a -> Handle -> IO a
runOutRedir = runOutRedirT
-- | Run 'OutRedirT' with a 'FilePath'
runOutRedirFT :: (Functor m,MonadIO m) => OutRedirT m a -> FilePath -> IOMode -> m a
runOutRedirFT m fp md
| md `elem` [AppendMode,WriteMode] = do
h <- liftIO $ openFile fp md
a <- runOutRedirT m h
liftIO $ hClose h
return a
| otherwise = error "runOutRedirFT does only accept AppendMode or WriteMode."
-- | Run 'OutRedir' with a 'FilePath'
runOutRedirF :: OutRedir a -> FilePath -> IOMode -> IO a
runOutRedirF = runOutRedirFT
-- Definition of RecorderT + instances
-- | Catches all output (much like VAR=$(...) in the shell)
newtype RecorderT m a = Recorder { runRecorderT' :: [String] -> m (a,[String]) }
-- | 'RecorderT' on the 'Identity'
type Recorder = RecorderT Identity
instance Monad m => Monad (RecorderT m) where
return a = Recorder $ \s -> return (a,s)
(Recorder r) >>= f = Recorder $ \s -> do (a,s') <- r s; runRecorderT' (f a) s'
instance MonadTrans RecorderT where
lift m = Recorder $ \s -> do a <- m; return (a,s)
instance Monad m => MonadPrinter (RecorderT m) where
mprint s = Recorder $ \s' -> return ((),s:s')
instance Monad m => Functor (RecorderT m) where
fmap f a = Recorder $ \s -> do (a',s') <- runRecorderT' a s; return (f a',s')
instance MonadIO m => MonadIO (RecorderT m) where
liftIO = lift . liftIO
-- Helper methods for RecorderT
-- | The recorder state. Use this together with 'replay', 'replayM' or 'replay_'.
newtype Replayable = Replayable [String]
instance Show Replayable where show r = show ((\(Replayable x) -> length x) r) ++ ":" ++ show (replay r)
-- | Replay a recorder state inside a 'Monad'.
replayM :: Monad m => m Replayable -> m String
replayM r = do (Replayable r') <- r; return (concat $ reverse r')
-- | Replay a recorder state in a pure context.
replay :: Replayable -> String
replay (Replayable r) = concat $ reverse r
-- | Replay the current recorder state without leaving the recorder.
replay_ :: Monad m => RecorderT m String
replay_ = Recorder $ \s -> return (concat $ reverse s,s)
-- | Run 'Recorder' and also return its state.
runRecorder :: Recorder a -> (a,Replayable)
runRecorder = second Replayable . runIdentity . flip runRecorderT' []
-- | Run 'RecorderT' and also return its state.
runRecorderT :: (Functor m,Monad m) => RecorderT m a -> m (a,Replayable)
runRecorderT = fmap (second Replayable) . flip runRecorderT' []
-- | Line-terminating alternative to 'mprint'
mprintLn :: MonadPrinter m => String -> m ()
mprintLn = mprint . (++"\n")
-- Shell-like syntax
-- | Redirection target that discards input.
data DiscardO = DiscardO
-- | Redirection target that records input.
data RecordO = RecordO
-- | Class for all redirection targets.
class RedirectionTarget t mt a r | t -> mt, t a -> r where
-- | Overwriting redirection.
(.>.) :: (Functor m,MonadIO m,MonadPrinter (mt m)) => mt m a -> t -> m r
-- | Appending redirection.
(.>>.) :: (Functor m,MonadIO m,MonadPrinter (mt m)) => mt m a -> t -> m r
(.>>.) = (.>.)
instance RedirectionTarget DiscardO DeafT a a where
m .>. _ = runDeafT m
instance RedirectionTarget RecordO RecorderT a (a,Replayable) where
m .>. _ = runRecorderT m
instance RedirectionTarget FilePath OutRedirT a a where
m .>. fp = runOutRedirFT m fp WriteMode
m .>>. fp = runOutRedirFT m fp AppendMode