phoityne-vscode-0.0.21.0: app/Phoityne/GHCi/Process.hs
{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE MultiWayIf #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE DeriveDataTypeable #-}
module Phoityne.GHCi.Process (
ErrorData
, GHCiProcess (..)
, Lock(..)
, _BASE_GHCI_VERSION
, runProcess
, exitProcess
, writeLine
, readTillPrompt
, readTillEOF
, readCharWhile
, readCharWhileIO
, readLineWhile
, readLineWhileIO
) where
import Control.Concurrent
import GHC.IO.Encoding
import Distribution.System
import qualified System.Process as S
import qualified System.IO as S
import qualified System.Exit as S
import qualified System.Environment as S
import qualified Control.Exception.Safe as E
import qualified Data.String.Utils as U
import qualified Data.Map as M
import qualified Data.Version as V
-- |
-- command error message.
--
type ErrorData = String
-- |
-- command error message.
--
_BASE_GHCI_VERSION :: V.Version
_BASE_GHCI_VERSION = V.Version [8, 0, 0] []
-- |
-- command error message.
--
data Lock = Lock
-- |
-- GHCi process data.
--
data GHCiProcess = GHCiProcess
{
inGHCiProcess :: S.Handle
, outGHCiProcess :: S.Handle
, errGHCiProcess :: S.Handle
, procGHCiProcess :: S.ProcessHandle
, promptGHCiProcess :: String
, versionGHCiProcess :: V.Version
, lockGHCiProcess :: MVar Lock
}
-- |
-- run ghci.
--
runProcess :: String
-> [String]
-> FilePath
-> String
-> M.Map String String
-> IO (Either ErrorData GHCiProcess)
runProcess cmd opts cwd pmt envs = flip E.catches handlers $ do
(fromPhoityneHandle, toGHCiHandle) <- S.createPipe
(fromGHCiHandle, toPhoityneHandle) <- S.createPipe
osEnc <- getReadHandleEncoding
S.hSetBuffering toPhoityneHandle S.NoBuffering
S.hSetEncoding toPhoityneHandle osEnc
S.hSetNewlineMode toPhoityneHandle $ S.NewlineMode S.CRLF S.LF
S.hSetBuffering fromPhoityneHandle S.NoBuffering
S.hSetEncoding fromPhoityneHandle S.utf8
S.hSetNewlineMode fromPhoityneHandle $ S.NewlineMode S.LF S.LF
S.hSetBuffering toGHCiHandle S.NoBuffering
S.hSetEncoding toGHCiHandle S.utf8
S.hSetNewlineMode toGHCiHandle $ S.NewlineMode S.LF S.LF
S.hSetBuffering fromGHCiHandle S.NoBuffering
S.hSetEncoding fromGHCiHandle osEnc
S.hSetNewlineMode fromGHCiHandle $ S.NewlineMode S.CRLF S.LF
runEnvs <- getRunEnv
ghciProc <- S.runProcess cmd opts (Just cwd) runEnvs (Just fromPhoityneHandle) (Just toPhoityneHandle) (Just toPhoityneHandle)
mvarLock <- newMVar Lock
return . Right $ GHCiProcess toGHCiHandle fromGHCiHandle fromGHCiHandle ghciProc pmt _BASE_GHCI_VERSION mvarLock
where
handlers = [ E.Handler someExcept ]
someExcept (e :: E.SomeException) = return . Left . show $ e
-- |
--
--
getReadHandleEncoding :: IO TextEncoding
getReadHandleEncoding = if
| Windows == buildOS -> mkTextEncoding "CP932//TRANSLIT"
| otherwise -> mkTextEncoding "UTF-8//TRANSLIT"
-- |
--
--
getRunEnv
| null envs = return Nothing
| otherwise = do
curEnvs <- S.getEnvironment
return $ Just $ M.toList envs ++ curEnvs
-- |
-- exit ghci.
--
exitProcess :: GHCiProcess -> IO (Either ErrorData S.ExitCode)
exitProcess (GHCiProcess _ _ _ proc _ _ _) = flip E.catches handlers $ do
code <- S.waitForProcess proc
return . Right $ code
where
handlers = [ E.Handler someExcept ]
someExcept (e :: E.SomeException) = return . Left . show $ e
-- |
-- write to ghci.
--
writeLine :: GHCiProcess -> String -> IO (Either ErrorData ())
writeLine (GHCiProcess ghciIn _ _ _ _ _ _) writeData = flip E.catches handlers $ S.hIsOpen ghciIn >>= \case
True -> do
S.hPutStrLn ghciIn writeData
return $ Right ()
False -> return $ Left "handle not open."
where
handlers = [ E.Handler someExcept ]
someExcept (e :: E.SomeException) = return . Left . show $ e
-- |
-- read char till prompt.
--
readTillPrompt :: GHCiProcess -> IO (Either ErrorData String)
readTillPrompt proc@(GHCiProcess _ _ _ _ pmt _ _) = readCharWhile proc (not . U.endswith pmt)
-- |
-- read char till EOF.
--
readTillEOF :: GHCiProcess -> IO (Either ErrorData String)
readTillEOF proc = readCharWhile proc (const True)
-- |
-- read char from ghci.
--
readCharWhile :: GHCiProcess -> (String -> Bool) -> IO (Either ErrorData String)
readCharWhile (GHCiProcess _ ghciOut _ _ _ _ _) condProc = flip E.catches handlers $ S.hIsOpen ghciOut >>= \case
True -> go []
False -> return . Left $ "handle not open."
where
go acc = S.hIsEOF ghciOut >>= \case
True -> return . Right $ acc
False -> do
c <- S.hGetChar ghciOut
let acc' = acc ++ [c]
if condProc acc' then go acc'
else return . Right $ acc'
handlers = [ E.Handler someExcept ]
someExcept (e :: E.SomeException) = return . Left . show $ e
-- |
-- read char from ghci.
--
readCharWhileIO :: GHCiProcess -> (String -> IO Bool) -> IO (Either ErrorData String)
readCharWhileIO (GHCiProcess _ ghciOut _ _ _ _ _) condProc = flip E.catches handlers $ S.hIsOpen ghciOut >>= \case
True -> go []
False -> return . Left $ "handle not open."
where
go acc = S.hIsEOF ghciOut >>= \case
True -> return . Right $ acc
False -> do
c <- S.hGetChar ghciOut
let acc' = acc ++ [c]
condProc acc' >>= \case
True -> go acc'
False -> return . Right $ acc'
handlers = [ E.Handler someExcept ]
someExcept (e :: E.SomeException) = return . Left . show $ e
-- |
-- read line from ghci.
--
readLineWhile :: GHCiProcess -> ([String] -> Bool) -> IO (Either ErrorData [String])
readLineWhile (GHCiProcess _ ghciOut _ _ _ _ _) condProc = flip E.catches handlers $ S.hIsOpen ghciOut >>= \case
True -> go []
False -> return . Left $ "handle not open."
where
go acc = S.hIsEOF ghciOut >>= \case
True -> return . Right $ acc
False -> do
l <- S.hGetLine ghciOut
let acc' = acc ++ [l]
if condProc acc' then go acc'
else return . Right $ acc'
handlers = [ E.Handler someExcept ]
someExcept (e :: E.SomeException) = return . Left . show $ e
-- |
-- read line from ghci.
--
readLineWhileIO :: GHCiProcess -> ([String] -> IO Bool) -> IO (Either ErrorData [String])
readLineWhileIO (GHCiProcess _ ghciOut _ _ _ _ _) condProc = flip E.catches handlers $ S.hIsOpen ghciOut >>= \case
True -> go []
False -> return . Left $ "handle not open."
where
go acc = S.hIsEOF ghciOut >>= \case
True -> return . Right $ acc
False -> do
l <- S.hGetLine ghciOut
let acc' = acc ++ [l]
condProc acc' >>= \case
True -> go acc'
False -> return . Right $ acc'
handlers = [ E.Handler someExcept ]
someExcept (e :: E.SomeException) = return . Left . show $ e