chatty-0.8.0.1: Text/Chatty/Printer.hs
{-# LANGUAGE FlexibleInstances, FunctionalDependencies, ConstraintKinds, KindSignatures #-}
{-
This module is part of Chatty.
Copyleft (c) 2014,2021,2026 Enum 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 Control.Monad.Trans.Resource
import System.IO
import Data.Kind
-- | 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
-- | DiscardT discards all output (much like >\/dev\/null in the shell)
newtype DiscardT m a = Discard { runDiscardT :: m a }
instance Monad m => Monad (DiscardT m) where
(Discard d) >>= f = Discard $ do d' <- d; runDiscardT (f d')
instance MonadTrans DiscardT where
lift = Discard
instance (Functor m, Monad m) => Applicative (DiscardT m) where
pure = Discard . return
(<*>) = ap
instance Functor m => Functor (DiscardT m) where
fmap f (Discard a) = Discard $ fmap f a
instance MonadIO m => MonadIO (DiscardT m) where
liftIO = lift . liftIO
instance Monad m => ChPrinter (DiscardT 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
(OutRedir r) >>= f = OutRedir $ \h -> do a <- r h; runOutRedirT (f a) h
instance MonadTrans OutRedirT where
lift m = OutRedir $ \_ -> 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 a = OutRedir $ \h -> return a
(<*>) = ap
instance MonadResource m => MonadResource (OutRedirT m) where
liftResourceT = lift . liftResourceT
-- | Run 'OutRedir' with a 'Handle'
runOutRedir :: OutRedir a -> Handle -> IO a
runOutRedir = runOutRedirT
-- | Run 'OutRedirT' with a 'FilePath'
runOutRedirFT :: MonadResource m => OutRedirT m a -> FilePath -> IOMode -> m a
runOutRedirFT m fp md
| md `elem` [AppendMode,WriteMode] = do
(key, h) <- allocate (liftIO $ openFile fp md) (liftIO . hClose)
a <- runOutRedirT m h
a `seq` release key
return a
| otherwise = error "runOutRedirFT does only accept AppendMode or WriteMode."
-- 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
(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 a = Recorder $ return (a,[])
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 :: 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 (c :: (* -> *) -> Constraint) mt a r | t -> mt c, t a -> r where
-- | Overwriting redirection.
(.>.) :: (c m,ChPrinter (mt m)) => mt m a -> t -> m r
-- | Appending redirection.
(.>>.) :: (c m,ChPrinter (mt m)) => mt m a -> t -> m r
(.>>.) = (.>.)
instance RedirectionTarget DiscardO Monad DiscardT a a where
m .>. _ = runDiscardT m
instance RedirectionTarget RecordO Monad RecorderT a (a,Replayable) where
m .>. _ = runRecorderT m
instance RedirectionTarget FilePath MonadResource OutRedirT a a where
m .>. fp = runOutRedirFT m fp WriteMode
m .>>. fp = runOutRedirFT m fp AppendMode
instance RedirectionTarget Handle MonadIO OutRedirT a a where
m .>. fp = runOutRedirT m fp