{-# 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 = String -> m ()
forall (m :: * -> *). ChPrinter m => String -> m ()
mprint
  -- | Flush the buffer.
  mflush :: m ()
  mflush = () -> m ()
forall (m :: * -> *) a. Monad m => a -> m a
return ()
  -- | Alternative to mprint that does not mask any characters (depends on the carrier).
  mnomask :: String -> m ()
  mnomask = String -> m ()
forall (m :: * -> *). ChPrinter m => String -> m ()
mprint

instance ChPrinter IO where
  mprint :: String -> IO ()
mprint = String -> IO ()
putStr
  mnoecho :: String -> IO ()
mnoecho String
_ = () -> IO ()
forall (m :: * -> *) a. Monad m => a -> m a
return ()
  mflush :: IO ()
mflush = Handle -> IO ()
hFlush Handle
stdout

-- | DeafT discards all output (much like >\/dev\/null in the shell)
newtype DeafT m a = Deaf { DeafT m a -> m a
runDeafT :: m a }

instance Monad m => Monad (DeafT m) where
  return :: a -> DeafT m a
return = m a -> DeafT m a
forall (m :: * -> *) a. m a -> DeafT m a
Deaf (m a -> DeafT m a) -> (a -> m a) -> a -> DeafT m a
forall b c a. (b -> c) -> (a -> b) -> a -> c
. a -> m a
forall (m :: * -> *) a. Monad m => a -> m a
return
  (Deaf m a
d) >>= :: DeafT m a -> (a -> DeafT m b) -> DeafT m b
>>= a -> DeafT m b
f = m b -> DeafT m b
forall (m :: * -> *) a. m a -> DeafT m a
Deaf (m b -> DeafT m b) -> m b -> DeafT m b
forall a b. (a -> b) -> a -> b
$ do a
d' <- m a
d; DeafT m b -> m b
forall (m :: * -> *) a. DeafT m a -> m a
runDeafT (a -> DeafT m b
f a
d')

instance MonadTrans DeafT where
  lift :: m a -> DeafT m a
lift = m a -> DeafT m a
forall (m :: * -> *) a. m a -> DeafT m a
Deaf

instance (Functor m, Monad m) => Applicative (DeafT m) where
  pure :: a -> DeafT m a
pure = a -> DeafT m a
forall (m :: * -> *) a. Monad m => a -> m a
return
  <*> :: DeafT m (a -> b) -> DeafT m a -> DeafT m b
(<*>) = DeafT m (a -> b) -> DeafT m a -> DeafT m b
forall (m :: * -> *) a b. Monad m => m (a -> b) -> m a -> m b
ap

instance Functor m => Functor (DeafT m) where
  fmap :: (a -> b) -> DeafT m a -> DeafT m b
fmap a -> b
f (Deaf m a
a) = m b -> DeafT m b
forall (m :: * -> *) a. m a -> DeafT m a
Deaf (m b -> DeafT m b) -> m b -> DeafT m b
forall a b. (a -> b) -> a -> b
$ (a -> b) -> m a -> m b
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
fmap a -> b
f m a
a

instance MonadIO m => MonadIO (DeafT m) where
  liftIO :: IO a -> DeafT m a
liftIO = m a -> DeafT m a
forall (t :: (* -> *) -> * -> *) (m :: * -> *) a.
(MonadTrans t, Monad m) =>
m a -> t m a
lift (m a -> DeafT m a) -> (IO a -> m a) -> IO a -> DeafT m a
forall b c a. (b -> c) -> (a -> b) -> a -> c
. IO a -> m a
forall (m :: * -> *) a. MonadIO m => IO a -> m a
liftIO

instance Monad m => ChPrinter (DeafT m) where
  mprint :: String -> DeafT m ()
mprint String
_ = () -> DeafT m ()
forall (m :: * -> *) a. Monad m => a -> m a
return ()

-- Definition of OutRedirT + instances
-- | Redirects all output to a given handle (much like >filename in the shell)
newtype OutRedirT m a = OutRedir { OutRedirT m a -> Handle -> m a
runOutRedirT :: Handle -> m a }
-- | 'OutRedirT' on a blank 'IO' monad
type OutRedir = OutRedirT IO

instance Monad m => Monad (OutRedirT m) where
  return :: a -> OutRedirT m a
return a
a = (Handle -> m a) -> OutRedirT m a
forall (m :: * -> *) a. (Handle -> m a) -> OutRedirT m a
OutRedir ((Handle -> m a) -> OutRedirT m a)
-> (Handle -> m a) -> OutRedirT m a
forall a b. (a -> b) -> a -> b
$ \Handle
h -> a -> m a
forall (m :: * -> *) a. Monad m => a -> m a
return a
a
  (OutRedir Handle -> m a
r) >>= :: OutRedirT m a -> (a -> OutRedirT m b) -> OutRedirT m b
>>= a -> OutRedirT m b
f = (Handle -> m b) -> OutRedirT m b
forall (m :: * -> *) a. (Handle -> m a) -> OutRedirT m a
OutRedir ((Handle -> m b) -> OutRedirT m b)
-> (Handle -> m b) -> OutRedirT m b
forall a b. (a -> b) -> a -> b
$ \Handle
h -> do a
a <- Handle -> m a
r Handle
h; OutRedirT m b -> Handle -> m b
forall (m :: * -> *) a. OutRedirT m a -> Handle -> m a
runOutRedirT (a -> OutRedirT m b
f a
a) Handle
h

instance MonadTrans OutRedirT where
  lift :: m a -> OutRedirT m a
lift m a
m = (Handle -> m a) -> OutRedirT m a
forall (m :: * -> *) a. (Handle -> m a) -> OutRedirT m a
OutRedir ((Handle -> m a) -> OutRedirT m a)
-> (Handle -> m a) -> OutRedirT m a
forall a b. (a -> b) -> a -> b
$ \Handle
h -> m a
m

instance MonadIO m => MonadIO (OutRedirT m) where
  liftIO :: IO a -> OutRedirT m a
liftIO = m a -> OutRedirT m a
forall (t :: (* -> *) -> * -> *) (m :: * -> *) a.
(MonadTrans t, Monad m) =>
m a -> t m a
lift (m a -> OutRedirT m a) -> (IO a -> m a) -> IO a -> OutRedirT m a
forall b c a. (b -> c) -> (a -> b) -> a -> c
. IO a -> m a
forall (m :: * -> *) a. MonadIO m => IO a -> m a
liftIO

instance MonadIO m => ChPrinter (OutRedirT m) where
  mprint :: String -> OutRedirT m ()
mprint String
s = (Handle -> m ()) -> OutRedirT m ()
forall (m :: * -> *) a. (Handle -> m a) -> OutRedirT m a
OutRedir ((Handle -> m ()) -> OutRedirT m ())
-> (Handle -> m ()) -> OutRedirT m ()
forall a b. (a -> b) -> a -> b
$ \Handle
h -> do IO () -> m ()
forall (m :: * -> *) a. MonadIO m => IO a -> m a
liftIO (IO () -> m ()) -> IO () -> m ()
forall a b. (a -> b) -> a -> b
$ Handle -> String -> IO ()
hPutStr Handle
h String
s; () -> m ()
forall (m :: * -> *) a. Monad m => a -> m a
return ()
  mflush :: OutRedirT m ()
mflush = (Handle -> m ()) -> OutRedirT m ()
forall (m :: * -> *) a. (Handle -> m a) -> OutRedirT m a
OutRedir ((Handle -> m ()) -> OutRedirT m ())
-> (Handle -> m ()) -> OutRedirT m ()
forall a b. (a -> b) -> a -> b
$ \Handle
h -> do IO () -> m ()
forall (m :: * -> *) a. MonadIO m => IO a -> m a
liftIO (IO () -> m ()) -> IO () -> m ()
forall a b. (a -> b) -> a -> b
$ Handle -> IO ()
hFlush Handle
h; () -> m ()
forall (m :: * -> *) a. Monad m => a -> m a
return ()

instance Monad m => Functor (OutRedirT m) where
  fmap :: (a -> b) -> OutRedirT m a -> OutRedirT m b
fmap a -> b
f OutRedirT m a
a = (Handle -> m b) -> OutRedirT m b
forall (m :: * -> *) a. (Handle -> m a) -> OutRedirT m a
OutRedir ((Handle -> m b) -> OutRedirT m b)
-> (Handle -> m b) -> OutRedirT m b
forall a b. (a -> b) -> a -> b
$ \Handle
h -> do a
a' <- OutRedirT m a -> Handle -> m a
forall (m :: * -> *) a. OutRedirT m a -> Handle -> m a
runOutRedirT OutRedirT m a
a Handle
h; b -> m b
forall (m :: * -> *) a. Monad m => a -> m a
return (a -> b
f a
a')

instance Monad m => Applicative (OutRedirT m) where
  pure :: a -> OutRedirT m a
pure = a -> OutRedirT m a
forall (m :: * -> *) a. Monad m => a -> m a
return
  <*> :: OutRedirT m (a -> b) -> OutRedirT m a -> OutRedirT m b
(<*>) = OutRedirT m (a -> b) -> OutRedirT m a -> OutRedirT m b
forall (m :: * -> *) a b. Monad m => m (a -> b) -> m a -> m b
ap

-- | Run 'OutRedir' with a 'Handle'
runOutRedir :: OutRedir a -> Handle -> IO a
runOutRedir :: OutRedir a -> Handle -> IO a
runOutRedir = OutRedir a -> Handle -> IO a
forall (m :: * -> *) a. OutRedirT m a -> Handle -> m a
runOutRedirT

-- | Run 'OutRedirT' with a 'FilePath'
runOutRedirFT :: (Functor m,MonadIO m) => OutRedirT m a -> FilePath -> IOMode -> m a
runOutRedirFT :: OutRedirT m a -> String -> IOMode -> m a
runOutRedirFT OutRedirT m a
m String
fp IOMode
md
  | IOMode
md IOMode -> [IOMode] -> Bool
forall (t :: * -> *) a. (Foldable t, Eq a) => a -> t a -> Bool
`elem` [IOMode
AppendMode,IOMode
WriteMode] = do
    Handle
h <- IO Handle -> m Handle
forall (m :: * -> *) a. MonadIO m => IO a -> m a
liftIO (IO Handle -> m Handle) -> IO Handle -> m Handle
forall a b. (a -> b) -> a -> b
$ String -> IOMode -> IO Handle
openFile String
fp IOMode
md
    a
a <- OutRedirT m a -> Handle -> m a
forall (m :: * -> *) a. OutRedirT m a -> Handle -> m a
runOutRedirT OutRedirT m a
m Handle
h
    IO () -> m ()
forall (m :: * -> *) a. MonadIO m => IO a -> m a
liftIO (IO () -> m ()) -> IO () -> m ()
forall a b. (a -> b) -> a -> b
$ Handle -> IO ()
hClose Handle
h
    a -> m a
forall (m :: * -> *) a. Monad m => a -> m a
return a
a
  | Bool
otherwise = String -> m a
forall a. HasCallStack => String -> a
error String
"runOutRedirFT does only accept AppendMode or WriteMode."

-- | Run 'OutRedir' with a 'FilePath'
runOutRedirF :: OutRedir a -> FilePath -> IOMode -> IO a
runOutRedirF :: OutRedir a -> String -> IOMode -> IO a
runOutRedirF = OutRedir a -> String -> IOMode -> IO a
forall (m :: * -> *) a.
(Functor m, MonadIO m) =>
OutRedirT m a -> String -> IOMode -> m a
runOutRedirFT

-- Definition of RecorderT + instances
-- | Catches all output (much like VAR=$(...) in the shell)
newtype RecorderT m a = Recorder { RecorderT m a -> m (a, [String])
runRecorderT' :: m (a,[String]) }
-- | 'RecorderT' on the 'Identity'
type Recorder = RecorderT Identity

instance Monad m => Monad (RecorderT m) where
  return :: a -> RecorderT m a
return a
a = m (a, [String]) -> RecorderT m a
forall (m :: * -> *) a. m (a, [String]) -> RecorderT m a
Recorder (m (a, [String]) -> RecorderT m a)
-> m (a, [String]) -> RecorderT m a
forall a b. (a -> b) -> a -> b
$ (a, [String]) -> m (a, [String])
forall (m :: * -> *) a. Monad m => a -> m a
return (a
a,[])
  (Recorder m (a, [String])
r) >>= :: RecorderT m a -> (a -> RecorderT m b) -> RecorderT m b
>>= a -> RecorderT m b
f = m (b, [String]) -> RecorderT m b
forall (m :: * -> *) a. m (a, [String]) -> RecorderT m a
Recorder (m (b, [String]) -> RecorderT m b)
-> m (b, [String]) -> RecorderT m b
forall a b. (a -> b) -> a -> b
$ do
    (a
a,[String]
s) <- m (a, [String])
r
    (b
a',[String]
s') <- RecorderT m b -> m (b, [String])
forall (m :: * -> *) a. RecorderT m a -> m (a, [String])
runRecorderT' (a -> RecorderT m b
f a
a)
    (b, [String]) -> m (b, [String])
forall (m :: * -> *) a. Monad m => a -> m a
return (b
a', [String]
s'[String] -> [String] -> [String]
forall a. [a] -> [a] -> [a]
++[String]
s)

instance MonadTrans RecorderT where
  lift :: m a -> RecorderT m a
lift m a
m = m (a, [String]) -> RecorderT m a
forall (m :: * -> *) a. m (a, [String]) -> RecorderT m a
Recorder (m (a, [String]) -> RecorderT m a)
-> m (a, [String]) -> RecorderT m a
forall a b. (a -> b) -> a -> b
$ do a
a <- m a
m; (a, [String]) -> m (a, [String])
forall (m :: * -> *) a. Monad m => a -> m a
return (a
a,[])

instance Monad m => ChPrinter (RecorderT m) where
  mprint :: String -> RecorderT m ()
mprint String
s = m ((), [String]) -> RecorderT m ()
forall (m :: * -> *) a. m (a, [String]) -> RecorderT m a
Recorder (m ((), [String]) -> RecorderT m ())
-> m ((), [String]) -> RecorderT m ()
forall a b. (a -> b) -> a -> b
$ ((), [String]) -> m ((), [String])
forall (m :: * -> *) a. Monad m => a -> m a
return ((),[String
s])

instance Monad m => Functor (RecorderT m) where
  fmap :: (a -> b) -> RecorderT m a -> RecorderT m b
fmap = (a -> b) -> RecorderT m a -> RecorderT m b
forall (m :: * -> *) a1 r. Monad m => (a1 -> r) -> m a1 -> m r
liftM

instance Monad m => Applicative (RecorderT m) where
  <*> :: RecorderT m (a -> b) -> RecorderT m a -> RecorderT m b
(<*>) = RecorderT m (a -> b) -> RecorderT m a -> RecorderT m b
forall (m :: * -> *) a b. Monad m => m (a -> b) -> m a -> m b
ap
  pure :: a -> RecorderT m a
pure = a -> RecorderT m a
forall (m :: * -> *) a. Monad m => a -> m a
return

instance MonadIO m => MonadIO (RecorderT m) where
  liftIO :: IO a -> RecorderT m a
liftIO = m a -> RecorderT m a
forall (t :: (* -> *) -> * -> *) (m :: * -> *) a.
(MonadTrans t, Monad m) =>
m a -> t m a
lift (m a -> RecorderT m a) -> (IO a -> m a) -> IO a -> RecorderT m a
forall b c a. (b -> c) -> (a -> b) -> a -> c
. IO a -> m a
forall (m :: * -> *) a. MonadIO m => IO a -> m a
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 :: Replayable -> String
show Replayable
r = Int -> String
forall a. Show a => a -> String
show ((\(Replayable [String]
x) -> [String] -> Int
forall (t :: * -> *) a. Foldable t => t a -> Int
length [String]
x) Replayable
r) String -> ShowS
forall a. [a] -> [a] -> [a]
++ String
":" String -> ShowS
forall a. [a] -> [a] -> [a]
++ ShowS
forall a. Show a => a -> String
show (Replayable -> String
replay Replayable
r)

-- | Replay a recorder state in a pure context.
replay :: Replayable -> String
replay :: Replayable -> String
replay (Replayable [String]
r) = [String] -> String
forall (t :: * -> *) a. Foldable t => t [a] -> [a]
concat ([String] -> String) -> [String] -> String
forall a b. (a -> b) -> a -> b
$ [String] -> [String]
forall a. [a] -> [a]
reverse [String]
r

-- | Run 'Recorder' and also return its state.
runRecorder :: Recorder a -> (a,Replayable)
runRecorder :: Recorder a -> (a, Replayable)
runRecorder = ([String] -> Replayable) -> (a, [String]) -> (a, Replayable)
forall (a :: * -> * -> *) b c d.
Arrow a =>
a b c -> a (d, b) (d, c)
second [String] -> Replayable
Replayable ((a, [String]) -> (a, Replayable))
-> (Recorder a -> (a, [String])) -> Recorder a -> (a, Replayable)
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Identity (a, [String]) -> (a, [String])
forall a. Identity a -> a
runIdentity (Identity (a, [String]) -> (a, [String]))
-> (Recorder a -> Identity (a, [String]))
-> Recorder a
-> (a, [String])
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Recorder a -> Identity (a, [String])
forall (m :: * -> *) a. RecorderT m a -> m (a, [String])
runRecorderT'

-- | Run 'RecorderT' and also return its state.
runRecorderT :: (Functor m,Monad m) => RecorderT m a -> m (a,Replayable)
runRecorderT :: RecorderT m a -> m (a, Replayable)
runRecorderT = ((a, [String]) -> (a, Replayable))
-> m (a, [String]) -> m (a, Replayable)
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
fmap (([String] -> Replayable) -> (a, [String]) -> (a, Replayable)
forall (a :: * -> * -> *) b c d.
Arrow a =>
a b c -> a (d, b) (d, c)
second [String] -> Replayable
Replayable) (m (a, [String]) -> m (a, Replayable))
-> (RecorderT m a -> m (a, [String]))
-> RecorderT m a
-> m (a, Replayable)
forall b c a. (b -> c) -> (a -> b) -> a -> c
. RecorderT m a -> m (a, [String])
forall (m :: * -> *) a. RecorderT m a -> m (a, [String])
runRecorderT'

-- | Line-terminating alternative to 'mprint'
mprintLn :: ChPrinter m => String -> m ()
mprintLn :: String -> m ()
mprintLn = String -> m ()
forall (m :: * -> *). ChPrinter m => String -> m ()
mprint (String -> m ()) -> ShowS -> String -> m ()
forall b c a. (b -> c) -> (a -> b) -> a -> c
. (String -> ShowS
forall a. [a] -> [a] -> [a]
++String
"\n")

-- | Line-terminating alternative to 'mnomask'
mnomaskLn :: ChPrinter m => String -> m ()
mnomaskLn :: String -> m ()
mnomaskLn = String -> m ()
forall (m :: * -> *). ChPrinter m => String -> m ()
mnomask (String -> m ()) -> ShowS -> String -> m ()
forall b c a. (b -> c) -> (a -> b) -> a -> c
. (String -> ShowS
forall a. [a] -> [a] -> [a]
++String
"\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
  (.>>.) = mt m a -> t -> m r
forall t (mt :: (* -> *) -> * -> *) a r (m :: * -> *).
(RedirectionTarget t mt a r, Functor m, MonadIO m,
 ChPrinter (mt m)) =>
mt m a -> t -> m r
(.>.)
instance RedirectionTarget DiscardO DeafT a a where
  DeafT m a
m .>. :: DeafT m a -> DiscardO -> m a
.>. DiscardO
_ = DeafT m a -> m a
forall (m :: * -> *) a. DeafT m a -> m a
runDeafT DeafT m a
m
instance RedirectionTarget RecordO RecorderT a (a,Replayable) where
  RecorderT m a
m .>. :: RecorderT m a -> RecordO -> m (a, Replayable)
.>. RecordO
_ = RecorderT m a -> m (a, Replayable)
forall (m :: * -> *) a.
(Functor m, Monad m) =>
RecorderT m a -> m (a, Replayable)
runRecorderT RecorderT m a
m
instance RedirectionTarget FilePath OutRedirT a a where
  OutRedirT m a
m .>. :: OutRedirT m a -> String -> m a
.>. String
fp = OutRedirT m a -> String -> IOMode -> m a
forall (m :: * -> *) a.
(Functor m, MonadIO m) =>
OutRedirT m a -> String -> IOMode -> m a
runOutRedirFT OutRedirT m a
m String
fp IOMode
WriteMode
  OutRedirT m a
m .>>. :: OutRedirT m a -> String -> m a
.>>. String
fp = OutRedirT m a -> String -> IOMode -> m a
forall (m :: * -> *) a.
(Functor m, MonadIO m) =>
OutRedirT m a -> String -> IOMode -> m a
runOutRedirFT OutRedirT m a
m String
fp IOMode
AppendMode
instance RedirectionTarget Handle OutRedirT a a where
  OutRedirT m a
m .>. :: OutRedirT m a -> Handle -> m a
.>. Handle
fp = OutRedirT m a -> Handle -> m a
forall (m :: * -> *) a. OutRedirT m a -> Handle -> m a
runOutRedirT OutRedirT m a
m Handle
fp