module System.Process.Run
(
RunT
, runT
, RunState(..)
, OutputStyle(..)
, RunM
, echoStart
, echoEnd
, output
, silent
, dots
, indent
, vlevel
, quieter
, noisier
, lazy
, strict
, message
, run
, module System.Process.ListLike
) where
#if __GLASGOW_HASKELL__ <= 709
import Data.Monoid (Monoid, mempty)
#endif
import Control.Monad (when)
import Control.Monad.State (evalState, evalStateT, get, modify, MonadState, put, StateT)
import Control.Monad.Trans (MonadIO, lift, liftIO)
import Data.ByteString (ByteString)
import qualified Data.ByteString.Lazy as Lazy (ByteString)
import Data.Char (ord)
import Data.Default (Default(def))
import Data.ListLike as ListLike
(break, fromList, head, hPutStr, length, ListLike, null, putStr, singleton, tail)
import Data.Monoid ((<>))
import Data.String (IsString, fromString)
import Data.Text (Text)
import Data.Word (Word8)
import qualified Data.Text.Lazy as Lazy (Text)
import System.IO (hPutStr, hPutStrLn, stderr)
import System.Process.ListLike
data RunState text
= RunState
{ _output :: OutputStyle
, _outprefix :: text
, _errprefix :: text
, _echoStart :: Bool
, _echoEnd :: Bool
, _verbosity :: Int
, _lazy :: Bool
, _message :: text
}
type RunT text m = StateT (RunState text) m
class (MonadState (RunState text) m,
ProcessText text char,
ListLikeProcessIO text char,
MonadIO m, IsString text, Eq char, Dot char) =>
RunM text char m
instance Dot Word8 where
dot = fromIntegral (ord '.')
instance (MonadIO m, MonadState (RunState String) m) => RunM String Char m
instance (MonadIO m, MonadState (RunState Text) m) => RunM Text Char m
instance (MonadIO m, MonadState (RunState Lazy.Text) m) => RunM Lazy.Text Char m
instance (MonadIO m, MonadState (RunState ByteString) m) => RunM ByteString Word8 m
instance (MonadIO m, MonadState (RunState Lazy.ByteString) m) => RunM Lazy.ByteString Word8 m
runT :: forall m text char a. (MonadIO m, ProcessText text char) => RunT text m a -> m a
runT action = evalStateT action (def :: RunState text)
data OutputStyle
= Dots Int
| All
| Indented
| Silent
instance ProcessText text char => Default (RunState text) where
def = RunState { _outprefix = fromString "1> "
, _errprefix = fromString "2> "
, _output = All
, _echoStart = True
, _echoEnd = True
, _verbosity = 3
, _lazy = False
, _message = mempty }
noEcho :: (MonadState (RunState t) m) => m ()
noEcho = modify (\x -> x { _echoStart = False, _echoEnd = False })
echoStart :: (MonadState (RunState t) m) => m ()
echoStart = modify (\x -> x { _echoStart = True })
echoEnd :: (MonadState (RunState t) m) => m ()
echoEnd = modify (\x -> x { _echoEnd = True })
output :: (MonadState (RunState t) m) => m ()
output = modify (\x -> x { _output = All })
silent :: (MonadState (RunState t) m) => m ()
silent = modify (\x -> x { _output = Silent })
dots :: (MonadState (RunState t) m) => Int -> m ()
dots n = modify (\x -> x { _output = Dots n })
indent :: (MonadState (RunState t) m, ListLike t char) => (t -> t) -> (t -> t) -> m ()
indent so se = modify $ \x ->
let so' = so (_outprefix x)
se' = se (_errprefix x) in
x { _outprefix = so'
, _errprefix = se'
, _output = if ListLike.null so' &&
ListLike.null se' then _output x else Indented }
noIndent :: (MonadState (RunState text) m, ListLike text char) => m ()
noIndent = indent (const mempty) (const mempty)
vlevel :: forall m text char.
(IsString text, ListLike text char, MonadIO m, MonadState (RunState text) m) =>
Int -> m ()
vlevel n = do
modify (\x -> x {_verbosity = n})
case n of
_ | n <= 0 -> noEcho >> silent >> noIndent
1 -> vlevel 0 >> echoStart
2 -> vlevel 1 >> echoEnd >> dots 100
_ ->
vlevel 2 >> output >> indent (const (fromString "1> ")) (const (fromString ("2> ")))
quieter :: RunM text char m => m ()
quieter = get >>= \x -> vlevel (_verbosity x 1)
noisier :: RunM text char m => m ()
noisier = get >>= \x -> vlevel (_verbosity x + 1)
strict :: RunM text char m => m ()
strict = modify (\x -> x { _lazy = False })
lazy :: RunM text char m => m ()
lazy = modify (\x -> x { _lazy = True})
message :: RunM text char m => (text -> text) -> m ()
message f = modify (\x -> x { _message = f (_message x) })
class Dot c where
dot :: c
instance Dot Char where
dot = '.'
run' :: forall m maker text char.
(RunM text char m,
ProcessMaker maker) =>
maker -> text -> m [Chunk text]
run' maker input = do
st0 <- get
when (_echoStart st0) (liftIO $ hPutStrLn stderr ("-> " ++ showProcessMakerForUser maker))
result <- liftIO $ (if _lazy st0 then readCreateProcessLazy else readCreateProcess) maker input >>= doOutput st0
when (_echoEnd st0) (liftIO $ hPutStrLn stderr ("<- " ++ showProcessMakerForUser maker))
return result
where
doOutput :: RunState text -> [Chunk text] -> IO [Chunk text]
doOutput (RunState {_output = Dots n}) cs = putDotsLn n cs
doOutput (RunState {_output = Silent}) cs = return cs
doOutput (RunState {_output = All}) cs = writeOutput cs
doOutput (RunState {_output = Indented, _outprefix = outp, _errprefix = errp}) cs = writeOutputIndented outp errp cs
run :: forall m maker text char result.
(RunM text char m,
ProcessMaker maker,
ProcessResult text result) =>
maker -> text -> m result
run maker input = run' maker input >>= return . collectOutput
putDotsLn :: (ListLikeProcessIO text char, Dot char) =>
Int -> [Chunk text] -> IO [Chunk text]
putDotsLn cpd chunks = putDots cpd chunks >>= \ r -> System.IO.hPutStr stderr "\n" >> return r
putDots :: (ListLikeProcessIO text char, Dot char) => Int -> [Chunk text] -> IO [Chunk text]
putDots charsPerDot chunks =
evalStateT (mapM (\ x -> dotifyChunk charsPerDot x >>= mapM_ (lift . putChunk) >> return x) chunks) 0
dotifyChunk :: forall text char m. (Monad m, ListLike text char, Dot char) =>
Int -> Chunk text -> StateT Int m [Chunk text]
dotifyChunk charsPerDot chunk =
case chunk of
Stdout x -> doChars (ListLike.length x)
Stderr x -> doChars (ListLike.length x)
_ -> return [chunk]
where
doChars :: Int -> StateT Int m [Chunk text]
doChars count = do
remaining <- get
let (count', remaining') = divMod (remaining + count) (fromIntegral charsPerDot)
put remaining'
if (count' > 0) then return [Stderr (ListLike.fromList (replicate count' dot))] else return []
putChunk :: ListLikeProcessIO text char => Chunk text -> IO ()
putChunk (Stdout x) = ListLike.putStr x
putChunk (Stderr x) = ListLike.hPutStr stderr x
putChunk _ = return ()
writeOutputIndented :: (ListLikeProcessIO text char, Eq char, IsString text) =>
text -> text -> [Chunk text] -> IO [Chunk text]
writeOutputIndented outp errp chunks =
mapM (\(c, cs) -> mapM_ writeChunk cs >> return c) (indentChunks outp errp chunks)
indentChunks :: forall text char. (ListLikeProcessIO text char, Eq char, IsString text) =>
text -> text -> [Chunk text] -> [(Chunk text, [Chunk text])]
indentChunks outp errp chunks =
evalState (mapM (indentChunk nl outp errp) chunks) BOL
where
nl :: char
nl = ListLike.head (fromString "\n" :: text)
data BOL = BOL | MOL deriving (Eq)
indentChunk :: forall m text char.
(Eq char, ListLike text char, MonadState BOL m) =>
char -> text -> text -> Chunk text -> m (Chunk text, [Chunk text])
indentChunk nl outp errp chunk =
case chunk of
Stdout x -> doText Stdout outp x >>= return . (chunk,)
Stderr x -> doText Stderr errp x >>= return . (chunk,)
_ -> return (chunk, [chunk])
where
doText con pre x = do
let (hd, tl) = ListLike.break (== nl) x
hd' <- doHead con pre hd
tl' <- doTail con pre tl
return $ hd' <> tl'
doHead _ _ x | ListLike.null x = return []
doHead con pre x = do
bol <- get
case bol of
BOL -> put MOL >> return [con (pre <> x)]
MOL -> return [con x]
doTail _ _ x | ListLike.null x = return []
doTail con pre x = do
bol <- get
put BOL
tl <- doText con pre (ListLike.tail x)
return $ (if bol == BOL then [con pre] else []) <> [con (singleton nl)] <> tl