chatty-0.7.0.0: Text/Chatty/Printer.hs
{-# LANGUAGE FlexibleInstances, MultiParamTypeClasses, FunctionalDependencies, Safe #-}
{-
This module is part of Chatty.
Copyleft (c) 2014 Marvin Cohrs
All wrongs reversed. Sharing is an act of love, not crime.
Please share Antisplice with everyone you like.
Chatty is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Chatty is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with Chatty. If not, see <http://www.gnu.org/licenses/>.
-}
-- | Provides a typeclass for all monads that may print text.
module Text.Chatty.Printer where
import Control.Applicative
import Control.Arrow
import Control.Monad
import Control.Monad.State
import Control.Monad.Identity
import Control.Monad.Writer
import System.IO
-- | A typeclass for all monads that may output strings.
class Monad m => ChPrinter 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 ()
-- | Alternative to mprint that does not mask any characters (depends on the carrier).
mnomask :: String -> m ()
mnomask = mprint
instance ChPrinter IO where
mprint = putStr
mnoecho _ = return ()
mflush = hFlush stdout
-- | 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, Monad m) => Applicative (DeafT m) where
pure = return
(<*>) = ap
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 => ChPrinter (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 }
-- | 'OutRedirT' on a blank 'IO' monad
type OutRedir = OutRedirT IO
instance Monad m => Monad (OutRedirT m) where
return a = OutRedir $ \h -> return a
(OutRedir r) >>= f = OutRedir $ \h -> do a <- r h; runOutRedirT (f a) h
instance MonadTrans OutRedirT where
lift m = OutRedir $ \h -> m
instance MonadIO m => MonadIO (OutRedirT m) where
liftIO = lift . liftIO
instance MonadIO m => ChPrinter (OutRedirT m) where
mprint s = OutRedir $ \h -> do liftIO $ hPutStr h s; return ()
mflush = OutRedir $ \h -> do liftIO $ hFlush h; return ()
instance Monad m => Functor (OutRedirT m) where
fmap f a = OutRedir $ \h -> do a' <- runOutRedirT a h; return (f a')
instance Monad m => Applicative (OutRedirT m) where
pure = return
(<*>) = ap
-- | 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' :: m (a,[String]) }
-- | 'RecorderT' on the 'Identity'
type Recorder = RecorderT Identity
instance Monad m => Monad (RecorderT m) where
return a = Recorder $ return (a,[])
(Recorder r) >>= f = Recorder $ do
(a,s) <- r
(a',s') <- runRecorderT' (f a)
return (a', s'++s)
instance MonadTrans RecorderT where
lift m = Recorder $ do a <- m; return (a,[])
instance Monad m => ChPrinter (RecorderT m) where
mprint s = Recorder $ return ((),[s])
instance Monad m => Functor (RecorderT m) where
fmap = liftM
instance Monad m => Applicative (RecorderT m) where
(<*>) = ap
pure = return
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 in a pure context.
replay :: Replayable -> String
replay (Replayable r) = concat $ reverse r
-- | Run 'Recorder' and also return its state.
runRecorder :: Recorder a -> (a,Replayable)
runRecorder = second Replayable . runIdentity . runRecorderT'
-- | Run 'RecorderT' and also return its state.
runRecorderT :: (Functor m,Monad m) => RecorderT m a -> m (a,Replayable)
runRecorderT = fmap (second Replayable) . runRecorderT'
-- | Line-terminating alternative to 'mprint'
mprintLn :: ChPrinter m => String -> m ()
mprintLn = mprint . (++"\n")
-- | Line-terminating alternative to 'mnomask'
mnomaskLn :: ChPrinter m => String -> m ()
mnomaskLn = mnomask . (++"\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,ChPrinter (mt m)) => mt m a -> t -> m r
-- | Appending redirection.
(.>>.) :: (Functor m,MonadIO m,ChPrinter (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
instance RedirectionTarget Handle OutRedirT a a where
m .>. fp = runOutRedirT m fp