gonito/Handler/Runner.hs

164 lines
5.2 KiB
Haskell
Raw Normal View History

2018-06-05 08:22:51 +02:00
module Handler.Runner where
import Import
2021-03-01 08:07:06 +01:00
import qualified System.IO as SIO
2018-06-05 08:22:51 +02:00
import System.Process
import System.Exit
2018-11-17 10:07:59 +01:00
import System.Environment
2018-06-05 08:22:51 +02:00
import Control.Concurrent.STM
import Control.Concurrent.Lifted (threadDelay)
import qualified Data.ByteString as BS
import Control.Monad.IO.Class
2018-06-05 08:22:51 +02:00
type Channel = TChan (Maybe Text)
data RunnerStatus a = RunnerOK a | RunnerError ExitCode
newtype Runner a = Runner { runRunner :: Channel -> Handler (RunnerStatus a) }
getChannel :: Runner Channel
getChannel = Runner {
runRunner = \chan -> return $ RunnerOK chan
}
instance Functor Runner where
fmap f runner = Runner {
runRunner = \chan -> do
s <- (runRunner runner) chan
return $ case s of
RunnerOK v -> RunnerOK $ f v
RunnerError e -> RunnerError e
}
instance Applicative Runner where
pure v = Runner {
2018-06-05 09:36:48 +02:00
runRunner = \_ -> return $ RunnerOK v
}
liftA2 f runner1 runner2 = Runner {
runRunner = \chan -> do
s1 <- (runRunner runner1) chan
case s1 of
RunnerOK v1 -> do
s2 <- (runRunner runner2) chan
case s2 of
RunnerOK v2 -> return $ RunnerOK $ f v1 v2
RunnerError e -> return $ RunnerError e
RunnerError e -> return $ RunnerError e
}
2018-06-05 09:36:48 +02:00
instance Monad Runner where
runner >>= k = Runner {
runRunner = \chan -> do
s <- (runRunner runner) chan
case s of
RunnerError e -> return $ RunnerError e
RunnerOK v -> do
sn <- (runRunner (k v)) chan
return $ case sn of
RunnerError e -> RunnerError e
RunnerOK w -> RunnerOK w
}
instance MonadIO Runner where
liftIO action = Runner {
runRunner = \_ -> do
r <- liftIO action
return $ RunnerOK r
}
2018-06-05 09:36:48 +02:00
runWithChannel :: Channel -> Runner () -> Handler ExitCode
runWithChannel chan runner = do
s <- (runRunner runner) chan
return $ case s of
RunnerOK () -> ExitSuccess
RunnerError e -> e
runProg :: Maybe FilePath -> FilePath -> [String] -> Runner ()
2022-01-19 12:46:23 +01:00
runProg workingDir programPath args = runProgWithEnv workingDir [] programPath args
runProgWithEnv :: Maybe FilePath -> [(String, String)] -> FilePath -> [String] -> Runner ()
runProgWithEnv workingDir extraEnv programPath args = Runner {
runRunner = \chan -> do
2022-01-19 12:46:23 +01:00
(code, _) <- runProgramWithEnv workingDir extraEnv programPath args chan
case code of
ExitSuccess -> return $ RunnerOK ()
_ -> return $ RunnerError code
}
2018-06-05 08:22:51 +02:00
runProgram :: Maybe FilePath -> FilePath -> [String] -> Channel -> Handler (ExitCode, Text)
2022-01-19 12:46:23 +01:00
runProgram workingDir programPath args chan =
runProgramWithEnv workingDir [] programPath args chan
runProgramWithEnv :: Maybe FilePath -> [(String, String)] -> FilePath -> [String] -> Channel -> Handler (ExitCode, Text)
runProgramWithEnv workingDir extraEnv programPath args chan = do
liftIO $ putStrLn $ pack $ show extraEnv
liftIO $ putStrLn $ pack $ show args
2018-11-17 10:07:59 +01:00
env <- liftIO $ getEnvironment
2018-06-05 08:22:51 +02:00
(_, Just hout, Just herr, pid) <-
2018-11-17 10:07:59 +01:00
liftIO $ createProcess (proc programPath args){
std_out = CreatePipe,
std_err = CreatePipe,
-- https://serverfault.com/questions/544156/git-clone-fail-instead-of-prompting-for-credentials
2022-01-19 12:46:23 +01:00
env = Just (("GIT_TERMINAL_PROMPT", "0") : (env ++ extraEnv)),
2018-11-17 10:07:59 +01:00
cwd = workingDir}
2018-06-05 08:22:51 +02:00
(code, out) <- gatherOutput pid hout herr chan
_ <- liftIO $ waitForProcess pid
return (code, out)
gatherOutput :: ProcessHandle -> Handle -> Handle -> Channel -> Handler (ExitCode, Text)
gatherOutput ph hout herr chan = work mempty mempty
where
work accout accerr = do
-- Read any outstanding input.
resterr <- takeABit herr accerr
restout <- takeABit hout accout
liftIO $ threadDelay 1000000
-- Check on the process.
s <- liftIO $ getProcessExitCode ph
-- Exit or loop.
case s of
Nothing -> work restout resterr
Just ec -> do
-- Get any last bit written between the read and the status
-- check.
_ <- takeFinalBit herr resterr
allTheRest <- takeFinalBit hout restout
return (ec, allTheRest)
2018-06-05 08:22:51 +02:00
takeABit h acc = do
bs <- liftIO $ BS.hGetNonBlocking h (64 * 1024)
2018-06-05 08:22:51 +02:00
let acc' = acc <> (decodeUtf8 bs)
let (fullLines, rest) = processOutput acc'
mapM_ (msg chan) fullLines
return rest
takeFinalBit h rest = do
lastPart <- liftIO $ BS.hGetContents h
let allTheRest = rest <> (decodeUtf8 lastPart)
mapM_ (msg chan) $ lines allTheRest
return allTheRest
2018-06-05 08:22:51 +02:00
msg :: Channel -> Text -> Handler ()
2021-03-01 08:07:06 +01:00
msg chan m = do
liftIO $ SIO.hPutStrLn stderr (unpack m)
liftIO $ atom $ writeTChan chan $ Just (m ++ "\n")
2018-06-05 08:22:51 +02:00
err :: Channel -> Text -> Handler ()
err = msg
raw :: Channel -> Text -> Handler ()
raw = msg
atom = Control.Concurrent.STM.atomically
processOutput :: Text -> ([Text], Text)
processOutput = processOutput' . lines
where processOutput' [] = ([], "")
processOutput' out = (init out, last out)
init [] = []
init [x] = []
init (x:xs) = (x:(init xs))
last [x] = x
last (_:xs) = last xs