chatty-0.5.4.0: Text/Chatty/Printer.hs
{-# LANGUAGE FlexibleInstances, MultiParamTypeClasses, FunctionalDependencies #-}
{-
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.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 ()
-- | Alternative to mprint that does not mask any characters (depends on the carrier).
mnomask :: String -> m ()
mnomask = mprint
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")
-- | Line-terminating alternative to 'mnomask'
mnomaskLn :: MonadPrinter 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,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