{-# LANGUAGE LambdaCase #-} {-# LANGUAGE MultiWayIf #-} {-# LANGUAGE BinaryLiterals #-} {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE ScopedTypeVariables #-} {-# LANGUAGE DeriveDataTypeable #-} module Phoityne.GHCi.IO.Process ( ErrorData , GHCiProcess (..) , runProcess , exitProcess , writeLine , readTillPrompt , readTillEOF , readCharWhile , readCharWhileIO , readLineWhile , readLineWhileIO ) where 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 Control.Exception as E import qualified Data.String.Utils as U -- | -- command error message. -- type ErrorData = String -- | -- GHCi process data. -- data GHCiProcess = GHCiProcess { inGHCiProcess :: S.Handle , outGHCiProcess :: S.Handle , errGHCiProcess :: S.Handle , procGHCiProcess :: S.ProcessHandle , promptGHCiProcess :: String } -- | -- run ghci. -- runProcess :: String -> [String] -> FilePath -> String -> IO (Either ErrorData GHCiProcess) runProcess cmd opts cwd pmt = 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 ghciProc <- S.runProcess cmd opts (Just cwd) Nothing (Just fromPhoityneHandle) (Just toPhoityneHandle) (Just toPhoityneHandle) return . Right $ GHCiProcess toGHCiHandle fromGHCiHandle fromGHCiHandle ghciProc pmt 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" -- | -- 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