{-# 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 . -} -- | 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